"use client";

import { Copy, Layers, Loader2, Package, Terminal } from "lucide-react";
import { useCallback, useEffect, useState } from "react";

import { LiveAndroidStreamConsole } from "@/components/live-product/LiveAndroidStreamConsole";

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

type AndroidOverview = {
  serial?: string;
  model?: string;
  brand?: string;
  android_version?: string;
  sdk?: string;
  battery?: string;
  focus?: string;
  resolution?: string;
  rotation?: string;
  screen_state?: string;
};

type PkgRow = { package: string; label?: string };
type UiNode = {
  label: string;
  text?: string;
  content_desc?: string;
  resource_id?: string;
  center_x: number;
  center_y: number;
  clickable?: boolean;
  enabled?: boolean;
};

const SHELL_SNIPPETS: { label: string; cmd: string }[] = [
  { label: "getprop", cmd: "getprop ro.build.version.release" },
  { label: "top pkg", cmd: "dumpsys activity activities | grep -E 'mResumedActivity' | tail -n 1" },
  { label: "sdcard", cmd: "ls -la /sdcard | head -n 24" },
  { label: "net", cmd: "dumpsys connectivity | head -n 40" },
];

type Props = {
  t: TFn;
  notify: (msg: string) => void;
  fetchJson: <T,>(path: string, init?: RequestInit) => Promise<T>;
  apiBase: string;
  /** Parent busy (e.g. adb actions); combined with local pending for UI lock. */
  busy: string | null;
  androidSerial: string;
  setAndroidSerial: (s: string) => void;
  devices: DeviceRow[];
  adbAvailable: boolean;
  apkPath: string;
  setApkPath: (v: string) => void;
  onAndroidAction: (action: string, payload?: Record<string, unknown>) => Promise<void>;
  onCopy: (text: string) => void;
  onRefreshDevices: () => void;
};

