import { useSyncExternalStore } from "react";
import { message } from "../ui";
import type { DeviceDetail, DeviceModem, ModemPnn } from "./types";
import { tl } from "../../lib/i18n";
import { lookupCarrier } from "../../lib/carrier";

/* ---------------------------------------------------------------------------
 * Lifecycle / status helpers (ported from the VoHive reference).
 * ------------------------------------------------------------------------- */

// Wt: phases where the control plane is recovering.
export function isRecoveringPhase(phase?: string): boolean {
  return (
    phase === "rebooting" ||
    phase === "usb_wait" ||
    phase === "worker_starting" ||
    phase === "qmi_starting" ||
    phase === "recovering" ||
    phase === "evicting"
  );
}

// ra: human label for a lifecycle phase.
export function lifecycleLabel(phase?: string): string {
  switch (phase) {
    case "rebooting":
      return tl("重启中");
    case "usb_wait":
      return tl("等待设备重新枚举");
    case "worker_starting":
      return tl("设备启动中");
    case "qmi_starting":
      return tl("QMI 启动中");
    case "recovering":
      return tl("控制面恢复中");
    case "degraded":
      return tl("控制面不稳定");
    case "evicting":
      return tl("重新接管中");
    case "online":
      return tl("在线");
    case "offline":
      return tl("离线");
    default:
      return "";
  }
}

// qt: device is online (running + control/healthy) and not recovering.
export function isDeviceOnline(device?: { running?: boolean; controlOnline?: boolean; healthy?: boolean; lifecyclePhase?: string } | null): boolean {
  if (!device || isRecoveringPhase(device.lifecyclePhase)) return false;
  return !!device.running && (device.controlOnline ?? device.healthy) === true;
}

// Ia: registered on the network (reg_status 1=home, 5=roaming).
export function isRegistered(device?: { modem?: { regStatus?: number } } | null): boolean {
  const s = device?.modem?.regStatus;
  return s === 1 || s === 5;
}

export interface StatusMeta {
  label: string;
  tag: "success" | "warning" | "danger";
  tone: "success" | "warning" | "danger";
  animated: boolean;
}

// us: status meta for a device list item.
export function deviceStatusMeta(device?: { running?: boolean; controlOnline?: boolean; healthy?: boolean; lifecyclePhase?: string } | null): StatusMeta {
  const phase = device?.lifecyclePhase;
  if (isRecoveringPhase(phase)) return { label: lifecycleLabel(phase) || tl("恢复中"), tag: "warning", tone: "warning", animated: true };
  if (phase === "degraded") return { label: tl("不稳定"), tag: "warning", tone: "warning", animated: true };
  if (device?.running) {
    return isDeviceOnline(device)
      ? { label: tl("在线"), tag: "success", tone: "success", animated: true }
      : { label: tl("恢复中"), tag: "warning", tone: "warning", animated: true };
  }
  return { label: tl("离线"), tag: "danger", tone: "danger", animated: false };
}

/* ---------------------------------------------------------------------------
 * Clipboard copy (Ea).
 * ------------------------------------------------------------------------- */
export async function copyText(value: unknown, msg = tl("已复制")): Promise<boolean> {
  const text = String(value ?? "").trim();
  if (!text) return false;
  try {
    if (navigator.clipboard?.writeText) {
      await navigator.clipboard.writeText(text);
      message.success(msg);
      return true;
    }
  } catch {
    /* fall through */
  }
  try {
    const ta = document.createElement("textarea");
    ta.value = text;
    ta.setAttribute("readonly", "true");
    ta.className = "fixed left-0 top-0 h-px w-px opacity-0 pointer-events-none";
    document.body.appendChild(ta);
    ta.focus();
    ta.select();
    ta.setSelectionRange(0, ta.value.length);
    const ok = document.execCommand("copy");
    document.body.removeChild(ta);
    if (ok) {
      message.success(msg);
      return true;
    }
  } catch {
    /* fall through */
  }
  message.warning(tl("浏览器限制，请手动复制"));
  return false;
}

