"use client";

import {
  Cable,
  ExternalLink,
  LayoutGrid,
  Link2,
  MousePointer2,
  Pause,
  Play,
  Radio,
  RefreshCw,
  Smartphone,
  Sparkles,
  Usb,
  Wifi,
} from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";

import { androidScrcpyWsUrl } from "@/lib/androidScrcpyWs";
import { fetchJsonResponse } from "@/lib/fetchJson";
import { ScrcpyH264Decoder, webCodecsH264Supported } from "@/lib/scrcpyH264Decoder";

type AndroidOverview = {
  model?: string;
  brand?: string;
  android_version?: string;
  sdk?: string;
  battery?: string;
  resolution?: string;
};

export type DeviceRow = {
  serial: string;
  model?: string;
  state: string;
  transport?: string;
};
type TFn = (zh: string, en: string) => string;

const BOOKMARKS_KEY = "live-hub-adb-bookmarks-v1";

function clientPointToCanvasPixel(
  canvas: HTMLCanvasElement,
  clientX: number,
  clientY: number,
): { x: number; y: number } | null {
  const rect = canvas.getBoundingClientRect();
  const nw = canvas.width;
  const nh = canvas.height;
  if (!nw || !nh) return null;
  const elAR = rect.width / rect.height;
  const imAR = nw / nh;
  let drawW: number;
  let drawH: number;
  let offX: number;
  let offY: number;
  if (imAR > elAR) {
    drawW = rect.width;
    drawH = rect.width / imAR;
    offX = 0;
    offY = (rect.height - drawH) / 2;
  } else {
    drawH = rect.height;
    drawW = rect.height * imAR;
    offX = (rect.width - drawW) / 2;
    offY = 0;
  }
  const lx = clientX - rect.left - offX;
  const ly = clientY - rect.top - offY;
  if (lx < 0 || ly < 0 || lx > drawW || ly > drawH) return null;
  return {
    x: Math.max(0, Math.min(nw - 1, Math.round((lx / drawW) * nw))),
    y: Math.max(0, Math.min(nh - 1, Math.round((ly / drawH) * nh))),
  };
}

function clientPointToImagePixel(
  img: HTMLImageElement,
  clientX: number,
  clientY: number,
): { x: number; y: number } | null {
  const rect = img.getBoundingClientRect();
  const nw = img.naturalWidth;
  const nh = img.naturalHeight;
  if (!nw || !nh) return null;
  const elAR = rect.width / rect.height;
  const imAR = nw / nh;
  let drawW: number;
  let drawH: number;
  let offX: number;
  let offY: number;
  if (imAR > elAR) {
    drawW = rect.width;
    drawH = rect.width / imAR;
    offX = 0;
    offY = (rect.height - drawH) / 2;
  } else {
    drawH = rect.height;
    drawW = rect.height * imAR;
    offX = (rect.width - drawW) / 2;
    offY = 0;
  }
  const lx = clientX - rect.left - offX;
  const ly = clientY - rect.top - offY;
  if (lx < 0 || ly < 0 || lx > drawW || ly > drawH) return null;
  return {
    x: Math.max(0, Math.min(nw - 1, Math.round((lx / drawW) * nw))),
    y: Math.max(0, Math.min(nh - 1, Math.round((ly / drawH) * nh))),
  };
}

const INTERVALS_MS = [0, 280, 450, 700, 1100] as const;

function transportBadge(tr: string | undefined) {
  switch (tr) {
    case "usb":
      return { Icon: Usb, zh: "USB", en: "USB", cls: "bg-amber-500/20 text-amber-100 ring-amber-500/35" };
    case "tcp":
      return { Icon: Wifi, zh: "网络", en: "TCP", cls: "bg-cyan-500/20 text-cyan-100 ring-cyan-500/35" };
    case "emulator":
      return { Icon: Cable, zh: "模拟器", en: "Emu", cls: "bg-fuchsia-500/20 text-fuchsia-100 ring-fuchsia-500/35" };
    default:
      return { Icon: Smartphone, zh: "?", en: "?", cls: "bg-zinc-600/30 text-zinc-400" };
  }
}

type Props = {
  t: TFn;
  apiBase: string;
  fetchJson: <T,>(path: string, init?: RequestInit) => Promise<T>;
  serial: string;
  devices: DeviceRow[];
  setSerial: (s: string) => void;
  adbAvailable: boolean;
  lock: boolean;
  overview: AndroidOverview | null;
  onAndroidAction: (action: string, payload?: Record<string, unknown>) => Promise<void>;
  notify: (msg: string) => void;
  onRefreshDevices: () => void;
};

const SCRCPY_URL = "https://github.com/Genymobile/scrcpy";
const ESCRCPY_URL = "https://github.com/viarotel-org/escrcpy";

/** 与 LiveControlApp 一致：apiBase 为空时用根相对路径（同域 FastAPI 托管静态页）。 */
function apiUrl(apiBase: string, path: string): string {
  const p = path.startsWith("/") ? path : `/${path}`;
  const b = (apiBase || "").trim().replace(/\/$/, "");
  return b ? `${b}${p}` : p;
}