export function LiveAndroidWorkstation({
  t,
  notify,
  fetchJson,
  apiBase,
  busy,
  androidSerial,
  setAndroidSerial,
  devices,
  adbAvailable,
  apkPath,
  setApkPath,
  onAndroidAction,
  onCopy,
  onRefreshDevices,
}: Props) {
  const [overview, setOverview] = useState<AndroidOverview | null>(null);
  const [overviewErr, setOverviewErr] = useState<string | null>(null);
  const [pkgQuery, setPkgQuery] = useState("");
  const [pkgList, setPkgList] = useState<PkgRow[]>([]);
  const [shellCmd, setShellCmd] = useState("getprop ro.product.model");
  const [shellOut, setShellOut] = useState("");
  const [logcatLines, setLogcatLines] = useState("300");
  const [logcatText, setLogcatText] = useState("");
  const [logcatErr, setLogcatErr] = useState<string | null>(null);
  const [uiNodes, setUiNodes] = useState<UiNode[]>([]);
  const [uiErr, setUiErr] = useState<string | null>(null);
  const [openUrl, setOpenUrl] = useState("https://");
  const [adbInputText, setAdbInputText] = useState("");
  const [logcatLive, setLogcatLive] = useState(false);
  const [pending, setPending] = useState<string | null>(null);

  const lock = !!(busy || pending);

  const loadOverview = useCallback(async () => {
    if (!androidSerial || !adbAvailable) {
      setOverview(null);
      setOverviewErr(null);
      return;
    }
    setPending("overview");
    setOverviewErr(null);
    try {
      const o = await fetchJson<AndroidOverview>(
        `/android/overview?serial=${encodeURIComponent(androidSerial)}`,
      );
      setOverview(o);
    } catch (e) {
      setOverview(null);
      setOverviewErr((e as Error).message);
    } finally {
      setPending(null);
    }
  }, [adbAvailable, androidSerial, fetchJson]);

  useEffect(() => {
    void loadOverview();
  }, [loadOverview]);

  const searchPackages = async () => {
    if (!androidSerial) {
      notify(t("请选择设备", "Pick a device"));
      return;
    }
    setPending("packages");
    try {
      const q = encodeURIComponent(pkgQuery.trim());
      const data = await fetchJson<{ packages: PkgRow[] }>(
        `/android/packages?serial=${encodeURIComponent(androidSerial)}&query=${q}&limit=80`,
      );
      setPkgList(data.packages || []);
    } catch (e) {
      notify((e as Error).message);
      setPkgList([]);
    } finally {
      setPending(null);
    }
  };

  const runShell = async () => {
    if (!androidSerial) {
      notify(t("请选择设备", "Pick a device"));
      return;
    }
    const cmd = shellCmd.trim();
    if (!cmd) return;
    setPending("shell");
    try {
      const data = await fetchJson<{ output?: string; message?: string }>("/android/action", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "shell", serial: androidSerial, payload: { command: cmd } }),
      });
      setShellOut(data.output ?? data.message ?? "");
    } catch (e) {
      setShellOut((e as Error).message);
    } finally {
      setPending(null);
    }
  };

  const refreshLogcat = useCallback(
    async (opts?: { silent?: boolean }) => {
      if (!androidSerial) {
        return;
      }
      const n = Math.min(3000, Math.max(1, parseInt(logcatLines, 10) || 200));
      const silent = !!opts?.silent;
      if (!silent) setPending("logcat");
      setLogcatErr(null);
      try {
        const data = await fetchJson<{ text?: string }>(
          `/android/logcat?serial=${encodeURIComponent(androidSerial)}&lines=${n}`,
        );
        setLogcatText(data.text ?? "");
      } catch (e) {
        setLogcatText("");
        setLogcatErr((e as Error).message);
      } finally {
        if (!silent) setPending(null);
      }
    },
    [androidSerial, fetchJson, logcatLines],
  );

  useEffect(() => {
    if (!logcatLive || !androidSerial || !adbAvailable) return;
    const id = window.setInterval(() => void refreshLogcat({ silent: true }), 3000);
    void refreshLogcat({ silent: true });
    return () => clearInterval(id);
  }, [logcatLive, androidSerial, adbAvailable, refreshLogcat]);

  const loadUiNodes = async () => {
    if (!androidSerial) {
      notify(t("请选择设备", "Pick a device"));
      return;
    }
    setPending("ui");
    setUiErr(null);
    try {
      const data = await fetchJson<{ nodes: UiNode[] }>(
        `/android/ui?serial=${encodeURIComponent(androidSerial)}&limit=100`,
      );
      setUiNodes(data.nodes || []);
    } catch (e) {
      setUiNodes([]);
      setUiErr((e as Error).message);
    } finally {
      setPending(null);
    }
  };

  const tapNode = async (x: number, y: number) => {
    await onAndroidAction("tap", { x, y });
  };

  const sendKey = async (keycode: string) => {
    await onAndroidAction("key", { keycode });
  };

  const openUrlGo = async () => {
    const u = openUrl.trim();
    if (!u || u === "https://") {
      notify(t("填写 URL", "Enter URL"));
      return;
    }
    await onAndroidAction("open_url", { url: u });
  };

  const sendAdbText = async () => {
    const raw = adbInputText.trim();
    if (!raw) {
      notify(t("填写要输入的文字", "Enter text"));
      return;
    }
    await onAndroidAction("text", { text: raw });
  };

  return (
    <div className="mx-auto max-w-5xl space-y-6">
      <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
        <div className="flex flex-wrap items-center justify-between gap-3">
          <h3 className="text-sm font-semibold text-white">{t("设备与工作台", "Device workstation")}</h3>
          {!adbAvailable ? (
            <span className="text-xs text-amber-200/90">{t("宿主机未检测到 adb", "adb not found on host")}</span>
          ) : (
            <button
              type="button"
              disabled={lock || !androidSerial}
              onClick={() => void loadOverview()}
              className="rounded-lg border border-white/10 bg-white/[0.05] px-3 py-1.5 text-xs text-zinc-300 hover:bg-white/[0.08]"
            >
              {t("刷新设备信息", "Refresh device info")}
            </button>
          )}
        </div>
        <div className="mt-4 flex flex-wrap gap-2">
          {devices.map((d) => (
            <button
              key={d.serial}
              type="button"
              onClick={() => setAndroidSerial(d.serial)}
              className={`rounded-xl border px-4 py-2 text-left text-xs ${
                androidSerial === d.serial
                  ? "border-violet-500/50 bg-violet-500/10 text-white"
                  : "border-white/10 bg-black/30 text-zinc-400"
              }`}
            >
              <div className="flex items-center gap-2">
                <span className="font-medium text-zinc-200">{d.model || d.serial}</span>
                {d.transport ? (
                  <span className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-[9px] uppercase text-zinc-500">
                    {d.transport}
                  </span>
                ) : null}
              </div>
              <div className="text-[10px] text-zinc-600">{d.serial}</div>
              {(d.state || "").toLowerCase() !== "device" ? (
                <div className="mt-1 text-[10px] font-medium text-amber-200/90">
                  {d.state === "unauthorized"
                    ? t("未授权：请在手机上点「允许 USB 调试」", "Unauthorized — allow USB debugging on device")
                    : t(`状态: ${d.state}`, `State: ${d.state}`)}
                </div>
              ) : null}
            </button>
          ))}
        </div>
        {adbAvailable && devices.length === 0 ? (
          <p className="mt-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3 text-xs leading-relaxed text-amber-200/90">
            {t(
              "未检测到已授权设备：双头 USB 接板子时请开「USB 调试」并授权；Redroid / 容器安卓可先 `adb connect IP:5555` 再刷新本页。",
              "No device: enable USB debugging for dual-USB to the board; for Redroid use adb connect IP:5555 then refresh.",
            )}
          </p>
        ) : null}
      </div>

      <LiveAndroidStreamConsole
        t={t}
        apiBase={apiBase}
        fetchJson={fetchJson}
        serial={androidSerial}
        devices={devices}
        setSerial={setAndroidSerial}
        adbAvailable={adbAvailable}
        lock={lock}
        overview={overview}
        onAndroidAction={onAndroidAction}
        notify={notify}
        onRefreshDevices={onRefreshDevices}
      />

      {adbAvailable && androidSerial ? (
        <div className="grid gap-4 lg:grid-cols-2">
          <div className="rounded-2xl border border-violet-500/15 bg-zinc-950/50 p-5">
            <h4 className="text-xs font-semibold uppercase tracking-wider text-violet-300/90">
              {t("设备信息", "Device info")}
            </h4>
            {overviewErr ? <p className="mt-2 text-xs text-rose-300">{overviewErr}</p> : null}
            {overview ? (
              <dl className="mt-3 space-y-1.5 font-mono text-[11px] text-zinc-400">
                <div>
                  <dt className="inline text-zinc-600">model</dt>{" "}
                  <dd className="inline text-zinc-300">
                    {overview.brand} {overview.model}
                  </dd>
                </div>
                <div>
                  <dt className="inline text-zinc-600">Android</dt>{" "}
                  <dd className="inline text-zinc-300">
                    {overview.android_version} (sdk {overview.sdk})
                  </dd>
                </div>
                <div>
                  <dt className="inline text-zinc-600">display</dt>{" "}
                  <dd className="inline text-zinc-300">{overview.resolution}</dd>
                </div>
                <div>
                  <dt className="inline text-zinc-600">rotation</dt>{" "}
                  <dd className="inline text-zinc-300">{overview.rotation}</dd>
                </div>
                <div className="whitespace-pre-wrap text-[10px] leading-snug text-zinc-500">
                  {overview.battery}
                </div>
                <div className="line-clamp-3 text-[10px] text-zinc-500">{overview.focus}</div>
              </dl>
            ) : !overviewErr ? (
              <p className="mt-2 text-xs text-zinc-500">{t("加载中…", "Loading…")}</p>
            ) : null}
          </div>

          <div className="rounded-2xl border border-cyan-500/15 bg-zinc-950/50 p-5">
            <h4 className="text-xs font-semibold uppercase tracking-wider text-cyan-300/90">
              {t("快捷控制", "Quick controls")}
            </h4>
            <p className="mt-2 text-[11px] text-zinc-500">
              {t("按键与常用应用；深层调试用下方 Shell / Logcat。", "Keys & apps; use Shell / Logcat for deep debug.")}
            </p>
            <div className="mt-3 flex flex-wrap gap-2">
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("3")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Home
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("4")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Back
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("187")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Recents
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void onAndroidAction("wake")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Wake
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void onAndroidAction("launch_package", { package: "com.zhiliaoapp.musically" })}
                className="rounded-lg bg-gradient-to-r from-pink-500/25 to-violet-500/25 px-3 py-1.5 text-xs ring-1 ring-white/10"
              >
                TikTok
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("24")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Vol+
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("25")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Vol−
              </button>
              <button
                type="button"
                disabled={lock}
                onClick={() => void sendKey("66")}
                className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs"
              >
                Enter
              </button>
            </div>
            <p className="mt-3 text-[10px] text-zinc-600">{t("滑动手势（中心区域）", "Swipe (center)")}</p>
            <div className="mt-1 flex flex-wrap gap-2">
              {(["up", "down", "left", "right"] as const).map((dir) => (
                <button
                  key={dir}
                  type="button"
                  disabled={lock}
                  onClick={() => void onAndroidAction("swipe", { direction: dir })}
                  className="rounded-lg border border-white/10 bg-black/30 px-2.5 py-1 font-mono text-[10px] uppercase text-zinc-300"
                >
                  {dir}
                </button>
              ))}
            </div>
            <div className="mt-4 rounded-lg border border-white/5 bg-black/20 p-3">
              <p className="text-[10px] font-semibold uppercase tracking-wider text-zinc-500">
                {t("输入文字（adb input text）", "Type text (adb)")}
              </p>
              <p className="mt-1 text-[10px] text-zinc-600">
                {t("仅适合英文与数字；中文需剪贴板或无障碍方案。", "ASCII-focused; CJK needs clipboard.")}
              </p>
              <div className="mt-2 flex flex-col gap-2 sm:flex-row">
                <input
                  className="min-w-0 flex-1 rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-xs"
                  value={adbInputText}
                  onChange={(e) => setAdbInputText(e.target.value)}
                  placeholder="hello / 123"
                  onKeyDown={(e) => {
                    if (e.key === "Enter") void sendAdbText();
                  }}
                />
                <button
                  type="button"
                  disabled={lock}
                  onClick={() => void sendAdbText()}
                  className="rounded-lg bg-emerald-500/20 px-3 py-2 text-xs text-emerald-100 ring-1 ring-emerald-500/30"
                >
                  {t("发送", "Send")}
                </button>
              </div>
            </div>
            <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center">
              <input
                className="min-w-0 flex-1 rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-xs"
                value={openUrl}
                onChange={(e) => setOpenUrl(e.target.value)}
                placeholder="https://"
              />
              <button
                type="button"
                disabled={lock}
                onClick={() => void openUrlGo()}
                className="rounded-lg bg-violet-500/20 px-3 py-2 text-xs text-violet-100 ring-1 ring-violet-500/30"
              >
                {t("打开链接", "Open URL")}
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {adbAvailable && androidSerial ? (
        <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
          <h3 className="flex items-center gap-2 text-sm font-semibold text-white">
            <Package className="h-4 w-4 text-emerald-400" />
            {t("包名检索与启动", "Packages")}
          </h3>
          <p className="mt-1 text-xs text-zinc-500">
            {t("过滤 pm list packages；行内可启动或强停。", "Filter pm list packages; launch or force-stop.")}
          </p>
          <div className="mt-3 flex flex-col gap-2 sm:flex-row">
            <input
              className="min-w-0 flex-1 rounded-xl border border-white/10 bg-black/40 px-4 py-2 text-sm"
              value={pkgQuery}
              onChange={(e) => setPkgQuery(e.target.value)}
              placeholder="tiktok / musically / …"
              onKeyDown={(e) => {
                if (e.key === "Enter") void searchPackages();
              }}
            />
            <button
              type="button"
              disabled={lock}
              onClick={() => void searchPackages()}
              className="rounded-xl bg-emerald-500/20 px-4 py-2 text-sm text-emerald-100 ring-1 ring-emerald-500/30"
            >
              {t("搜索", "Search")}
            </button>
          </div>
          <ul className="mt-4 max-h-48 space-y-1 overflow-y-auto rounded-lg border border-white/5 bg-black/30 p-2 font-mono text-[11px]">
            {pkgList.map((p) => (
              <li key={p.package} className="flex flex-wrap items-center justify-between gap-2 py-1 text-zinc-400">
                <span className="min-w-0 truncate text-zinc-300">{p.package}</span>
                <span className="flex shrink-0 gap-1">
                  <button
                    type="button"
                    disabled={lock}
                    onClick={() => void onAndroidAction("launch_package", { package: p.package })}
                    className="rounded bg-white/10 px-2 py-0.5 text-[10px] text-zinc-200"
                  >
                    {t("启动", "Launch")}
                  </button>
                  <button
                    type="button"
                    disabled={lock}
                    onClick={() => void onAndroidAction("force_stop", { package: p.package })}
                    className="rounded bg-rose-500/15 px-2 py-0.5 text-[10px] text-rose-200"
                  >
                    {t("强停", "Stop")}
                  </button>
                </span>
              </li>
            ))}
            {!pkgList.length ? (
              <li className="py-2 text-center text-zinc-600">{t("暂无结果，先搜索", "No results")}</li>
            ) : null}
          </ul>
        </div>
      ) : null}

      {adbAvailable && androidSerial ? (
        <div className="grid gap-6 xl:grid-cols-2">
          <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
            <h3 className="flex items-center gap-2 text-sm font-semibold text-white">
              <Terminal className="h-4 w-4 text-amber-400" />
              {t("交互式 Shell", "ADB shell")}
            </h3>
            <p className="mt-1 text-xs text-zinc-500">
              {t("Ctrl+Enter 执行；勿执行不可逆命令。", "Ctrl+Enter to run.")}
            </p>
            <div className="mt-2 flex flex-wrap gap-1.5">
              {SHELL_SNIPPETS.map((s) => (
                <button
                  key={s.label}
                  type="button"
                  disabled={lock}
                  onClick={() => setShellCmd(s.cmd)}
                  className="rounded-md border border-white/10 bg-white/[0.04] px-2 py-1 text-[10px] text-zinc-400 hover:text-zinc-200"
                >
                  {s.label}
                </button>
              ))}
            </div>
            <textarea
              className="mt-3 min-h-[100px] w-full resize-y rounded-xl border border-white/10 bg-black/50 p-3 font-mono text-xs text-zinc-200"
              value={shellCmd}
              onChange={(e) => setShellCmd(e.target.value)}
              spellCheck={false}
              onKeyDown={(e) => {
                if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
                  e.preventDefault();
                  void runShell();
                }
              }}
            />
            <div className="mt-2 flex flex-wrap items-center gap-2">
              <button
                type="button"
                disabled={lock}
                onClick={() => void runShell()}
                className="rounded-xl bg-amber-500/20 px-4 py-2 text-sm text-amber-100 ring-1 ring-amber-500/30"
              >
                {pending === "shell" ? (
                  <span className="inline-flex items-center gap-2">
                    <Loader2 className="h-3.5 w-3.5 animate-spin" />
                    Run
                  </span>
                ) : (
                  "Run"
                )}
              </button>
              {shellOut ? (
                <button
                  type="button"
                  disabled={!shellOut}
                  onClick={() => onCopy(shellOut)}
                  className="inline-flex items-center gap-1 rounded-lg border border-white/10 px-3 py-1.5 text-xs text-zinc-400 hover:text-zinc-200"
                >
                  <Copy className="h-3.5 w-3.5" />
                  {t("复制输出", "Copy")}
                </button>
              ) : null}
            </div>
            {shellOut ? (
              <pre className="mt-3 max-h-64 overflow-auto whitespace-pre-wrap rounded-lg border border-white/5 bg-black/40 p-3 font-mono text-[11px] text-zinc-400 xl:max-h-[min(24rem,50vh)]">
                {shellOut}
              </pre>
            ) : null}
          </div>

          <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
            <h3 className="text-sm font-semibold text-white">{t("Logcat（最近行）", "Logcat (recent)")}</h3>
            <p className="mt-1 text-xs text-zinc-500">
              {t("logcat -d -t N；大日志可能略慢。", "logcat -d -t N.")}
            </p>
            <div className="mt-3 flex flex-wrap items-center gap-2">
              <input
                className="w-24 rounded-lg border border-white/10 bg-black/40 px-3 py-2 font-mono text-xs"
                value={logcatLines}
                onChange={(e) => setLogcatLines(e.target.value)}
              />
              <label className="flex cursor-pointer items-center gap-2 text-[11px] text-zinc-400">
                <input
                  type="checkbox"
                  className="rounded border-white/20 bg-black/40"
                  checked={logcatLive}
                  onChange={(e) => setLogcatLive(e.target.checked)}
                />
                {t("每 3 秒自动刷新", "Auto every 3s")}
              </label>
              <button
                type="button"
                disabled={lock}
                onClick={() => {
                  if (!androidSerial) {
                    notify(t("请选择设备", "Pick a device"));
                    return;
                  }
                  void refreshLogcat();
                }}
                className="rounded-xl bg-cyan-500/20 px-4 py-2 text-sm text-cyan-100 ring-1 ring-cyan-500/30"
              >
                {t("拉取日志", "Pull logs")}
              </button>
              {logcatText ? (
                <button
                  type="button"
                  onClick={() => onCopy(logcatText)}
                  className="inline-flex items-center gap-1 rounded-lg border border-white/10 px-3 py-1.5 text-xs text-zinc-400 hover:text-zinc-200"
                >
                  <Copy className="h-3.5 w-3.5" />
                  {t("复制", "Copy")}
                </button>
              ) : null}
            </div>
            {logcatErr ? <p className="mt-2 text-xs text-rose-300">{logcatErr}</p> : null}
            {logcatText ? (
              <pre className="mt-3 max-h-72 overflow-auto whitespace-pre-wrap rounded-lg border border-white/5 bg-black/50 p-3 font-mono text-[10px] leading-tight text-zinc-500 xl:max-h-[min(24rem,50vh)]">
                {logcatText}
              </pre>
            ) : null}
          </div>
        </div>
      ) : null}

      {adbAvailable && androidSerial ? (
        <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
          <h3 className="flex items-center gap-2 text-sm font-semibold text-white">
            <Layers className="h-4 w-4 text-fuchsia-400" />
            {t("界面节点（uiautomator dump）", "UI nodes")}
          </h3>
          <p className="mt-1 text-xs text-zinc-500">
            {t("列出可交互节点，点击坐标模拟触控。", "Clickable nodes from UI dump; tap by coordinates.")}
          </p>
          <button
            type="button"
            disabled={lock}
            onClick={() => void loadUiNodes()}
            className="mt-3 rounded-xl border border-fuchsia-500/30 bg-fuchsia-500/10 px-4 py-2 text-sm text-fuchsia-100"
          >
            {t("刷新节点树", "Refresh nodes")}
          </button>
          {uiErr ? <p className="mt-2 text-xs text-rose-300">{uiErr}</p> : null}
          <ul className="mt-3 max-h-64 space-y-1 overflow-y-auto rounded-lg border border-white/5 bg-black/30 p-2 text-[11px]">
            {uiNodes.map((n, i) => (
              <li
                key={`${n.center_x}-${n.center_y}-${i}`}
                className="flex flex-wrap items-center justify-between gap-2 border-b border-white/[0.04] py-1.5 text-zinc-400 last:border-0"
              >
                <div className="min-w-0 flex-1">
                  <span className="text-zinc-200">{n.label}</span>
                  {n.resource_id ? (
                    <span className="ml-2 font-mono text-[10px] text-zinc-600">{n.resource_id}</span>
                  ) : null}
                  <div className="font-mono text-[10px] text-zinc-600">
                    ({n.center_x}, {n.center_y}){n.clickable ? " · click" : ""}
                  </div>
                </div>
                <button
                  type="button"
                  disabled={lock}
                  onClick={() => void tapNode(n.center_x, n.center_y)}
                  className="shrink-0 rounded bg-white/10 px-2 py-1 text-[10px] text-zinc-200"
                >
                  Tap
                </button>
              </li>
            ))}
            {!uiNodes.length && !uiErr ? (
              <li className="py-4 text-center text-zinc-600">{t("尚未加载", "Not loaded")}</li>
            ) : null}
          </ul>
        </div>
      ) : null}

      {adbAvailable && androidSerial ? (
        <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
          <h3 className="text-sm font-semibold text-white">{t("安装 APK（设备上的路径）", "Install APK (path on device)")}</h3>
          <p className="mt-1 text-xs text-zinc-500">
            {t("请先用 adb push 将 apk 推到 /sdcard/ 等路径。", "Push APK to /sdcard/ via adb first.")}
          </p>
          <input
            className="mt-4 w-full rounded-xl border border-white/10 bg-black/40 px-4 py-3 text-sm"
            value={apkPath}
            onChange={(e) => setApkPath(e.target.value)}
          />
          <button
            type="button"
            disabled={lock || !adbAvailable || !androidSerial}
            onClick={() => void onAndroidAction("install_apk", { path: apkPath })}
            className="mt-3 rounded-xl bg-emerald-500/20 px-4 py-2 text-sm font-medium text-emerald-100 ring-1 ring-emerald-500/30"
          >
            pm install
          </button>
        </div>
      ) : null}
    </div>
  );
}