/* ---------------------------------------------------------------------------
 * Sensitive-info visibility store (vohive_show_sensitive), shared across tabs.
 * ------------------------------------------------------------------------- */
const SENSITIVE_KEY = "vocat_show_sensitive";
let sensitiveValue = readSensitive();
const sensitiveListeners = new Set<() => void>();

function readSensitive(): boolean {
  try {
    return typeof window === "undefined" ? false : window.localStorage.getItem(SENSITIVE_KEY) === "1";
  } catch {
    return false;
  }
}
function writeSensitive(value: boolean) {
  try {
    if (typeof window !== "undefined") window.localStorage.setItem(SENSITIVE_KEY, value ? "1" : "0");
  } catch {
    /* ignore */
  }
}
function setSensitive(value: boolean) {
  if (sensitiveValue === value) return;
  sensitiveValue = value;
  writeSensitive(value);
  for (const listener of sensitiveListeners) listener();
}
function subscribeSensitive(listener: () => void) {
  sensitiveListeners.add(listener);
  return () => {
    sensitiveListeners.delete(listener);
  };
}

export function useShowSensitive(): [boolean, () => void] {
  const value = useSyncExternalStore(subscribeSensitive, () => sensitiveValue);
  return [value, () => setSensitive(!sensitiveValue)];
}

/* ---------------------------------------------------------------------------
 * Control-device backend detection (Ut): wwan<N>qmi<N> => fixed QMI backend.
 * ------------------------------------------------------------------------- */
export function isQmiControl(controlDevice?: string): boolean {
  const s = String(controlDevice || "").trim();
  if (!s) return false;
  const base = s.replace(/\\/g, "/").split("/").filter(Boolean).pop() || s;
  return /^wwan\d+qmi\d+$/.test(base);
}

/* ---------------------------------------------------------------------------
 * Signal helpers (5-bar overview variant).
 * ------------------------------------------------------------------------- */
export function signalValid(dbm?: number | null): boolean {
  return typeof dbm === "number" && Number.isFinite(dbm) && dbm !== 0 && dbm !== -999;
}
// 0..5 bars
export function signalLevel(dbm?: number | null): number {
  if (!signalValid(dbm)) return 0;
  const d = dbm as number;
  return d >= -75 ? 5 : d >= -85 ? 4 : d >= -95 ? 3 : d >= -105 ? 2 : 1;
}
export type SignalTone = "green" | "amber" | "red" | "gray";
export function signalTone(dbm?: number | null): SignalTone {
  if (!signalValid(dbm)) return "gray";
  const d = dbm as number;
  return d >= -85 ? "green" : d >= -100 ? "amber" : "red";
}

/* ---------------------------------------------------------------------------
 * SIM operator display (native SPN / OPL+PNN / PLMN).
 * ------------------------------------------------------------------------- */
function plmnOf(modem?: DeviceModem): string {
  const mcc = String(modem?.nativeMcc ?? "").trim();
  const mnc = String(modem?.nativeMnc ?? "").trim();
  return mcc && mnc ? `${mcc}${mnc}` : "";
}
function pnnName(pnn?: ModemPnn): string {
  return String(pnn?.fullName ?? "").trim() || String(pnn?.shortName ?? "").trim();
}
function firstPnnName(list?: ModemPnn[]): string {
  if (!Array.isArray(list)) return "";
  for (const item of list) {
    const name = pnnName(item);
    if (name) return name;
  }
  return "";
}
// $t: PLMN match supporting "x" wildcards.
function plmnMatch(pattern: unknown, plmn: string): boolean {
  const p = String(pattern ?? "").trim().toLowerCase();
  if (!p || !plmn) return false;
  if (p === plmn) return true;
  if (!p.includes("x")) return p.length < plmn.length && plmn.startsWith(p);
  if (p.length !== plmn.length) return false;
  for (let i = 0; i < p.length; i++) if (p[i] !== "x" && p[i] !== plmn[i]) return false;
  return true;
}
// Ct: derive operator name from OPL -> PNN records.
function oplPnnName(modem?: DeviceModem): string {
  const plmn = plmnOf(modem);
  if (!plmn || !Array.isArray(modem?.opl) || !Array.isArray(modem?.pnn)) return "";
  for (const opl of modem!.opl!) {
    if (!plmnMatch(opl?.plmn, plmn)) continue;
    const record = Number(opl?.pnnRecord ?? 0);
    if (!record) continue;
    const name = pnnName(modem!.pnn!.find((p) => p.record === record));
    if (name) return name;
  }
  return "";
}