export function LiveAndroidStreamConsole({
  t,
  apiBase,
  fetchJson,
  serial,
  devices,
  setSerial,
  adbAvailable,
  lock,
  overview,
  onAndroidAction,
  notify,
  onRefreshDevices,
}: Props) {
  const [frameTs, setFrameTs] = useState(0);
  const [liveMs, setLiveMs] = useState<(typeof INTERVALS_MS)[number]>(450);
  const [imgBusy, setImgBusy] = useState(false);
  const [imgErr, setImgErr] = useState<string | null>(null);
  const [metrics, setMetrics] = useState<{ width: number; height: number } | null>(null);
  const [tapMode, setTapMode] = useState(true);
  const [connectAddr, setConnectAddr] = useState("");
  const [pairHost, setPairHost] = useState("");
  const [pairPort, setPairPort] = useState("37777");
  const [pairCode, setPairCode] = useState("");
  const [connBusy, setConnBusy] = useState(false);
  const [bookmarks, setBookmarks] = useState<string[]>([]);
  const [scrcpyStream, setScrcpyStream] = useState(false);
  /** 用户在本机序列号上点了「关闭真流」后，不再自动拉起，直到切换设备。 */
  const [scrcpyUserStoppedForSerial, setScrcpyUserStoppedForSerial] = useState<string | null>(null);
  const [scrcpyConnected, setScrcpyConnected] = useState(false);
  const [scrcpyErr, setScrcpyErr] = useState<string | null>(null);
  const [scrcpyInfo, setScrcpyInfo] = useState<{
    server_jar?: boolean;
    adb_ok?: boolean;
    server_version?: string;
    hint_zh?: string;
  } | null>(null);
  const lastTapRef = useRef<{ t: number; x: number; y: number } | null>(null);
  const imgRef = useRef<HTMLImageElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const decoderRef = useRef<ScrcpyH264Decoder | null>(null);
  const mirrorFocusRef = useRef<HTMLDivElement | null>(null);
  const shotFailRef = useRef(0);

  const activeTransport = devices.find((d) => d.serial === serial)?.transport;
  const useFastShot = liveMs > 0;

  const shotUrl = serial
    ? `${apiUrl(apiBase, "/android/screenshot")}?serial=${encodeURIComponent(serial)}&ts=${frameTs}${useFastShot ? "&fast=1" : ""}`
    : "";

  const bumpFrame = useCallback(() => {
    setFrameTs((n) => n + 1);
  }, []);

  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(BOOKMARKS_KEY);
      if (raw) {
        const v = JSON.parse(raw) as unknown;
        if (Array.isArray(v)) setBookmarks(v.map(String).filter(Boolean).slice(0, 24));
      }
    } catch {
      /* ignore */
    }
  }, []);

  const saveBookmark = (addr: string) => {
    const a = addr.trim();
    if (!a) return;
    const next = [a, ...bookmarks.filter((x) => x !== a)].slice(0, 16);
    setBookmarks(next);
    try {
      window.localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(next));
    } catch {
      /* ignore */
    }
    notify(t("已保存快捷地址", "Saved quick address"));
  };

  useEffect(() => {
    if (!serial || !adbAvailable) {
      setMetrics(null);
      return;
    }
    let cancelled = false;
    void (async () => {
      try {
        const data = await fetchJsonResponse<{ width?: number; height?: number }>(
          `${apiUrl(apiBase, "/android/display_metrics")}?serial=${encodeURIComponent(serial)}`,
        );
        if (cancelled) return;
        if (typeof data.width === "number" && typeof data.height === "number") {
          setMetrics({ width: data.width, height: data.height });
        }
      } catch {
        /* ignore */
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [apiBase, adbAvailable, serial]);

  useEffect(() => {
    if (!adbAvailable || !serial || liveMs <= 0 || scrcpyStream) return;
    const id = window.setInterval(() => setFrameTs((n) => n + 1), liveMs);
    return () => clearInterval(id);
  }, [adbAvailable, liveMs, serial, scrcpyStream]);

  useEffect(() => {
    if (!adbAvailable) {
      setScrcpyInfo(null);
      return;
    }
    let cancelled = false;
    void (async () => {
      try {
        const data = await fetchJsonResponse<{
          server_jar?: boolean;
          adb_ok?: boolean;
          server_version?: string;
          hint_zh?: string;
        }>(apiUrl(apiBase, "/android/scrcpy/info"));
        if (!cancelled) setScrcpyInfo(data);
      } catch {
        /* ignore */
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [apiBase, adbAvailable]);

  useEffect(() => {
    if (!scrcpyStream) setScrcpyErr(null);
  }, [scrcpyStream]);

  useEffect(() => {
    setScrcpyUserStoppedForSerial(null);
  }, [serial]);

  useEffect(() => {
    const row = devices.find((d) => d.serial === serial);
    const userStopped = Boolean(serial && scrcpyUserStoppedForSerial === serial);
    const canAuto =
      adbAvailable &&
      Boolean(serial) &&
      row?.state === "device" &&
      Boolean(scrcpyInfo?.server_jar) &&
      webCodecsH264Supported() &&
      !userStopped;

    if (canAuto) {
      setScrcpyStream(true);
      setLiveMs(0);
      return;
    }

    if (userStopped || !serial || !adbAvailable) {
      setScrcpyStream(false);
      return;
    }
    if (scrcpyInfo && !scrcpyInfo.server_jar) {
      setScrcpyStream(false);
      return;
    }
    if (!webCodecsH264Supported()) {
      setScrcpyStream(false);
      return;
    }
    if (row && row.state !== "device") {
      setScrcpyStream(false);
      return;
    }
  }, [serial, devices, adbAvailable, scrcpyInfo, scrcpyUserStoppedForSerial]);

  useEffect(() => {
    if (!scrcpyStream || !serial || !adbAvailable) {
      wsRef.current?.close();
      wsRef.current = null;
      decoderRef.current?.reset();
      decoderRef.current = null;
      setScrcpyConnected(false);
      return;
    }
    if (!webCodecsH264Supported()) {
      setScrcpyErr(t("当前浏览器不支持 WebCodecs H.264", "WebCodecs H.264 not supported in this browser"));
      setScrcpyStream(false);
      return;
    }
    const canvas = canvasRef.current;
    if (!canvas) return;

    const dec = new ScrcpyH264Decoder(canvas, (msg) => setScrcpyErr(msg));
    decoderRef.current = dec;

    const wsUrl = androidScrcpyWsUrl(apiBase, serial);
    const ws = new WebSocket(wsUrl);
    wsRef.current = ws;
    ws.binaryType = "arraybuffer";

    ws.onopen = () => {
      setScrcpyConnected(true);
      setScrcpyErr(null);
    };
    ws.onmessage = (ev) => {
      if (typeof ev.data === "string") {
        try {
          const j = JSON.parse(ev.data) as { error?: string };
          if (j.error) setScrcpyErr(j.error);
        } catch {
          /* ignore */
        }
        return;
      }
      decoderRef.current?.push(ev.data as ArrayBuffer);
    };
    ws.onerror = () => {
      setScrcpyErr(t("WebSocket 异常", "WebSocket error"));
    };
    ws.onclose = () => {
      setScrcpyConnected(false);
      decoderRef.current?.reset();
      decoderRef.current = null;
    };

    return () => {
      ws.close();
      wsRef.current = null;
      dec.reset();
      if (decoderRef.current === dec) decoderRef.current = null;
    };
  }, [scrcpyStream, serial, adbAvailable, apiBase, t]);

  useEffect(() => {
    if (serial && adbAvailable) {
      shotFailRef.current = 0;
      setFrameTs((n) => n + 1);
    }
  }, [serial, adbAvailable]);

  const runConnect = async () => {
    const addr = connectAddr.trim();
    if (!addr) {
      notify(t("填写 host:port", "Enter host:port"));
      return;
    }
    setConnBusy(true);
    try {
      const r = await fetchJson<{ ok?: boolean; message?: string }>("/android/connect", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ address: addr }),
      });
      notify(r.message || (r.ok ? "OK" : t("连接结果见日志", "See message")));
      onRefreshDevices();
      bumpFrame();
    } catch (e) {
      notify((e as Error).message);
    } finally {
      setConnBusy(false);
    }
  };

  const runDisconnect = async (addr: string) => {
    setConnBusy(true);
    try {
      const r = await fetchJson<{ ok?: boolean; message?: string }>("/android/disconnect", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ address: addr }),
      });
      notify(r.message || "OK");
      onRefreshDevices();
    } catch (e) {
      notify((e as Error).message);
    } finally {
      setConnBusy(false);
    }
  };

  const runPair = async () => {
    const h = pairHost.trim();
    const p = parseInt(pairPort, 10) || 37777;
    const c = pairCode.trim();
    if (!h || !c) {
      notify(t("填写配对 IP 与码", "Host + code required"));
      return;
    }
    setConnBusy(true);
    try {
      const r = await fetchJson<{ ok?: boolean; message?: string }>("/android/pair", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ host: h, port: p, code: c }),
      });
      notify(r.message || (r.ok ? t("配对成功后再点连接", "Paired — now Connect") : "pair"));
    } catch (e) {
      notify((e as Error).message);
    } finally {
      setConnBusy(false);
    }
  };

  const onImageClick = async (e: React.MouseEvent<HTMLImageElement>) => {
    if (!tapMode || lock || !adbAvailable || !serial) return;
    const img = imgRef.current;
    if (!img) return;
    const p = clientPointToImagePixel(img, e.clientX, e.clientY);
    if (!p) return;

    const now = Date.now();
    const prev = lastTapRef.current;
    if (prev && now - prev.t < 320 && Math.abs(prev.x - p.x) < 28 && Math.abs(prev.y - p.y) < 28) {
      lastTapRef.current = null;
      await onAndroidAction("double_tap", { x: p.x, y: p.y });
      notify(t("双击", "Double tap"));
    } else {
      lastTapRef.current = { t: now, x: p.x, y: p.y };
      await onAndroidAction("tap", { x: p.x, y: p.y });
    }
    bumpFrame();
  };

  const onImageContextMenu = async (e: React.MouseEvent<HTMLImageElement>) => {
    e.preventDefault();
    if (lock || !adbAvailable || !serial) return;
    const img = imgRef.current;
    if (!img) return;
    const p = clientPointToImagePixel(img, e.clientX, e.clientY);
    if (!p) return;
    await onAndroidAction("long_press", { x: p.x, y: p.y, duration: 720 });
    notify(t("长按", "Long press"));
    bumpFrame();
  };

  const onMirrorKeyDown = (e: React.KeyboardEvent) => {
    if (!serial || lock) return;
    const tag = (e.target as HTMLElement).tagName;
    if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
    const k = e.key.toLowerCase();
    const map: Record<string, [string, Record<string, unknown>?]> = {
      h: ["key", { keycode: "3" }],
      b: ["key", { keycode: "4" }],
      r: ["key", { keycode: "187" }],
      w: ["wake", undefined],
      m: ["key", { keycode: "82" }],
    };
    const hit = map[k];
    if (!hit) return;
    e.preventDefault();
    void (async () => {
      await onAndroidAction(hit[0], hit[1]);
      if (!scrcpyStream) bumpFrame();
    })();
  };

  const onCanvasClick = async (e: React.MouseEvent<HTMLCanvasElement>) => {
    if (!tapMode || lock || !adbAvailable || !serial) return;
    const c = canvasRef.current;
    if (!c) return;
    const p = clientPointToCanvasPixel(c, e.clientX, e.clientY);
    if (!p) return;
    const now = Date.now();
    const prev = lastTapRef.current;
    if (prev && now - prev.t < 320 && Math.abs(prev.x - p.x) < 28 && Math.abs(prev.y - p.y) < 28) {
      lastTapRef.current = null;
      await onAndroidAction("double_tap", { x: p.x, y: p.y });
      notify(t("双击", "Double tap"));
    } else {
      lastTapRef.current = { t: now, x: p.x, y: p.y };
      await onAndroidAction("tap", { x: p.x, y: p.y });
    }
    if (!scrcpyStream) bumpFrame();
  };

  const onCanvasContextMenu = async (e: React.MouseEvent<HTMLCanvasElement>) => {
    e.preventDefault();
    if (lock || !adbAvailable || !serial) return;
    const c = canvasRef.current;
    if (!c) return;
    const p = clientPointToCanvasPixel(c, e.clientX, e.clientY);
    if (!p) return;
    await onAndroidAction("long_press", { x: p.x, y: p.y, duration: 720 });
    notify(t("长按", "Long press"));
    if (!scrcpyStream) bumpFrame();
  };

  const onImgError = () => {
    setImgBusy(false);
    shotFailRef.current += 1;
    if (shotFailRef.current <= 3) {
      setImgErr(null);
      window.setTimeout(() => bumpFrame(), 380 + shotFailRef.current * 200);
    } else {
      setImgErr(t("截图连续失败：检查 USB 授权 / 无线是否断开 / Redroid 端口", "Screenshot failed repeatedly"));
    }
  };

  const onImgLoad = () => {
    setImgBusy(false);
    shotFailRef.current = 0;
    setImgErr(null);
  };

  if (!adbAvailable) {
    return (
      <div className="rounded-2xl border border-amber-500/25 bg-amber-500/[0.06] p-6 text-center text-sm text-amber-200/90">
        {t("宿主机未安装 adb，无法使用 Live 控制台。", "adb is required for the Live console.")}
      </div>
    );
  }

  const tb = transportBadge(activeTransport);
  const HintIcon = tb.Icon;

  return (
    <div className="space-y-5">
      <div className="rounded-2xl border border-sky-500/20 bg-gradient-to-br from-sky-500/[0.08] via-zinc-950/80 to-zinc-900/90 p-4 ring-1 ring-white/[0.05]">
        <div className="flex flex-wrap items-center gap-2 border-b border-white/[0.06] pb-3">
          <Link2 className="h-4 w-4 text-sky-400" />
          <h4 className="text-sm font-semibold text-white">{t("链路管理", "Link manager")}</h4>
          <span className="text-[10px] text-zinc-500">
            {t("USB / Redroid :5555 / 无线调试（先配对再连接）", "USB · Redroid :5555 · wireless (pair then connect)")}
          </span>
        </div>
        <div className="mt-3 grid gap-3 lg:grid-cols-2">
          <div className="space-y-2 rounded-xl border border-white/[0.06] bg-black/35 p-3">
            <p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">adb connect</p>
            <div className="flex flex-wrap gap-2">
              <input
                className="min-w-[12rem] flex-1 rounded-lg border border-white/10 bg-black/50 px-3 py-2 font-mono text-xs text-zinc-200"
                placeholder="192.168.1.10:5555"
                value={connectAddr}
                onChange={(e) => setConnectAddr(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && void runConnect()}
              />
              <button
                type="button"
                disabled={connBusy || lock}
                onClick={() => void runConnect()}
                className="rounded-lg bg-sky-500/25 px-3 py-2 text-xs font-semibold text-sky-100 ring-1 ring-sky-400/40"
              >
                {connBusy ? "…" : "Connect"}
              </button>
              <button
                type="button"
                disabled={connBusy || lock || !connectAddr.trim()}
                onClick={() => saveBookmark(connectAddr)}
                className="rounded-lg border border-white/10 px-3 py-2 text-xs text-zinc-400 hover:text-zinc-200"
              >
                {t("存书签", "Pin")}
              </button>
            </div>
            {bookmarks.length ? (
              <div className="flex flex-wrap gap-1.5 pt-1">
                {bookmarks.map((b) => (
                  <button
                    key={b}
                    type="button"
                    className="rounded-md bg-white/[0.06] px-2 py-0.5 font-mono text-[10px] text-zinc-400 hover:bg-white/10"
                    onClick={() => setConnectAddr(b)}
                  >
                    {b}
                  </button>
                ))}
              </div>
            ) : null}
            <div className="flex flex-wrap gap-2 pt-2">
              <button
                type="button"
                disabled={connBusy || lock || !serial}
                onClick={() => void runDisconnect(serial)}
                className="rounded-lg border border-rose-500/30 bg-rose-500/10 px-2.5 py-1.5 text-[10px] text-rose-200"
              >
                {t("断开当前 TCP", "Disconnect current")}
              </button>
              <button
                type="button"
                disabled={connBusy || lock}
                onClick={() => void runDisconnect("")}
                className="rounded-lg border border-white/10 px-2.5 py-1.5 text-[10px] text-zinc-500 hover:text-zinc-300"
              >
                {t("断开全部 TCP", "Disconnect all TCP")}
              </button>
              <button
                type="button"
                disabled={connBusy || lock}
                onClick={() => {
                  onRefreshDevices();
                  bumpFrame();
                  notify(t("已刷新设备列表", "Devices refreshed"));
                }}
                className="inline-flex items-center gap-1 rounded-lg border border-white/10 px-2.5 py-1.5 text-[10px] text-zinc-400"
              >
                <RefreshCw className="h-3 w-3" />
                ADB list
              </button>
            </div>
          </div>
          <div className="space-y-2 rounded-xl border border-white/[0.06] bg-black/35 p-3">
            <p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">adb pair（无线调试）</p>
            <div className="flex flex-wrap gap-2">
              <input
                className="w-32 rounded-lg border border-white/10 bg-black/50 px-2 py-1.5 font-mono text-xs"
                placeholder="IP"
                value={pairHost}
                onChange={(e) => setPairHost(e.target.value)}
              />
              <input
                className="w-20 rounded-lg border border-white/10 bg-black/50 px-2 py-1.5 font-mono text-xs"
                placeholder="port"
                value={pairPort}
                onChange={(e) => setPairPort(e.target.value)}
              />
              <input
                className="min-w-[6rem] flex-1 rounded-lg border border-white/10 bg-black/50 px-2 py-1.5 font-mono text-xs tracking-widest"
                placeholder="123456"
                value={pairCode}
                onChange={(e) => setPairCode(e.target.value)}
              />
              <button
                type="button"
                disabled={connBusy || lock}
                onClick={() => void runPair()}
                className="rounded-lg bg-violet-500/20 px-3 py-1.5 text-xs text-violet-100 ring-1 ring-violet-400/35"
              >
                Pair
              </button>
            </div>
            <p className="text-[10px] leading-relaxed text-zinc-600">
              {t(
                "手机上「无线调试」会显示配对端口与 6 位码；配对成功后用上方 connect 连接 adb 显示的 IP:5555。",
                "Use the pairing port + 6-digit code from Wireless debugging; then connect to the listed adb IP:5555.",
              )}
            </p>
          </div>
        </div>
      </div>

      <div className="relative overflow-hidden rounded-3xl border border-emerald-500/25 bg-gradient-to-br from-emerald-500/[0.12] via-zinc-950/90 to-violet-500/[0.08] p-1 shadow-2xl shadow-emerald-900/20 ring-1 ring-white/[0.06]">
        <div className="pointer-events-none absolute -right-20 -top-20 h-56 w-56 rounded-full bg-emerald-500/10 blur-3xl" aria-hidden />
        <div className="relative rounded-[1.35rem] border border-white/[0.06] bg-zinc-950/80 p-4 backdrop-blur-sm">
          <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
            <div className="flex items-start gap-3">
              <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-emerald-400/30 to-violet-500/25 ring-1 ring-white/10">
                <Sparkles className="h-6 w-6 text-emerald-200" />
              </div>
              <div>
                <h3 className="text-base font-semibold tracking-tight text-white">
                  {t("无人直播系统 · ADB 云控台", "Unmanned Live System · ADB farm console")}
                </h3>
                <p className="mt-1 max-w-xl text-[11px] leading-relaxed text-zinc-500">
                  {t(
                    "统一适配 USB / Redroid / 无线 adb：已授权设备默认走官方 scrcpy-server 的 WebSocket H.264 真流（与桌面 ",
                    "USB / Redroid / wireless adb: authorized devices default to official scrcpy-server WebSocket H.264 (same stack as ",
                  )}
                  <a href={ESCRCPY_URL} target="_blank" rel="noreferrer" className="text-emerald-300/90 underline-offset-2 hover:underline">
                    escrcpy
                  </a>
                  {t(" / ", " / ")}
                  <a href={SCRCPY_URL} target="_blank" rel="noreferrer" className="text-emerald-300/90 underline-offset-2 hover:underline">
                    scrcpy
                  </a>
                  {t("）；无 jar 或关闭真流时用截图。", "); PNG screenshot when jar missing or stream off.")}
                </p>
              </div>
            </div>
            <div className="flex shrink-0 flex-col gap-1.5 self-start sm:flex-row">
              <a
                href={ESCRCPY_URL}
                target="_blank"
                rel="noreferrer"
                className="inline-flex items-center gap-1.5 rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-[11px] font-medium text-zinc-300 hover:bg-white/[0.07]"
              >
                escrcpy
                <ExternalLink className="h-3.5 w-3.5 opacity-70" />
              </a>
              <a
                href={SCRCPY_URL}
                target="_blank"
                rel="noreferrer"
                className="inline-flex items-center gap-1.5 rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-[11px] font-medium text-zinc-300 hover:bg-white/[0.07]"
              >
                scrcpy
                <ExternalLink className="h-3.5 w-3.5 opacity-70" />
              </a>
            </div>
          </div>

          {devices.length > 0 ? (
            <div className="mt-5">
              <p className="mb-2 flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
                <LayoutGrid className="h-3.5 w-3.5" />
                {t("设备矩阵", "Device matrix")}
              </p>
              <div className="flex gap-2 overflow-x-auto pb-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
                {devices.map((d) => {
                  const on = d.state === "device";
                  const active = serial === d.serial;
                  const badge = transportBadge(d.transport);
                  const BIcon = badge.Icon;
                  return (
                    <button
                      key={d.serial}
                      type="button"
                      onClick={() => setSerial(d.serial)}
                      className={`flex min-w-[9rem] flex-col rounded-xl border px-3 py-2 text-left transition ${
                        active
                          ? "border-emerald-500/50 bg-emerald-500/15 ring-1 ring-emerald-400/30"
                          : "border-white/10 bg-black/30 hover:border-white/20"
                      }`}
                    >
                      <span className="flex items-center gap-1.5 text-xs font-medium text-zinc-200">
                        <Smartphone className="h-3.5 w-3.5 text-emerald-400/80" />
                        <span className="truncate">{d.model || d.serial.slice(0, 10)}</span>
                      </span>
                      <span className="mt-0.5 truncate font-mono text-[9px] text-zinc-600">{d.serial}</span>
                      <div className="mt-1 flex flex-wrap gap-1">
                        <span
                          className={`inline-flex items-center gap-0.5 rounded-md px-1.5 py-0.5 text-[9px] font-semibold ring-1 ${badge.cls}`}
                        >
                          <BIcon className="h-2.5 w-2.5" />
                          {t(badge.zh, badge.en)}
                        </span>
                        <span
                          className={`rounded-md px-1.5 py-0.5 text-[9px] font-semibold ${
                            on ? "bg-emerald-500/20 text-emerald-200" : "bg-zinc-600/30 text-zinc-500"
                          }`}
                        >
                          {d.state}
                        </span>
                      </div>
                    </button>
                  );
                })}
              </div>
            </div>
          ) : null}

          <div className="mt-6 grid gap-6 xl:grid-cols-12">
            <div className="xl:col-span-8">
              <div className="flex flex-wrap items-center gap-2 border-b border-white/[0.06] pb-3">
                <span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
                  {t("刷新间隔", "Interval")}
                </span>
                {INTERVALS_MS.map((ms) => (
                  <button
                    key={ms}
                    type="button"
                    onClick={() => setLiveMs(ms)}
                    className={`rounded-lg px-2.5 py-1 text-[10px] font-semibold transition ${
                      liveMs === ms
                        ? "bg-violet-500/25 text-violet-100 ring-1 ring-violet-400/35"
                        : "bg-black/30 text-zinc-500 hover:text-zinc-300"
                    }`}
                  >
                    {ms === 0 ? t("手动", "Manual") : `${ms}ms`}
                  </button>
                ))}
                <span
                  className={`ml-1 inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[9px] font-medium ring-1 ${tb.cls}`}
                >
                  <HintIcon className="h-3 w-3" />
                  {activeTransport === "tcp"
                    ? t("建议 ≥450ms", "Suggest ≥450ms")
                    : t("可试 280ms", "Try 280ms")}
                </span>
                <button
                  type="button"
                  disabled={lock}
                  onClick={() => void bumpFrame()}
                  className="ml-auto inline-flex items-center gap-1 rounded-lg border border-white/10 bg-white/[0.05] px-2.5 py-1 text-[10px] text-zinc-300 hover:bg-white/[0.08]"
                >
                  <RefreshCw className={`h-3 w-3 ${imgBusy ? "animate-spin" : ""}`} />
                  {t("下一帧", "Next frame")}
                </button>
                <button
                  type="button"
                  disabled={lock || !serial}
                  onClick={() => void onAndroidAction("wake")}
                  className="rounded-lg bg-amber-500/20 px-2.5 py-1 text-[10px] font-semibold text-amber-100 ring-1 ring-amber-500/30"
                >
                  Wake
                </button>
                <label className="flex cursor-pointer items-center gap-1.5 text-[10px] text-zinc-400">
                  <input
                    type="checkbox"
                    className="rounded border-white/20 bg-black/50"
                    checked={tapMode}
                    onChange={(e) => setTapMode(e.target.checked)}
                  />
                  <MousePointer2 className="h-3 w-3" />
                  {t("点按穿透", "Tap-through")}
                </label>
                <button
                  type="button"
                  disabled={lock || !serial || !scrcpyInfo?.server_jar}
                  title={
                    scrcpyInfo && !scrcpyInfo.server_jar && scrcpyInfo.hint_zh
                      ? scrcpyInfo.hint_zh
                      : t("官方 scrcpy-server + WebSocket H.264", "Official scrcpy-server + WebSocket H.264")
                  }
                  onClick={() => {
                    setScrcpyStream((v) => {
                      const next = !v;
                      if (next) {
                        setScrcpyUserStoppedForSerial(null);
                        notify(t("已开启实时画面（H.264 WebSocket）", "Live H.264 WebSocket stream on"));
                      } else {
                        if (serial) setScrcpyUserStoppedForSerial(serial);
                        notify(t("已关闭实时画面（将使用截图）", "Live stream off — screenshot mode"));
                      }
                      return next;
                    });
                  }}
                  className={`inline-flex items-center gap-1 rounded-lg border px-2.5 py-1 text-[10px] font-semibold transition ${
                    scrcpyStream
                      ? "border-cyan-400/50 bg-cyan-500/20 text-cyan-100 ring-1 ring-cyan-400/35"
                      : "border-white/10 bg-black/30 text-zinc-400 hover:text-zinc-200"
                  }`}
                >
                  <Radio className="h-3 w-3" />
                  {scrcpyStream ? t("关闭真流", "Stop WS") : t("实时画面", "Live H.264")}
                </button>
              </div>
              {scrcpyInfo && !scrcpyInfo.server_jar ? (
                <p className="mt-2 text-[10px] text-amber-200/80">
                  {t(
                    "未检测到 scrcpy-server：启动时会自动拉取；内网可在 runtime.env 配置 SCRCPY_SERVER_DOWNLOAD_URL（支持 {tag}）。",
                    "scrcpy-server missing: auto-fetched on startup; set SCRCPY_SERVER_DOWNLOAD_URL ({tag}) for mirrors.",
                  )}
                  {scrcpyInfo.server_version ? ` (${t("期望版本", "version")} ${scrcpyInfo.server_version})` : null}
                </p>
              ) : null}
              <p className="mt-2 text-[10px] text-zinc-600">
                {t(
                  "快捷键（镜像区聚焦时）：H Home · B Back · R 多任务 · W 唤醒 · M 菜单",
                    "Hotkeys (mirror focused): H Home · B Back · R Recents · W Wake · M Menu",
                )}
              </p>

              <div className="relative mx-auto mt-4 max-h-[min(78vh,720px)] min-h-[280px]">
                {!serial ? (
                  <p className="py-16 text-center text-sm text-zinc-600">{t("请选择或连接设备", "Pick or connect a device")}</p>
                ) : (
                  <div
                    ref={mirrorFocusRef}
                    tabIndex={0}
                    onKeyDown={onMirrorKeyDown}
                    className="relative flex justify-center outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/50"
                  >
                    <div
                      className="relative rounded-[2rem] border-[10px] border-zinc-800 bg-black shadow-inner shadow-black/80 ring-1 ring-white/10"
                      style={{ maxHeight: "min(72vh, 680px)" }}
                    >
                      <div className="absolute left-1/2 top-0 z-10 h-5 w-24 -translate-x-1/2 rounded-b-xl bg-black/80" />
                      {scrcpyStream ? (
                        <canvas
                          ref={canvasRef}
                          className={`max-h-[min(72vh,680px)] w-auto max-w-full rounded-[1.35rem] object-contain ${
                            tapMode ? "cursor-crosshair" : "cursor-default"
                          }`}
                          draggable={false}
                          onClick={onCanvasClick}
                          onContextMenu={onCanvasContextMenu}
                        />
                      ) : (
                        /* eslint-disable-next-line @next/next/no-img-element */
                        <img
                          ref={imgRef}
                          src={shotUrl || undefined}
                          alt="device"
                          className={`max-h-[min(72vh,680px)] w-auto max-w-full rounded-[1.35rem] object-contain ${
                            tapMode ? "cursor-crosshair" : "cursor-default"
                          }`}
                          draggable={false}
                          onLoadStart={() => {
                            setImgBusy(true);
                          }}
                          onLoad={onImgLoad}
                          onError={onImgError}
                          onClick={onImageClick}
                          onContextMenu={onImageContextMenu}
                        />
                      )}
                    </div>
                    {scrcpyConnected ? (
                      <div className="pointer-events-none absolute bottom-3 right-3 flex items-center gap-1 rounded-full bg-cyan-500/90 px-2 py-0.5 text-[9px] font-bold text-cyan-950 shadow-lg">
                        <Radio className="h-3 w-3" />
                        SCRCPY·WS
                      </div>
                    ) : liveMs > 0 && !scrcpyStream ? (
                      <div className="pointer-events-none absolute bottom-3 right-3 flex items-center gap-1 rounded-full bg-emerald-500/90 px-2 py-0.5 text-[9px] font-bold text-emerald-950 shadow-lg">
                        <Play className="h-3 w-3" />
                        LIVE
                      </div>
                    ) : !scrcpyStream ? (
                      <div className="pointer-events-none absolute bottom-3 right-3 flex items-center gap-1 rounded-full bg-zinc-700/90 px-2 py-0.5 text-[9px] font-bold text-zinc-200">
                        <Pause className="h-3 w-3" />
                        {t("单帧", "Still")}
                      </div>
                    ) : null}
                  </div>
                )}
                {scrcpyErr ? <p className="mt-3 text-center text-xs text-rose-300">{scrcpyErr}</p> : null}
                {imgErr && !scrcpyStream ? <p className="mt-3 text-center text-xs text-rose-300">{imgErr}</p> : null}
                <p className="mt-3 text-center text-[10px] text-zinc-600">
                  {t("左键单击 / 双击 · 右键长按 · 坐标对齐 PNG 像素", "Left tap/double · right long-press · PNG pixel coords")}
                </p>
              </div>
            </div>

            <div className="space-y-4 xl:col-span-4">
              <div className="rounded-2xl border border-white/[0.07] bg-black/40 p-4">
                <p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
                  {t("遥测", "Telemetry")}
                </p>
                {overview ? (
                  <dl className="mt-3 space-y-2 font-mono text-[11px] text-zinc-400">
                    <div className="flex justify-between gap-2">
                      <dt className="text-zinc-600">device</dt>
                      <dd className="text-right text-zinc-200">
                        {overview.brand} {overview.model}
                      </dd>
                    </div>
                    <div className="flex justify-between gap-2">
                      <dt className="text-zinc-600">Android</dt>
                      <dd className="text-zinc-300">
                        {overview.android_version} (API {overview.sdk})
                      </dd>
                    </div>
                    <div className="flex justify-between gap-2">
                      <dt className="text-zinc-600">wm size</dt>
                      <dd className="text-cyan-300/90">
                        {metrics ? `${metrics.width}×${metrics.height}` : overview.resolution || "—"}
                      </dd>
                    </div>
                    <div className="whitespace-pre-wrap text-[10px] leading-snug text-zinc-500">{overview.battery}</div>
                  </dl>
                ) : (
                  <p className="mt-2 text-xs text-zinc-600">{t("在上方点「刷新设备信息」", "Use Refresh device info above")}</p>
                )}
              </div>

              <div className="rounded-2xl border border-violet-500/20 bg-gradient-to-b from-violet-500/[0.08] to-black/40 p-4">
                <p className="text-[10px] font-semibold uppercase tracking-wider text-violet-300/80">
                  {t("导航坞", "Nav dock")}
                </p>
                <div className="mt-3 grid grid-cols-3 gap-2">
                  {(
                    [
                      ["3", "Home"],
                      ["4", "Back"],
                      ["187", "Recents"],
                    ] as const
                  ).map(([code, label]) => (
                    <button
                      key={code}
                      type="button"
                      disabled={lock || !serial}
                      onClick={() => {
                        void onAndroidAction("key", { keycode: code });
                        bumpFrame();
                      }}
                      className="rounded-xl border border-white/10 bg-white/[0.06] py-3 text-xs font-semibold text-zinc-200 hover:bg-white/[0.1]"
                    >
                      {label}
                    </button>
                  ))}
                </div>
                <div className="mt-2 grid grid-cols-2 gap-2">
                  <button
                    type="button"
                    disabled={lock || !serial}
                    onClick={() => {
                      void onAndroidAction("key", { keycode: "24" });
                      bumpFrame();
                    }}
                    className="rounded-xl border border-white/10 bg-black/30 py-2 text-[11px] text-zinc-300"
                  >
                    Vol+
                  </button>
                  <button
                    type="button"
                    disabled={lock || !serial}
                    onClick={() => {
                      void onAndroidAction("key", { keycode: "25" });
                      bumpFrame();
                    }}
                    className="rounded-xl border border-white/10 bg-black/30 py-2 text-[11px] text-zinc-300"
                  >
                    Vol−
                  </button>
                </div>
              </div>

              <div className="rounded-2xl border border-cyan-500/15 bg-cyan-500/[0.05] p-4">
                <p className="text-[10px] font-semibold uppercase tracking-wider text-cyan-300/80">
                  {t("手势", "Gestures")}
                </p>
                <div className="mt-2 grid grid-cols-2 gap-2">
                  {(["up", "down", "left", "right"] as const).map((dir) => (
                    <button
                      key={dir}
                      type="button"
                      disabled={lock || !serial}
                      onClick={() => {
                        void onAndroidAction("swipe", { direction: dir });
                        bumpFrame();
                      }}
                      className="rounded-lg border border-white/10 bg-black/30 py-2 font-mono text-[10px] uppercase text-zinc-300"
                    >
                      {dir}
                    </button>
                  ))}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