function withPlmn(name: string, plmn: string): string {
  return plmn ? `${name} (${plmn})` : name;
}

// cardPlmnOf: the SIM's home PLMN from the card MCC/MNC (last-resort display).
function cardPlmnOf(modem?: DeviceModem): string {
  const mcc = String(modem?.cardMcc ?? "").trim();
  const mnc = String(modem?.cardMnc ?? "").trim();
  return mcc && mnc ? `${mcc}${mnc}` : "";
}

// Dt: the "原运营商" display string.
export function simOperatorDisplay(device?: DeviceDetail | null): string {
  const modem = device?.modem;
  if (!device) return "--";
  const spn = String(modem?.nativeSpn ?? "").trim();
  const name = oplPnnName(modem) || firstPnnName(modem?.pnn);
  const plmn = plmnOf(modem);
  if (spn) return withPlmn(spn, plmn);
  if (name) return withPlmn(name, plmn);
  // Home ("original") carrier resolved from the SIM's IMSI via the MCC/MNC table.
  // Readable even when the modem isn't camped (VoWiFi RF-off / flight mode).
  const carrier = lookupCarrier(modem?.imsi);
  if (carrier) return withPlmn(carrier.name, carrier.mcc + carrier.mnc);
  if (plmn) return plmn;
  const cardPlmn = cardPlmnOf(modem);
  if (cardPlmn) return cardPlmn;
  return "--";
}

/* ---------------------------------------------------------------------------
 * Minimal SSE reader (fetch + ReadableStream), cookie-authenticated.
 * onData fires per `data:` line; onEvent fires per blank-line dispatch.
 * ------------------------------------------------------------------------- */
export interface EventStreamHandlers {
  onEvent?: (event: string, data: string) => void;
  onData?: (data: string) => void;
  signal?: AbortSignal;
}

export async function readEventStream(
  path: string,
  params: Record<string, string | undefined>,
  handlers: EventStreamHandlers,
): Promise<void> {
  const qs = new URLSearchParams();
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined && value !== "") qs.set(key, value);
  }
  const query = qs.toString();
  const url = `${path.startsWith("/api") ? path : `/api${path}`}${query ? `?${query}` : ""}`;
  const response = await fetch(url, {
    method: "GET",
    headers: { Accept: "text/event-stream" },
    credentials: "include",
    signal: handlers.signal,
  });
  if (!response.ok) throw new Error((await response.text()) || `HTTP ${response.status}`);
  if (!response.body) throw new Error("No stream body");

  const reader = response.body.getReader();
  const decoder = new TextDecoder("utf-8");
  let buffer = "";
  let event = "";
  let dataLines: string[] = [];
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    for (;;) {
      const idx = buffer.indexOf("\n");
      if (idx < 0) break;
      let line = buffer.slice(0, idx);
      buffer = buffer.slice(idx + 1);
      if (line.endsWith("\r")) line = line.slice(0, -1);
      if (line === "") {
        const data = dataLines.join("\n");
        if (event && handlers.onEvent) handlers.onEvent(event, data);
        event = "";
        dataLines = [];
        continue;
      }
      if (line.startsWith("event:")) {
        event = line.slice(6).trim();
        continue;
      }
      if (line.startsWith("data:")) {
        const payload = line.slice(5).replace(/^\s*/, "");
        dataLines.push(payload);
        handlers.onData?.(payload);
        continue;
      }
    }
  }
}
