const STORAGE_KEYS = {
  compassJump: "compassJumpEnabled",
  douyinDownload: "douyinDetailDownloadEnabled",
  agentControl: "mmwAgentBrowserControlEnabled"
};

const AGENT_CONTROL_PROMPT = [
  "你现在可以通过 MMWGZT 插件控制当前浏览器。先确认插件弹窗里已经打开“AI 接管”，并确认本地会话桥已启动；当前版本的 click/type/scroll/key/move 走浏览器级输入注入，不再只是页面内 DOM 事件。",
  "如果你能直接发起本机 HTTP 请求，优先使用本地会话桥：http://127.0.0.1:18766/v1/agent-browser 。启动脚本默认在 desktop-tauri\\\\python_bridge\\\\start_agent_browser_bridge.bat 。",
  "多 Agent 模式下不要裸发控制命令。先固定一个 agentId，然后：GET /clients 查看 clientId；POST /leases/acquire 提交 { agentId, clientId, ttlMs } 拿 leaseId；之后 POST /commands 必须带 { agentId, leaseId, clientId, action, tabId, payload }；长任务期间定期 POST /leases/renew；结束时 POST /leases/release。",
  "本地会话桥额外支持调试动作：plugin_status 查看插件运行状态与当前解析 tab；plugin_visual_state 查看当前画面监听帧与会话摘要；plugin_logs 查看最近插件运行日志；plugin_logs_clear 清空本轮运行日志。以上四个动作不对网页 window.postMessage 暴露。",
  "如果当前只能在网页上下文里工作，再退回 window.postMessage 协议；页面桥主要负责 snapshot/target resolve/highlight，真正的低层输入优先经由本地会话桥触发。",
  "请求格式：window.postMessage({ type: \"MMW_AGENT_BROWSER_CONTROL\", requestId: \"req-1\", action: \"snapshot\", payload: { limit: 80 } }, \"*\");",
  "结果监听：window.addEventListener(\"message\", (event) => { const data = event.data || {}; if (data.type === \"MMW_AGENT_BROWSER_CONTROL_RESULT\") console.log(data); });",
  "可用动作：state、snapshot、click、type、scroll、key、move、highlight、wait、navigate、back、forward、reload、screenshot、new_tab、open_tab、list_tabs、activate_tab、close_tab。",
  "建议流程：先拿租约，再查 state 或 list_tabs 拿 tabId；对具体页面先 snapshot，再复用 ref 做 click/type/scroll；动作失败时重新 snapshot。"
].join("\n");

const REFERENCE_CAPTURE_PROMPT = [
  "请用 MMWGZT 插件的管理员调试-参考插件抓包来复现参考插件流程。",
  "我已经拖入参考插件包并点击“开始参考抓包”。请手动加载/运行参考插件，然后只复现一个最小流程：打开目标页面、点击参考插件入口、触发加载商品或设置动作。",
  "复现后回到 MMWGZT 插件弹窗，点击“导出”或“复制”，把日志发给我。",
  "重点看 reference-fetch、reference-xhr、reference-network-request、reference-network-response、reference-dom-hook、reference-storage-set、reference-postMessage 事件。不要发送账号密码、cookie 或验证码。"
].join("\n");

const DEFAULT_REFERENCE_RULES = {
  packageName: "Douyin ecommerce reference defaults",
  packageVersion: "",
  scannedFiles: 0,
  manifestMatches: [
    "https://haohuo.jinritemai.com/*",
    "https://fxg.jinritemai.com/*",
    "https://www.douyin.com/*",
    "https://*.douyin.com/*",
    "https://qianchuan.jinritemai.com/*",
    "https://*.oceanengine.com/*",
    "https://*.dangxun.com/*",
    "https://*.dxcdn.cn/*"
  ],
  urlKeywords: [
    "promotion/pack/h5",
    "promotion/pack/detail",
    "product/sku/list",
    "ecom/product/detail",
    "freight/template/getShopFreightTemplate",
    "getTemplateManageInfo",
    "ffa/g/create",
    "ffa/mshop",
    "qianchuan.jinritemai.com",
    "ad.oceanengine.com",
    "uni-prom",
    "overall",
    "createV2",
    "promotion-v2",
    "dangxun",
    "dxcdn",
    "eval_code_url"
  ],
  bodyKeywords: [
    "promotion_h5",
    "detail_info",
    "basic_info_data",
    "title_info",
    "price_info",
    "head_figure_data",
    "product_format",
    "sku_list",
    "specs",
    "templateName",
    "template_name",
    "popupConfig",
    "contentConfig",
    "optionsConfig",
    "eval_code_url",
    "registeredUserScript",
    "userScripts",
    "serverData"
  ],
  messageKeywords: ["hook_data", "hook_detail", "hook_skulist", "tabMessageBrige", "REQUEST", "RESPONSE", "contentInit", "optionsInit", "popupInit", "executeWebScript"],
  storageKeys: ["custom_value", "custom_value_list", "custom_img", "Json", "detail_text_load", "GjServerCfg", "HuskyLoginToken", "GjRegisteredHash"],
  hookSelectors: [".hook_data", ".hook_detail", ".hook_skulist", ".hook_pc", ".hook_confirm"],
  notes: ["未拖入参考包时使用的抖店搬家/千川参考抓包默认规则。"]
};

const REFERENCE_MAX_FILES = 160;
const REFERENCE_MAX_TEXT_BYTES = 1024 * 1024;
const REFERENCE_MAX_ZIP_BYTES = 80 * 1024 * 1024;
const REFERENCE_TEXT_FILE_RE = /\.(js|mjs|cjs|json|html?|txt|css)$/i;
const REFERENCE_BINARY_FILE_RE = /\.(png|jpe?g|gif|webp|avif|svg|ico|mp4|mov|m4v|avi|webm|mp3|wav|ogg|flac|woff2?|ttf|otf|eot|pdf|map)$/i;

const state = {
  activeTab: null,
  settings: {
    compassJump: true,
    douyinDownload: true,
    agentControl: false
  },
  debug: {
    admin: false,
    enabled: false,
    pageProbeEnabled: false,
    screenObserverEnabled: false,
    screenObserver: null,
    referenceCaptureEnabled: false,
    referenceCaptureRules: DEFAULT_REFERENCE_RULES,
    referenceCapturePackage: {},
    referenceCaptureSession: null,
    referenceCaptureSessions: [],
    logs: [],
    count: 0,
    maxLogs: 0
  },
  legalConsent: {
    accepted: false,
    version: "",
    acceptedAt: 0
  },
  tutorialNavigation: { url: "", revision: 0 },
  feedback: { text: "", kind: "" },
  agentControlStatus: {
    text: "\u672a\u68c0\u6d4b\u5f53\u524d\u9875\u9762\u63a5\u7ba1\u901a\u9053",
    kind: "",
    loading: false
  }
};
let adminDebugLiveRefreshTimer = 0;

document.addEventListener("DOMContentLoaded", () => {
  bindUi();
  chrome.storage.onChanged.addListener(handleStorageChange);
  initialize().catch((error) => {
    setFeedback(error.message || "插件弹窗初始化失败。", "error");
    render();
  });
});

async function initialize() {
  document.querySelector("#versionText").textContent = `版本 v${chrome.runtime.getManifest().version}`;
  await Promise.all([refreshLegalConsent(), refreshSettings(), refreshActiveTab(), refreshAdminDebug(), refreshTutorialNavigation()]);
  if (state.legalConsent.accepted) {
    await refreshAgentControlStatus();
  } else {
    setAgentControlStatus("\u540c\u610f\u534f\u8bae\u540e\u53ef\u68c0\u6d4b\u5f53\u524d\u9875\u9762\u63a5\u7ba1\u901a\u9053");
  }
  render();
}

function syncAdminDebugLiveRefresh() {
  const shouldRun = state.debug.admin && (state.debug.referenceCaptureEnabled || state.debug.screenObserverEnabled);
  if (!shouldRun) {
    if (adminDebugLiveRefreshTimer) {
      clearInterval(adminDebugLiveRefreshTimer);
      adminDebugLiveRefreshTimer = 0;
    }
    return;
  }
  if (adminDebugLiveRefreshTimer) return;
  adminDebugLiveRefreshTimer = setInterval(() => {
    if (document.hidden) return;
    refreshAdminDebug().catch(() => {});
  }, 2500);
}

window.addEventListener("beforeunload", () => {
  if (!adminDebugLiveRefreshTimer) return;
  clearInterval(adminDebugLiveRefreshTimer);
  adminDebugLiveRefreshTimer = 0;
});

function bindUi() {
  document.querySelectorAll("[data-view]").forEach((button) => {
    button.addEventListener("click", () => openFloatingDesk(button.dataset.view || "home"));
  });

  document.querySelectorAll("[data-open-link]").forEach((button) => {
    button.addEventListener("click", () => openUrl(button.dataset.openLink || ""));
  });

  document.querySelector("[data-open-ecommerce]")?.addEventListener("click", () => {
    openNativeEcommerceTool().catch((error) => {
      setFeedback(error?.message || "电商工具打开失败。", "error");
      render();
    });
  });

  document.querySelectorAll("[data-open-options]").forEach((button) => {
    button.addEventListener("click", openAixOptions);
  });

  document.querySelector("#consentMask")?.addEventListener("click", () => {
    requireLegalConsent();
  });

  document.querySelectorAll("[data-setting]").forEach((input) => {
    input.addEventListener("change", () => updateSetting(input.dataset.setting || "", input.checked));
  });

  document.querySelector("#checkAgentControlButton")?.addEventListener("click", () => {
    refreshAgentControlStatus(true).catch((error) => {
      setAgentControlStatus(error?.message || "\u63a5\u7ba1\u901a\u9053\u68c0\u6d4b\u5931\u8d25", "error");
      render();
    });
  });
  document.querySelector("#copyAgentPromptButton")?.addEventListener("click", copyAgentControlPrompt);

  document.querySelector("#debugToggleButton")?.addEventListener("click", toggleAdminDebug);
  document.querySelector("#debugPageProbeButton")?.addEventListener("click", toggleAdminPageProbe);
  document.querySelector("#debugRefreshButton")?.addEventListener("click", () => refreshAdminDebug(true));
  document.querySelector("#debugCopyButton")?.addEventListener("click", copyAdminDebugLogs);
  document.querySelector("#debugDownloadButton")?.addEventListener("click", downloadAdminDebugLogs);
  document.querySelector("#debugClearButton")?.addEventListener("click", clearAdminDebugLogs);
  document.querySelector("#screenObserverToggleButton")?.addEventListener("click", toggleAdminScreenObserver);
  document.querySelector("#screenObserverRefreshButton")?.addEventListener("click", captureAdminScreenObserverFrame);
  document.querySelector("#referenceDropZone")?.addEventListener("click", () => document.querySelector("#referenceFilesInput")?.click());
  document.querySelector("#referenceDropZone")?.addEventListener("dragover", handleReferenceDragOver);
  document.querySelector("#referenceDropZone")?.addEventListener("dragleave", handleReferenceDragLeave);
  document.querySelector("#referenceDropZone")?.addEventListener("drop", handleReferenceDrop);
  document.querySelector("#referenceFilesButton")?.addEventListener("click", () => document.querySelector("#referenceFilesInput")?.click());
  document.querySelector("#referenceFolderButton")?.addEventListener("click", () => document.querySelector("#referenceFolderInput")?.click());
  document.querySelector("#referenceDefaultButton")?.addEventListener("click", useDefaultReferenceRules);
  document.querySelector("#referenceFilesInput")?.addEventListener("change", (event) => loadReferenceFiles(event.target.files));
  document.querySelector("#referenceFolderInput")?.addEventListener("change", (event) => loadReferenceFiles(event.target.files));
  document.querySelector("#referenceStartButton")?.addEventListener("click", startReferenceCapture);
  document.querySelector("#referenceStopButton")?.addEventListener("click", stopReferenceCapture);
  document.querySelector("#referencePromptButton")?.addEventListener("click", copyReferenceCapturePrompt);
}

function handleStorageChange(changes, areaName) {
  if (areaName !== "local") return;
  const legalKeys = window.MMWLegalConsent?.STORAGE_KEYS || {};
  if (
    changes[legalKeys.accepted]
    || changes[legalKeys.version]
    || changes[legalKeys.acceptedAt]
  ) {
    refreshLegalConsent().then(render).catch(() => render());
  }
  if (changes[STORAGE_KEYS.compassJump]) {
    state.settings.compassJump = changes[STORAGE_KEYS.compassJump].newValue !== false;
  }
  if (changes[STORAGE_KEYS.douyinDownload]) {
    state.settings.douyinDownload = changes[STORAGE_KEYS.douyinDownload].newValue !== false;
  }
  if (changes[STORAGE_KEYS.agentControl]) {
    state.settings.agentControl = changes[STORAGE_KEYS.agentControl].newValue === true;
  }
  render();
}

async function refreshLegalConsent() {
  const consent = window.MMWLegalConsent;
  if (!consent?.init) {
    state.legalConsent = { accepted: true, version: "", acceptedAt: 0 };
    return;
  }
  const status = await consent.init({
    onChange: (nextStatus) => {
      state.legalConsent = nextStatus;
      render();
    }
  });
  state.legalConsent = status;
}

async function refreshSettings() {
  const values = await chrome.storage.local.get([
    STORAGE_KEYS.compassJump,
    STORAGE_KEYS.douyinDownload,
    STORAGE_KEYS.agentControl
  ]);
  state.settings.compassJump = values[STORAGE_KEYS.compassJump] !== false;
  state.settings.douyinDownload = values[STORAGE_KEYS.douyinDownload] !== false;
  state.settings.agentControl = values[STORAGE_KEYS.agentControl] === true;
}

async function refreshActiveTab() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  state.activeTab = tab || null;
}

async function refreshAdminDebug(showFeedback = false) {
  const response = await chrome.runtime.sendMessage({ type: "ADMIN_DEBUG_STATUS" }).catch((error) => ({
    ok: false,
    error: error.message || String(error)
  }));
  applyAdminDebugResponse(response);
  if (showFeedback && response?.ok) setFeedback("管理员调试日志已刷新。", "success");
  else if (showFeedback && response?.error) setFeedback(response.error, "error");
  render();
}

function applyAdminDebugResponse(response) {
  if (!response?.ok) {
    state.debug = {
      admin: false,
      enabled: false,
      pageProbeEnabled: false,
      screenObserverEnabled: false,
      screenObserver: null,
      referenceCaptureEnabled: false,
      referenceCaptureRules: DEFAULT_REFERENCE_RULES,
      referenceCapturePackage: {},
      referenceCaptureSession: null,
      referenceCaptureSessions: [],
      logs: [],
      count: 0,
      maxLogs: 0
    };
    return;
  }
  state.debug = {
    admin: response.admin === true,
    enabled: response.enabled === true,
    pageProbeEnabled: response.pageProbeEnabled === true,
    screenObserverEnabled: response.screenObserverEnabled === true,
    screenObserver: normalizeScreenObserverForUi(response.screenObserver),
    referenceCaptureEnabled: response.referenceCaptureEnabled === true,
    referenceCaptureRules: normalizeReferenceRulesForUi(response.referenceCaptureRules),
    referenceCapturePackage: normalizeReferencePackageForUi(response.referenceCapturePackage),
    referenceCaptureSession: normalizeReferenceSessionForUi(response.referenceCaptureSession),
    referenceCaptureSessions: normalizeReferenceSessionsForUi(response.referenceCaptureSessions),
    logs: Array.isArray(response.logs) ? response.logs : [],
    count: Number(response.count || 0),
    maxLogs: Number(response.maxLogs || 0)
  };
}

async function toggleAdminDebug() {
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_ENABLED",
    enabled: !state.debug.enabled
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(response?.ok ? (state.debug.enabled ? "管理员调试采集已启用。" : "管理员调试采集已停用。") : response.error, response?.ok ? "success" : "error");
  render();
}

async function toggleAdminPageProbe() {
  const nextEnabled = !state.debug.pageProbeEnabled;
  if (nextEnabled && !state.debug.enabled) {
    const enableResponse = await chrome.runtime.sendMessage({
      type: "ADMIN_DEBUG_SET_ENABLED",
      enabled: true
    }).catch((error) => ({ ok: false, error: error.message || String(error) }));
    applyAdminDebugResponse(enableResponse);
    if (!enableResponse?.ok) {
      setFeedback(enableResponse.error, "error");
      render();
      return;
    }
  }
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_PAGE_PROBE",
    enabled: nextEnabled
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(
    response?.ok
      ? (state.debug.pageProbeEnabled ? "页面抓包已启用，刷新目标页面后可捕获更完整。" : "页面抓包已停用。")
      : response.error,
    response?.ok ? "success" : "error"
  );
  render();
}

async function toggleAdminScreenObserver() {
  const nextEnabled = !state.debug.screenObserverEnabled;
  if (nextEnabled && !state.debug.enabled) {
    const enableResponse = await chrome.runtime.sendMessage({
      type: "ADMIN_DEBUG_SET_ENABLED",
      enabled: true
    }).catch((error) => ({ ok: false, error: error.message || String(error) }));
    applyAdminDebugResponse(enableResponse);
    if (!enableResponse?.ok) {
      setFeedback(enableResponse.error, "error");
      render();
      return;
    }
  }
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_SCREEN_OBSERVER",
    enabled: nextEnabled
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(
    response?.ok
      ? (state.debug.screenObserverEnabled ? "画面监听已开启，会和参考抓包会话同步记录最近画面帧。" : "画面监听已关闭。")
      : response.error,
    response?.ok ? "success" : "error"
  );
  render();
}

async function captureAdminScreenObserverFrame() {
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_CAPTURE_SCREEN_OBSERVER_FRAME",
    reason: "manual"
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(response?.ok ? "已补抓一帧当前画面。" : response.error, response?.ok ? "success" : "error");
  render();
}

async function copyAdminDebugLogs() {
  const text = formatAdminDebugLogs();
  if (!text) {
    setFeedback("当前没有可复制的调试日志。", "error");
    render();
    return;
  }
  try {
    await navigator.clipboard.writeText(text);
    setFeedback("调试日志已复制。", "success");
  } catch {
    const output = document.querySelector("#debugOutput");
    output?.select();
    document.execCommand("copy");
    setFeedback("调试日志已复制。", "success");
  }
  render();
}

function downloadAdminDebugLogs() {
  const text = formatAdminDebugLogs();
  if (!text) {
    setFeedback("当前没有可导出的调试日志。", "error");
    render();
    return;
  }
  const blob = new Blob([text], { type: "application/json;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.download = `mmw-admin-debug-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
  anchor.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
  setFeedback("调试日志已导出。", "success");
  render();
}

async function clearAdminDebugLogs() {
  const response = await chrome.runtime.sendMessage({ type: "ADMIN_DEBUG_CLEAR" }).catch((error) => ({
    ok: false,
    error: error.message || String(error)
  }));
  applyAdminDebugResponse(response);
  setFeedback(response?.ok ? "调试日志已清空。" : response.error, response?.ok ? "success" : "error");
  render();
}

function handleReferenceDragOver(event) {
  event.preventDefault();
  event.currentTarget?.classList.add("is-dragging");
}

function handleReferenceDragLeave(event) {
  event.currentTarget?.classList.remove("is-dragging");
}

async function handleReferenceDrop(event) {
  event.preventDefault();
  event.currentTarget?.classList.remove("is-dragging");
  const files = await filesFromDataTransfer(event.dataTransfer).catch(() => []);
  await loadReferenceFiles(files.length ? files : event.dataTransfer?.files);
}

async function loadReferenceFiles(fileList) {
  const files = Array.from(fileList || []).filter(Boolean);
  if (!files.length) return;
  setFeedback("正在解析参考插件包...", "");
  render();

  try {
    const { entries, warnings, fileCount } = await readReferenceEntries(files);
    if (!entries.length) {
      throw new Error("没有找到可读取的参考插件文本文件。zip 无法解压时，请先解压后点“选择文件夹”。");
    }
    const rules = buildReferenceRules(entries);
    const packageSummary = {
      name: rules.packageName || guessReferencePackageName(files),
      version: rules.packageVersion || "",
      fileCount,
      scannedFiles: entries.length,
      loadedAt: new Date().toISOString(),
      warnings
    };
    const response = await chrome.runtime.sendMessage({
      type: "ADMIN_DEBUG_SET_REFERENCE_CAPTURE",
      enabled: false,
      rules,
      packageSummary
    }).catch((error) => ({ ok: false, error: error.message || String(error) }));
    applyAdminDebugResponse(response);
    setFeedback(
      response?.ok
        ? `参考包规则已生成：扫描 ${entries.length} 个文件。点击“开始参考抓包”后运行参考插件。`
        : response.error,
      response?.ok ? "success" : "error"
    );
  } catch (error) {
    setFeedback(error?.message || String(error), "error");
  } finally {
    const fileInput = document.querySelector("#referenceFilesInput");
    const folderInput = document.querySelector("#referenceFolderInput");
    if (fileInput) fileInput.value = "";
    if (folderInput) folderInput.value = "";
    render();
  }
}

async function useDefaultReferenceRules() {
  const packageSummary = {
    name: DEFAULT_REFERENCE_RULES.packageName,
    version: DEFAULT_REFERENCE_RULES.packageVersion,
    fileCount: 0,
    scannedFiles: 0,
    loadedAt: new Date().toISOString(),
    warnings: ["使用内置抖店搬家默认抓包规则。"]
  };
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_REFERENCE_CAPTURE",
    enabled: false,
    rules: DEFAULT_REFERENCE_RULES,
    packageSummary
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(response?.ok ? "已切换为默认抖店抓包规则。" : response.error, response?.ok ? "success" : "error");
  render();
}

async function startReferenceCapture() {
  const rules = getEffectiveReferenceRules();
  const packageSummary = getEffectiveReferencePackage(rules);
  if (!packageSummary.loadedAt) packageSummary.loadedAt = new Date().toISOString();
  const session = buildReferenceCaptureSessionForUi(packageSummary);
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_REFERENCE_CAPTURE",
    enabled: true,
    rules,
    packageSummary,
    session
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(
    response?.ok
      ? `参考插件抓包已开启，会话：${normalizeReferenceSessionForUi(response.referenceCaptureSession)?.name || session.name}。刷新目标页面后运行参考插件，再导出日志。`
      : response.error,
    response?.ok ? "success" : "error"
  );
  render();
}

async function stopReferenceCapture() {
  const rules = getEffectiveReferenceRules();
  const packageSummary = getEffectiveReferencePackage(rules);
  const session = normalizeReferenceSessionForUi(state.debug.referenceCaptureSession);
  const response = await chrome.runtime.sendMessage({
    type: "ADMIN_DEBUG_SET_REFERENCE_CAPTURE",
    enabled: false,
    rules,
    packageSummary,
    session: session ? { ...session, endedAt: new Date().toISOString() } : null
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));
  applyAdminDebugResponse(response);
  setFeedback(response?.ok ? `参考插件抓包已停止${session?.name ? `：${session.name}` : ""}。` : response.error, response?.ok ? "success" : "error");
  render();
}

async function copyReferenceCapturePrompt() {
  const text = [
    REFERENCE_CAPTURE_PROMPT,
    "",
    "当前参考抓包规则摘要：",
    formatReferenceRules()
  ].join("\n");
  try {
    await navigator.clipboard.writeText(text);
    setFeedback("已复制参考抓包提示词。", "success");
  } catch {
    setFeedback("复制失败，请手动复制规则摘要。", "error");
  }
  render();
}

async function filesFromDataTransfer(dataTransfer) {
  const items = Array.from(dataTransfer?.items || []);
  const entries = items
    .map((item) => item.webkitGetAsEntry?.())
    .filter(Boolean);
  if (!entries.length) return Array.from(dataTransfer?.files || []);
  const files = [];
  for (const entry of entries) {
    await collectDataTransferEntry(entry, files);
    if (files.length >= REFERENCE_MAX_FILES) break;
  }
  return files;
}

async function collectDataTransferEntry(entry, files) {
  if (!entry || files.length >= REFERENCE_MAX_FILES) return;
  if (entry.isFile) {
    const file = await new Promise((resolve) => entry.file(resolve, () => resolve(null)));
    if (file) {
      file.__mmwRelativePath = String(entry.fullPath || file.name || "").replace(/^\/+/, "");
      files.push(file);
    }
    return;
  }
  if (!entry.isDirectory) return;
  const reader = entry.createReader();
  while (files.length < REFERENCE_MAX_FILES) {
    const batch = await new Promise((resolve) => reader.readEntries(resolve, () => resolve([])));
    if (!batch.length) break;
    for (const child of batch) {
      await collectDataTransferEntry(child, files);
      if (files.length >= REFERENCE_MAX_FILES) break;
    }
  }
}

async function readReferenceEntries(files) {
  const entries = [];
  const warnings = [];
  let fileCount = 0;

  for (const file of files.slice(0, REFERENCE_MAX_FILES)) {
    const path = getReferenceFilePath(file);
    if (/\.zip$/i.test(path)) {
      const zipResult = await readReferenceZip(file).catch((error) => ({
        entries: [],
        fileCount: 0,
        warnings: [`${path}: ${error?.message || String(error)}`]
      }));
      entries.push(...zipResult.entries);
      fileCount += zipResult.fileCount;
      warnings.push(...zipResult.warnings);
      continue;
    }

    fileCount += 1;
    if (!shouldReadReferencePath(path)) continue;
    if (file.size > REFERENCE_MAX_TEXT_BYTES) {
      warnings.push(`${path}: 文件过大，已跳过。`);
      continue;
    }
    entries.push({ path, text: await file.text() });
    if (entries.length >= REFERENCE_MAX_FILES) break;
  }

  if (files.length > REFERENCE_MAX_FILES) warnings.push(`文件数量超过 ${REFERENCE_MAX_FILES}，只扫描前 ${REFERENCE_MAX_FILES} 个。`);
  return { entries: entries.slice(0, REFERENCE_MAX_FILES), warnings: cleanTextList(warnings, 12, 240), fileCount };
}

async function readReferenceZip(file) {
  if (file.size > REFERENCE_MAX_ZIP_BYTES) {
    throw new Error("zip 包过大，建议先解压后选择插件目录。");
  }
  const buffer = await file.arrayBuffer();
  const view = new DataView(buffer);
  const endOffset = findZipEndOffset(view);
  if (endOffset < 0) throw new Error("不是有效的 zip 包。");

  const decoder = new TextDecoder("utf-8");
  const totalEntries = view.getUint16(endOffset + 10, true);
  let pointer = view.getUint32(endOffset + 16, true);
  const entries = [];
  const warnings = [];
  let fileCount = 0;

  for (let index = 0; index < totalEntries && pointer < view.byteLength; index += 1) {
    if (view.getUint32(pointer, true) !== 0x02014b50) break;
    const method = view.getUint16(pointer + 10, true);
    const compressedSize = view.getUint32(pointer + 20, true);
    const uncompressedSize = view.getUint32(pointer + 24, true);
    const nameLength = view.getUint16(pointer + 28, true);
    const extraLength = view.getUint16(pointer + 30, true);
    const commentLength = view.getUint16(pointer + 32, true);
    const localOffset = view.getUint32(pointer + 42, true);
    const path = decoder.decode(new Uint8Array(buffer, pointer + 46, nameLength));
    pointer += 46 + nameLength + extraLength + commentLength;

    if (!path || path.endsWith("/")) continue;
    fileCount += 1;
    if (!shouldReadReferencePath(path)) continue;
    if (entries.length >= REFERENCE_MAX_FILES) continue;
    if (uncompressedSize > REFERENCE_MAX_TEXT_BYTES) {
      warnings.push(`${path}: 文件过大，已跳过。`);
      continue;
    }
    try {
      const text = await readZipTextEntry(buffer, view, { method, compressedSize, localOffset });
      entries.push({ path, text });
    } catch (error) {
      warnings.push(`${path}: ${error?.message || String(error)}`);
    }
  }

  return { entries, warnings: cleanTextList(warnings, 12, 240), fileCount };
}

function findZipEndOffset(view) {
  const minOffset = Math.max(0, view.byteLength - 66000);
  for (let offset = view.byteLength - 22; offset >= minOffset; offset -= 1) {
    if (view.getUint32(offset, true) === 0x06054b50) return offset;
  }
  return -1;
}

async function readZipTextEntry(buffer, view, entry) {
  if (view.getUint32(entry.localOffset, true) !== 0x04034b50) {
    throw new Error("zip 本地文件头异常。");
  }
  const nameLength = view.getUint16(entry.localOffset + 26, true);
  const extraLength = view.getUint16(entry.localOffset + 28, true);
  const dataStart = entry.localOffset + 30 + nameLength + extraLength;
  const data = buffer.slice(dataStart, dataStart + entry.compressedSize);
  if (entry.method === 0) return new TextDecoder("utf-8").decode(data);
  if (entry.method !== 8) throw new Error(`不支持的 zip 压缩方式 ${entry.method}。`);
  if (typeof DecompressionStream !== "function") {
    throw new Error("当前浏览器不支持直接解压 zip，请先解压后选择文件夹。");
  }
  const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
  const decompressed = await new Response(stream).arrayBuffer();
  return new TextDecoder("utf-8").decode(decompressed);
}

function buildReferenceRules(entries) {
  const manifest = readReferenceManifest(entries);
  const manifestMatches = new Set(DEFAULT_REFERENCE_RULES.manifestMatches);
  const urlKeywords = new Set(DEFAULT_REFERENCE_RULES.urlKeywords);
  const bodyKeywords = new Set(DEFAULT_REFERENCE_RULES.bodyKeywords);
  const messageKeywords = new Set(DEFAULT_REFERENCE_RULES.messageKeywords);
  const storageKeys = new Set(DEFAULT_REFERENCE_RULES.storageKeys);
  const hookSelectors = new Set(DEFAULT_REFERENCE_RULES.hookSelectors);
  const notes = [];

  for (const match of manifest.matches) manifestMatches.add(match);
  for (const entry of entries) {
    extractReferenceRulesFromText(entry.text, {
      urlKeywords,
      bodyKeywords,
      messageKeywords,
      storageKeys,
      hookSelectors
    });
  }

  if (!manifest.name) notes.push("未识别到 manifest 名称，已按文件内容生成抓包关键词。");
  return {
    packageName: manifest.name || "Reference plugin",
    packageVersion: manifest.version || "",
    scannedFiles: entries.length,
    manifestMatches: finalTextList(manifestMatches, 80, 240),
    urlKeywords: finalTextList(urlKeywords, 120, 220),
    bodyKeywords: finalTextList(bodyKeywords, 160, 220),
    messageKeywords: finalTextList(messageKeywords, 120, 160),
    storageKeys: finalTextList(storageKeys, 80, 120),
    hookSelectors: finalTextList(hookSelectors, 60, 120),
    notes: finalTextList(new Set([...DEFAULT_REFERENCE_RULES.notes, ...notes]), 20, 240)
  };
}

function readReferenceManifest(entries) {
  const manifests = entries
    .filter((entry) => /(^|\/)manifest\.json$/i.test(entry.path))
    .sort((a, b) => a.path.length - b.path.length);
  const result = { name: "", version: "", matches: [] };
  for (const entry of manifests) {
    try {
      const manifest = JSON.parse(entry.text);
      result.name = String(manifest.name || manifest.short_name || "").trim();
      result.version = String(manifest.version || "").trim();
      const matches = [];
      for (const script of Array.isArray(manifest.content_scripts) ? manifest.content_scripts : []) {
        if (Array.isArray(script.matches)) matches.push(...script.matches);
      }
      if (Array.isArray(manifest.host_permissions)) matches.push(...manifest.host_permissions);
      if (Array.isArray(manifest.permissions)) matches.push(...manifest.permissions.filter((item) => /^https?:\/\//i.test(item)));
      result.matches = cleanTextList(matches, 80, 240);
      if (result.name || result.version || result.matches.length) break;
    } catch {}
  }
  return result;
}

function extractReferenceRulesFromText(text, target) {
  const source = String(text || "").slice(0, REFERENCE_MAX_TEXT_BYTES);
  extractUrlKeywords(source, target.urlKeywords);
  extractBodyKeywords(source, target.bodyKeywords);
  extractMessageKeywords(source, target.messageKeywords);
  extractStorageKeys(source, target.storageKeys);
  extractHookSelectors(source, target.hookSelectors);
}

function extractUrlKeywords(source, output) {
  const urlRe = /https?:\/\/[^\s"'`<>\\)]+|["'`]((?:\/|\.\/|\.\.\/)[^"'`<>\\]{4,180})["'`]/g;
  let match;
  while ((match = urlRe.exec(source))) {
    addUsefulUrlTerm(output, match[1] || match[0]);
    if (output.size >= 140) return;
  }
}

function addUsefulUrlTerm(output, rawValue) {
  let value = String(rawValue || "").trim().replace(/^["'`]+|["'`,;]+$/g, "");
  if (!value) return;
  try {
    const url = new URL(value, "https://example.invalid");
    if (url.hostname && url.hostname !== "example.invalid" && /douyin|jinritemai|bytedance|ecom|fxg|haohuo|qianchuan|oceanengine|dangxun|dxcdn|hsq/i.test(url.hostname)) {
      output.add(url.hostname);
    }
    value = `${url.pathname || ""}${url.search || ""}`;
  } catch {}
  value = decodeMaybeText(value).replace(/^\.{0,2}\//, "").replace(/[?#].*$/, "");
  if (!/promotion|product|sku|freight|template|order|shop|upload|detail|haohuo|jinritemai|douyin|fxg|mshop|ffa|ecommerce|trade|logistics|category|title|price|pack|qianchuan|oceanengine|uni-prom|overall|createV2|eval_code_url|dangxun|dxcdn|hsq/i.test(value)) return;
  if (value.length < 4) return;
  output.add(value.slice(0, 220));
}

function extractBodyKeywords(source, output) {
  const fieldRe = /\b[a-zA-Z_$][\w$.-]{2,90}\b/g;
  let match;
  while ((match = fieldRe.exec(source))) {
    const term = match[0];
    if (!/hook_|title|price|sku|spu|image|img|pic|cover|freight|template|logistic|category|shop|product|promotion|detail|stock|spec|attr|origin|publish|submit|audit|brand|qianchuan|uni|overall|eval|serverData|contentConfig|popupConfig|optionsConfig|registeredUserScript|userScripts|campaign|creative|budget|roi/i.test(term)) continue;
    output.add(term.slice(0, 220));
    if (output.size >= 180) return;
  }
}

function extractMessageKeywords(source, output) {
  const messageRe = /\b[a-zA-Z_$][\w$-]{2,100}\b/g;
  let match;
  while ((match = messageRe.exec(source))) {
    const term = match[0];
    if (!/postMessage|sendMessage|onMessage|bridge|hook|REQUEST|RESPONSE|message|tabMessage|contentInit|optionsInit|popupInit|executeWebScript|executeCSS|customCmd/i.test(term)) continue;
    output.add(term.slice(0, 160));
    if (output.size >= 130) return;
  }
}

function extractStorageKeys(source, output) {
  const storageRe = /\b(?:localStorage|sessionStorage)\s*\.\s*(?:getItem|setItem|removeItem)\s*\(\s*["'`]([^"'`]{1,100})["'`]/g;
  let match;
  while ((match = storageRe.exec(source))) {
    output.add(match[1].slice(0, 120));
    if (output.size >= 90) return;
  }
}

function extractHookSelectors(source, output) {
  const selectorRe = /\b(?:querySelector(?:All)?|matches)\s*\(\s*["'`]([^"'`]{1,120})["'`]\s*\)/g;
  let match;
  while ((match = selectorRe.exec(source))) {
    const selector = match[1].trim();
    if (!/hook|data-|sku|detail|confirm|pc/i.test(selector)) continue;
    output.add(selector.slice(0, 120));
    if (output.size >= 70) return;
  }
  const classRe = /class=["'][^"']*\b(hook[_a-z0-9-]*)\b[^"']*["']/gi;
  while ((match = classRe.exec(source))) {
    output.add(`.${match[1]}`);
    if (output.size >= 70) return;
  }
}

function getReferenceFilePath(file) {
  return String(file?.webkitRelativePath || file?.__mmwRelativePath || file?.name || "").replace(/\\/g, "/");
}

function shouldReadReferencePath(path) {
  const value = String(path || "");
  if (!value || /(^|\/)(__MACOSX|\.git|node_modules)(\/|$)/i.test(value)) return false;
  if (REFERENCE_BINARY_FILE_RE.test(value)) return false;
  return REFERENCE_TEXT_FILE_RE.test(value) || /(^|\/)manifest\.json$/i.test(value);
}

function guessReferencePackageName(files) {
  const first = files.find(Boolean);
  const path = getReferenceFilePath(first);
  return path.split(/[\\/]/).filter(Boolean)[0] || first?.name || "Reference plugin";
}

function normalizeReferenceRulesForUi(value) {
  const source = value && typeof value === "object" ? value : {};
  const hasSource = Object.keys(source).length > 0;
  const fallback = hasSource ? {} : DEFAULT_REFERENCE_RULES;
  return {
    packageName: String(source.packageName || fallback.packageName || "").trim(),
    packageVersion: String(source.packageVersion || fallback.packageVersion || "").trim(),
    scannedFiles: Math.max(0, Number(source.scannedFiles || fallback.scannedFiles || 0) || 0),
    manifestMatches: cleanTextList(source.manifestMatches || fallback.manifestMatches, 80, 240),
    urlKeywords: cleanTextList(source.urlKeywords || fallback.urlKeywords, 120, 220),
    bodyKeywords: cleanTextList(source.bodyKeywords || fallback.bodyKeywords, 160, 220),
    messageKeywords: cleanTextList(source.messageKeywords || fallback.messageKeywords, 120, 160),
    storageKeys: cleanTextList(source.storageKeys || fallback.storageKeys, 80, 120),
    hookSelectors: cleanTextList(source.hookSelectors || fallback.hookSelectors, 60, 120),
    notes: cleanTextList(source.notes || fallback.notes, 20, 240)
  };
}

function normalizeReferencePackageForUi(value) {
  const source = value && typeof value === "object" ? value : {};
  return {
    name: String(source.name || source.packageName || "").trim(),
    version: String(source.version || source.packageVersion || "").trim(),
    fileCount: Math.max(0, Number(source.fileCount || 0) || 0),
    scannedFiles: Math.max(0, Number(source.scannedFiles || 0) || 0),
    loadedAt: String(source.loadedAt || "").trim(),
    warnings: cleanTextList(source.warnings, 12, 240)
  };
}

function getEffectiveReferenceRules() {
  const rules = normalizeReferenceRulesForUi(state.debug.referenceCaptureRules);
  return rules.urlKeywords.length || rules.bodyKeywords.length || rules.manifestMatches.length
    ? rules
    : normalizeReferenceRulesForUi(DEFAULT_REFERENCE_RULES);
}

function getEffectiveReferencePackage(rules = getEffectiveReferenceRules()) {
  const source = normalizeReferencePackageForUi(state.debug.referenceCapturePackage);
  return {
    name: source.name || rules.packageName || DEFAULT_REFERENCE_RULES.packageName,
    version: source.version || rules.packageVersion || "",
    fileCount: source.fileCount,
    scannedFiles: source.scannedFiles || rules.scannedFiles || 0,
    loadedAt: source.loadedAt || "",
    warnings: source.warnings
  };
}

function hasLoadedReferencePackage() {
  const pkg = normalizeReferencePackageForUi(state.debug.referenceCapturePackage);
  const rules = normalizeReferenceRulesForUi(state.debug.referenceCaptureRules);
  return Boolean(pkg.loadedAt || pkg.fileCount || rules.scannedFiles);
}

function formatReferenceRules() {
  const rules = getEffectiveReferenceRules();
  return JSON.stringify({
    enabled: state.debug.referenceCaptureEnabled,
    session: state.debug.referenceCaptureSession,
    package: getEffectiveReferencePackage(rules),
    rules
  }, null, 2);
}

function cleanTextList(value, maxItems, maxLength) {
  const result = [];
  const seen = new Set();
  const source = Array.isArray(value) ? value : [];
  for (const item of source) {
    const text = String(item || "").trim();
    const key = text.toLowerCase();
    if (!text || seen.has(key)) continue;
    seen.add(key);
    result.push(text.slice(0, maxLength));
    if (result.length >= maxItems) break;
  }
  return result;
}

function finalTextList(value, maxItems, maxLength) {
  return cleanTextList(Array.from(value || []), maxItems, maxLength);
}

function decodeMaybeText(value) {
  const text = String(value || "");
  try {
    return decodeURIComponent(text);
  } catch {
    return text;
  }
}

function formatAdminDebugLogs() {
  if (!state.debug.logs.length && !hasLoadedReferencePackage() && !state.debug.referenceCaptureEnabled && !state.debug.screenObserverEnabled) return "";
  return JSON.stringify({
    exportedAt: new Date().toISOString(),
    version: chrome.runtime.getManifest().version,
    activeTab: {
      id: state.activeTab?.id,
      url: state.activeTab?.url || "",
      title: state.activeTab?.title || ""
    },
    enabled: state.debug.enabled,
    pageProbeEnabled: state.debug.pageProbeEnabled,
    screenObserverEnabled: state.debug.screenObserverEnabled,
    referenceCapture: {
      enabled: state.debug.referenceCaptureEnabled,
      session: state.debug.referenceCaptureSession,
      sessions: state.debug.referenceCaptureSessions,
      package: getEffectiveReferencePackage(),
      rules: getEffectiveReferenceRules()
    },
    screenObserver: stripScreenObserverForExport(state.debug.screenObserver),
    count: state.debug.count,
    logs: state.debug.logs
  }, null, 2);
}

async function openFloatingDesk(view) {
  await refreshActiveTab();
  const tab = state.activeTab;
  if (!tab?.id || !/^https?:\/\//i.test(tab.url || "")) {
    setFeedback("请先切到网页标签页，再打开插件功能。", "error");
    render();
    return;
  }

  try {
    await chrome.tabs.sendMessage(tab.id, { type: "OPEN_FLOATING_DESK", view });
  } catch {
    await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      files: ["floating-desktop.js"]
    });
    await chrome.tabs.sendMessage(tab.id, { type: "OPEN_FLOATING_DESK", view });
  }

  window.close();
}

async function openNativeEcommerceTool() {
  await refreshActiveTab();
  const tab = state.activeTab;
  if (!tab?.id || !/^https?:\/\//i.test(tab.url || "")) {
    setFeedback("请先切到普通网页标签页，再打开电商工具。", "error");
    render();
    return;
  }

  const probe = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    world: "MAIN",
    func: () => Boolean(document.querySelector(".mcjApp"))
  });
  if (probe?.[0]?.result === true) {
    await chrome.tabs.update(tab.id, { active: true });
  } else {
    await chrome.tabs.create({
      url: "https://fxg.jinritemai.com/ffa/mshop/homepage/index",
      active: true
    });
  }
  window.close();
}

async function openUrl(url) {
  const cleanUrl = String(url || "").trim();
  if (!cleanUrl) return;
  const targetUrl = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(cleanUrl)
    ? cleanUrl
    : chrome.runtime.getURL(cleanUrl.replace(/^\/+/, ""));
  await chrome.tabs.create({ url: targetUrl, active: true });
  window.close();
}

async function refreshTutorialNavigation() {
  const response = await chrome.runtime.sendMessage({ type: "GET_TUTORIAL_NAVIGATION" }).catch(() => null);
  const navigation = response?.ok ? (response.tutorialNavigation || {}) : {};
  const url = String(navigation.url || "").trim();
  state.tutorialNavigation = {
    url: /^https?:\/\//i.test(url) ? url : "",
    revision: Number(navigation.revision || 0)
  };
}

async function openAixOptions() {
  const optionsUrl = chrome.runtime.getURL("options/options.html");
  await new Promise((resolve) => {
    const openTab = () => chrome.tabs.create({ url: optionsUrl, active: true }, () => resolve());
    try {
      if (!chrome.runtime.openOptionsPage) {
        openTab();
        return;
      }
      chrome.runtime.openOptionsPage(() => {
        if (chrome.runtime.lastError) openTab();
        else resolve();
      });
    } catch {
      openTab();
    }
  });
  window.close();
}

async function updateSetting(key, checked) {
  if (!Object.prototype.hasOwnProperty.call(STORAGE_KEYS, key)) return;

  if (key === "agentControl") {
    await updateAgentControlSetting(checked);
    return;
  }

  if (key === "compassJump" && checked) {
    const gate = await verifyCompassJumpAccess();
    if (!gate.ok) {
      state.settings.compassJump = false;
      await chrome.storage.local.set({ [STORAGE_KEYS.compassJump]: false }).catch(() => {});
      setFeedback(gate.message, "error");
      render();
      return;
    }
  }

  state.settings[key] = checked;
  await chrome.storage.local.set({ [STORAGE_KEYS[key]]: checked });
  if (key === "douyinDownload") {
    setFeedback(checked ? "DouYin enabled. Refresh the Douyin page if it was already open." : "DouYin disabled.", "success");
    render();
    return;
  }
  setFeedback(
    checked ? "罗盘视频跳转已开启，并同步到悬浮窗和页面小开关。" : "罗盘视频跳转已关闭，并同步到悬浮窗和页面小开关。",
    "success"
  );
  render();
}

async function updateAgentControlSetting(checked) {
  const response = await chrome.runtime.sendMessage({
    type: "MMW_AGENT_CONTROL_SET_ENABLED",
    enabled: checked === true
  }).catch((error) => ({ ok: false, error: error.message || String(error) }));

  if (!response?.ok) {
    state.settings.agentControl = !checked;
    setFeedback(response?.error || "AI 接管切换失败。", "error");
    render();
    return;
  }

  state.settings.agentControl = checked === true;
  state.agentControlStatus = formatAgentControlStatus(response.result || {});
  const agentControlFeedbackText = checked
    ? "\u0041\u0049 \u63a5\u7ba1\u5df2\u5f00\u542f\u3002\u5f53\u524d\u53ea\u662f\u5141\u8bb8\u63a5\u7ba1\uff0c\u7b49\u5230 Agent \u771f\u6b63\u63a5\u5165\u540e\u624d\u4f1a\u663e\u793a\u84dd\u8272\u8fb9\u6846\u3002"
    : "\u0041\u0049 \u63a5\u7ba1\u5df2\u5173\u95ed\u3002\u5916\u90e8 Agent \u53ea\u80fd\u67e5\u8be2\u72b6\u6001\uff0c\u4e0d\u80fd\u7ee7\u7eed\u63a7\u5236\u9875\u9762\u3002";
  setFeedback(agentControlFeedbackText, checked ? "success" : "");
  render();
}

async function copyAgentControlPrompt() {
  try {
    await navigator.clipboard.writeText(AGENT_CONTROL_PROMPT);
    setFeedback("已复制给 Agent 的接管提示词。", "success");
  } catch {
    setFeedback("复制失败，请更新浏览器或手动复制接口文档里的提示。", "error");
  }
  render();
}

async function refreshAgentControlStatus(showFeedback = false) {
  if (!state.legalConsent.accepted) {
    setAgentControlStatus("\u540c\u610f\u534f\u8bae\u540e\u53ef\u68c0\u6d4b\u5f53\u524d\u9875\u9762\u63a5\u7ba1\u901a\u9053");
    render();
    return;
  }

  state.agentControlStatus = {
    text: "\u6b63\u5728\u68c0\u6d4b\u5f53\u524d\u9875\u9762\u63a5\u7ba1\u901a\u9053...",
    kind: "pending",
    loading: true
  };
  render();

  await refreshActiveTab();
  const response = await chrome.runtime.sendMessage({
    type: "MMW_AGENT_CONTROL_GET_STATE",
    tabId: state.activeTab?.id || null
  }).catch((error) => ({
    ok: false,
    error: error.message || String(error)
  }));

  if (!response?.ok) {
    setAgentControlStatus(response?.error || "\u83b7\u53d6\u63a5\u7ba1\u72b6\u6001\u5931\u8d25", "error");
    if (showFeedback) setFeedback(state.agentControlStatus.text, "error");
    render();
    return;
  }

  state.agentControlStatus = formatAgentControlStatus(response.result || {});
  if (showFeedback && state.agentControlStatus.kind === "error") {
    setFeedback(state.agentControlStatus.text, "error");
  }
  render();
}

function formatAgentControlStatus(result) {
  const tabUrl = String(result?.tab?.url || "");
  const host = formatAgentControlHost(result?.page?.url || tabUrl);
  const localBridge = result?.localBridge || {};
  const localBridgeHint = String(result?.protocol?.localBridgeStartHint || localBridge.startHint || "").trim();
  const localBridgeError = String(localBridge.lastError || "").trim();

  if (!result?.tab?.id) {
    return {
      text: "\u672a\u627e\u5230\u53ef\u68c0\u6d4b\u7684\u6d4f\u89c8\u5668\u6807\u7b7e\u9875",
      kind: "error",
      loading: false
    };
  }

  if (!/^https?:\/\//i.test(tabUrl)) {
    return {
      text: "\u5f53\u524d\u6807\u7b7e\u9875\u4e0d\u662f\u53ef\u63a5\u7ba1\u7f51\u9875",
      kind: "error",
      loading: false
    };
  }

  if (result.bridgeReady !== true) {
    return {
      text: result.enabled
        ? `${host || "\u5f53\u524d\u9875\u9762"}\u63a5\u7ba1\u5f00\u5173\u5df2\u5f00\uff0c\u4f46\u63a5\u7ba1\u6865\u8fd8\u6ca1\u6709\u56de\u5305`
        : `${host || "\u5f53\u524d\u9875\u9762"}\u8fd8\u6ca1\u6709\u63a5\u7ba1\u6865\u56de\u5305`,
      kind: "error",
      loading: false
    };
  }

  if (result.enabled !== true) {
    return {
      text: `${host || "\u5f53\u524d\u9875\u9762"}\u5df2\u6ce8\u5165\u63a5\u7ba1\u6865\uff0c\u4f46 AI \u63a5\u7ba1\u5f00\u5173\u672a\u5f00`,
      kind: "",
      loading: false
    };
  }

  if (result.page?.connected === true) {
    return {
      text: `${host || "\u5f53\u524d\u9875\u9762"}\u5df2\u88ab Agent \u63a5\u7ba1\uff0c\u5f53\u524d click/type/scroll/key/move \u8d70\u6d4f\u89c8\u5668\u7ea7\u8f93\u5165\u63a5\u7ba1`,
      kind: "success",
      loading: false
    };
  }

  if (localBridge.connected === true) {
    return {
      text: `${host || "\u5f53\u524d\u9875\u9762"}\u7684\u672c\u5730\u6d4f\u89c8\u5668\u4f1a\u8bdd\u6865\u5df2\u88ab Agent \u63a5\u5165\uff0c\u4f46\u5f53\u524d\u9875\u8fd8\u6ca1\u6536\u5230\u9875\u9762\u52a8\u4f5c\uff0c\u6682\u4e0d\u663e\u793a\u84dd\u8272\u8fb9\u6846`,
      kind: "success",
      loading: false
    };
  }

  if (localBridge.available !== true) {
    const hintText = localBridgeHint ? `\uff0c\u542f\u52a8\uff1a${localBridgeHint}` : "";
    const errorText = localBridgeError ? `\uff0c\u6700\u8fd1\u9519\u8bef\uff1a${localBridgeError}` : "";
    return {
      text: `${host || "\u5f53\u524d\u9875\u9762"}\u9875\u9762\u63a5\u7ba1\u6865\u5df2\u5c31\u7eea\uff0c\u4f46\u672c\u5730\u6d4f\u89c8\u5668\u4f1a\u8bdd\u6865\u672a\u8fde\u901a${hintText}${errorText}`,
      kind: "pending",
      loading: false
    };
  }

  return {
    text: `${host || "\u5f53\u524d\u9875\u9762"} AI \u63a5\u7ba1\u5f00\u5173\u5df2\u5f00\uff0c\u672c\u5730\u4f1a\u8bdd\u6865\u5df2\u8fde\u901a\uff0c\u6d4f\u89c8\u5668\u7ea7\u8f93\u5165\u94fe\u8def\u5df2\u5c31\u7eea\uff0c\u7b49\u5f85 Agent \u771f\u6b63\u63a5\u5165`,
    kind: "pending",
    loading: false
  };
}

function formatAgentControlHost(url) {
  try {
    return new URL(String(url || "")).host;
  } catch {
    return "";
  }
}

function setAgentControlStatus(text, kind = "", loading = false) {
  state.agentControlStatus = {
    text: String(text || "").trim(),
    kind,
    loading
  };
}

async function verifyCompassJumpAccess() {
  const response = await chrome.runtime.sendMessage({ type: "PLUGIN_AUTH_STATUS", refresh: false }).catch(() => null);
  const auth = response?.ok ? response.auth : null;
  if (auth?.loggedIn && auth?.active) {
    return { ok: true };
  }
  return {
    ok: false,
    message: response?.error || auth?.message || auth?.entitlement?.label || "请先登录账号后再开启罗盘视频跳转。"
  };
}

function render() {
  document.querySelector("#statusText").textContent = state.legalConsent.accepted
    ? currentTabText()
    : "请先阅读并同意免责声明和用户协议。";
  const compassToggle = document.querySelector("#compassJumpToggle");
  if (compassToggle) compassToggle.checked = state.settings.compassJump;
  const douyinToggle = document.querySelector("#douyinDownloadToggle");
  if (douyinToggle) douyinToggle.checked = state.settings.douyinDownload;
  const agentControlToggle = document.querySelector("#agentControlToggle");
  if (agentControlToggle) agentControlToggle.checked = state.settings.agentControl;
  const checkAgentControlButton = document.querySelector("#checkAgentControlButton");
  if (checkAgentControlButton) {
    checkAgentControlButton.textContent = state.agentControlStatus.loading
      ? "\u68c0\u6d4b\u4e2d..."
      : "\u68c0\u6d4b\u5f53\u524d\u9875";
  }
  const agentControlStateText = document.querySelector("#agentControlStateText");
  if (agentControlStateText) {
    agentControlStateText.textContent = state.agentControlStatus.text;
    agentControlStateText.className = `agentControlState${state.agentControlStatus.kind ? ` ${state.agentControlStatus.kind}` : ""}`;
  }
  const tutorialButton = document.querySelector("#tutorialNavigationButton");
  if (tutorialButton) {
    tutorialButton.dataset.openLink = state.tutorialNavigation.url;
    tutorialButton.disabled = !state.tutorialNavigation.url;
    tutorialButton.title = state.tutorialNavigation.url ? "打开使用教程" : "管理员尚未配置教程链接";
  }

  renderAdminDebug();
  syncAdminDebugLiveRefresh();

  const feedback = document.querySelector("#feedbackText");
  feedback.textContent = state.feedback.text || (state.legalConsent.accepted
    ? "悬浮窗里的罗盘开关和页面里的“点击视频自动抖音打开”会和这里实时同步。"
    : "未同意协议前，功能按钮已锁定。点击遮罩阅读协议后继续使用。");
  feedback.className = `feedback${state.feedback.kind ? ` ${state.feedback.kind}` : ""}`;
  applyLegalConsentLock();
}

function renderAdminDebug() {
  const panel = document.querySelector("#adminDebugPanel");
  if (!panel) return;
  panel.hidden = !state.debug.admin;
  if (!state.debug.admin) {
    syncAdminDebugLiveRefresh();
    return;
  }
  const rules = getEffectiveReferenceRules();
  const referencePackage = getEffectiveReferencePackage(rules);
  const observer = normalizeScreenObserverForUi(state.debug.screenObserver);
  const latestObserverFrame = normalizeScreenObserverFrameForUi(observer?.latestFrame);

  const meta = document.querySelector("#debugMeta");
  if (meta) {
    const captureText = state.debug.enabled ? "采集中" : "已停用";
    const probeText = state.debug.pageProbeEnabled ? "页面抓包开" : "页面抓包关";
    const observerText = state.debug.screenObserverEnabled ? "画面监听开" : "画面监听关";
    const referenceText = state.debug.referenceCaptureEnabled ? "参考抓包开" : "参考抓包关";
    meta.textContent = `${captureText} · ${probeText} · ${observerText} · ${referenceText} · ${state.debug.count}/${state.debug.maxLogs || "-"} 条`;
  }

  const toggle = document.querySelector("#debugToggleButton");
  if (toggle) toggle.textContent = state.debug.enabled ? "停用采集" : "启用采集";

  const pageProbe = document.querySelector("#debugPageProbeButton");
  if (pageProbe) {
    pageProbe.textContent = state.debug.pageProbeEnabled ? "停用抓包" : "页面抓包";
    pageProbe.classList.toggle("is-active", state.debug.pageProbeEnabled);
  }

  const referenceMeta = document.querySelector("#referenceCaptureMeta");
  if (referenceMeta) {
    const name = referencePackage.name || rules.packageName || "默认规则";
    const countText = rules.scannedFiles ? `${rules.scannedFiles} 文件` : "默认规则";
    referenceMeta.textContent = state.debug.referenceCaptureEnabled
      ? `抓包中 · ${name}`
      : `${name} · ${countText}`;
  }

  const referenceSessionMeta = document.querySelector("#referenceCaptureSessionMeta");
  if (referenceSessionMeta) {
    const activeSession = normalizeReferenceSessionForUi(state.debug.referenceCaptureSession);
    const recentSession = activeSession || normalizeReferenceSessionSummaryForUi(state.debug.referenceCaptureSessions[0]);
    if (activeSession) {
      const observerCount = Number(observer?.frameCount || 0) || 0;
      referenceSessionMeta.textContent = `当前会话：${activeSession.name || activeSession.id} · ${Math.max(0, Number(activeSession.seq || 0) || 0)} 条 · 画面 ${observerCount} 帧`;
    } else if (recentSession?.id) {
      referenceSessionMeta.textContent = `最近会话：${recentSession.name || recentSession.id} · ${Number(recentSession.eventCount || 0) || 0} 条`;
    } else {
      referenceSessionMeta.textContent = "未开始录制会话";
    }
  }

  const screenObserverMeta = document.querySelector("#screenObserverMeta");
  if (screenObserverMeta) {
    if (state.debug.screenObserverEnabled) {
      if (latestObserverFrame?.id) {
        screenObserverMeta.textContent = `已开启 · ${Number(observer?.frameCount || 0) || 0} 帧 · ${latestObserverFrame.reason || "loop"}`;
      } else if (observer?.lastError) {
        screenObserverMeta.textContent = `已开启 · ${observer.lastError}`;
      } else {
        screenObserverMeta.textContent = "已开启 · 等待参考会话画面";
      }
    } else {
      screenObserverMeta.textContent = "未开启";
    }
  }

  const screenObserverToggle = document.querySelector("#screenObserverToggleButton");
  if (screenObserverToggle) {
    screenObserverToggle.classList.toggle("is-active", state.debug.screenObserverEnabled);
    screenObserverToggle.textContent = state.debug.screenObserverEnabled ? "画面监听中" : "开启画面监听";
  }

  const screenObserverRefresh = document.querySelector("#screenObserverRefreshButton");
  if (screenObserverRefresh) {
    screenObserverRefresh.disabled = !state.debug.screenObserverEnabled;
  }

  const screenObserverPreview = document.querySelector("#screenObserverPreview");
  const screenObserverEmpty = document.querySelector("#screenObserverEmpty");
  if (screenObserverPreview && screenObserverEmpty) {
    if (latestObserverFrame?.dataUrl) {
      screenObserverPreview.src = latestObserverFrame.dataUrl;
      screenObserverPreview.hidden = false;
      const timeText = latestObserverFrame.time ? formatReferenceSessionTime(latestObserverFrame.time) : "刚刚";
      screenObserverEmpty.textContent = `最新帧：${timeText} · ${latestObserverFrame.reason || "loop"} · ${latestObserverFrame.title || latestObserverFrame.pageUrl || "当前页面"}`;
    } else {
      screenObserverPreview.hidden = true;
      screenObserverPreview.removeAttribute("src");
      screenObserverEmpty.textContent = state.debug.screenObserverEnabled
        ? (observer?.lastError || "开启后会随参考抓包会话同步记录最近画面帧。")
        : "开启后会随参考抓包会话同步记录最近画面帧。";
    }
  }

  const referenceStart = document.querySelector("#referenceStartButton");
  if (referenceStart) {
    referenceStart.classList.toggle("is-active", state.debug.referenceCaptureEnabled);
    referenceStart.textContent = state.debug.referenceCaptureEnabled ? "参考抓包中" : "开始参考抓包";
    referenceStart.disabled = state.debug.referenceCaptureEnabled;
  }

  const referenceStop = document.querySelector("#referenceStopButton");
  if (referenceStop) referenceStop.disabled = !state.debug.referenceCaptureEnabled;

  const referenceOutput = document.querySelector("#referenceRuleOutput");
  if (referenceOutput) referenceOutput.value = formatReferenceRules();

  const output = document.querySelector("#debugOutput");
  if (output) output.value = formatAdminDebugLogs();
}

function currentTabText() {
  const url = state.activeTab?.url || "";
  if (!/^https?:\/\//i.test(url)) return "请先切到罗盘、抖音或其他网页标签页。";

  try {
    return `当前页面：${new URL(url).host}`;
  } catch {
    return "当前页面已连接。";
  }
}

function buildReferenceCaptureSessionForUi(packageSummary) {
  const pkg = normalizeReferencePackageForUi(packageSummary);
  const now = new Date();
  const label = `${pkg.name || "参考抓包"}${pkg.version ? ` v${pkg.version}` : ""} ${formatReferenceSessionTime(now)}`.trim();
  return {
    name: label,
    packageName: pkg.name || "",
    packageVersion: pkg.version || "",
    startedAt: now.toISOString()
  };
}

function normalizeReferenceSessionForUi(value) {
  const source = value && typeof value === "object" ? value : null;
  if (!source) return null;
  const id = String(source.id || "").trim();
  const name = String(source.name || "").trim();
  if (!id && !name) return null;
  return {
    id,
    name,
    packageName: String(source.packageName || "").trim(),
    packageVersion: String(source.packageVersion || "").trim(),
    startedAt: String(source.startedAt || "").trim(),
    updatedAt: String(source.updatedAt || "").trim(),
    endedAt: String(source.endedAt || "").trim(),
    seq: Math.max(0, Number(source.seq || 0) || 0),
    active: source.active === true
  };
}

function normalizeReferenceSessionSummaryForUi(value) {
  const session = normalizeReferenceSessionForUi(value);
  if (!session) return null;
  return {
    ...session,
    eventCount: Math.max(0, Number(value?.eventCount || 0) || 0),
    tabCount: Math.max(0, Number(value?.tabCount || 0) || 0),
    firstAt: Math.max(0, Number(value?.firstAt || 0) || 0),
    lastAt: Math.max(0, Number(value?.lastAt || 0) || 0),
    tabs: Array.isArray(value?.tabs) ? value.tabs.slice(0, 40) : [],
    sampleEvents: Array.isArray(value?.sampleEvents) ? value.sampleEvents.slice(0, 12) : []
  };
}

function normalizeReferenceSessionsForUi(value) {
  return Array.isArray(value) ? value.map(normalizeReferenceSessionSummaryForUi).filter(Boolean) : [];
}

function formatReferenceSessionTime(value) {
  const date = value instanceof Date ? value : new Date(value || Date.now());
  if (Number.isNaN(date.getTime())) return "会话";
  const pad = (input) => String(input).padStart(2, "0");
  return `${date.getMonth() + 1}-${date.getDate()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}

function normalizeScreenObserverFrameForUi(value) {
  const source = value && typeof value === "object" ? value : null;
  if (!source) return null;
  const id = String(source.id || "").trim();
  const dataUrl = String(source.dataUrl || "").trim();
  if (!id && !dataUrl) return null;
  return {
    id,
    time: Math.max(0, Number(source.time || 0) || 0),
    iso: String(source.iso || "").trim(),
    reason: String(source.reason || "").trim(),
    sessionId: String(source.sessionId || "").trim(),
    tabId: Math.max(0, Number(source.tabId || 0) || 0),
    windowId: Math.max(0, Number(source.windowId || 0) || 0),
    pageUrl: String(source.pageUrl || "").trim(),
    title: String(source.title || "").trim(),
    status: String(source.status || "").trim(),
    mimeType: String(source.mimeType || "image/jpeg").trim(),
    dataUrl,
    dataUrlLength: Math.max(0, Number(source.dataUrlLength || dataUrl.length || 0) || 0)
  };
}

function normalizeScreenObserverForUi(value) {
  const source = value && typeof value === "object" ? value : null;
  if (!source) return null;
  return {
    enabled: source.enabled === true,
    capturing: source.capturing === true,
    sessionId: String(source.sessionId || "").trim(),
    sessionName: String(source.sessionName || "").trim(),
    captureGapMs: Math.max(0, Number(source.captureGapMs || 0) || 0),
    maxFrames: Math.max(0, Number(source.maxFrames || 0) || 0),
    frameCount: Math.max(0, Number(source.frameCount || 0) || 0),
    captureCount: Math.max(0, Number(source.captureCount || 0) || 0),
    lastCaptureAt: Math.max(0, Number(source.lastCaptureAt || 0) || 0),
    lastCaptureReason: String(source.lastCaptureReason || "").trim(),
    lastErrorAt: Math.max(0, Number(source.lastErrorAt || 0) || 0),
    lastError: String(source.lastError || "").trim(),
    latestFrame: normalizeScreenObserverFrameForUi(source.latestFrame),
    frames: Array.isArray(source.frames) ? source.frames.map(normalizeScreenObserverFrameForUi).filter(Boolean) : []
  };
}

function stripScreenObserverForExport(value) {
  const observer = normalizeScreenObserverForUi(value);
  if (!observer) return null;
  const stripFrame = (frame) => frame ? {
    id: frame.id,
    time: frame.time,
    iso: frame.iso,
    reason: frame.reason,
    sessionId: frame.sessionId,
    tabId: frame.tabId,
    windowId: frame.windowId,
    pageUrl: frame.pageUrl,
    title: frame.title,
    status: frame.status,
    mimeType: frame.mimeType,
    dataUrlLength: frame.dataUrlLength
  } : null;
  return {
    enabled: observer.enabled,
    capturing: observer.capturing,
    sessionId: observer.sessionId,
    sessionName: observer.sessionName,
    captureGapMs: observer.captureGapMs,
    maxFrames: observer.maxFrames,
    frameCount: observer.frameCount,
    captureCount: observer.captureCount,
    lastCaptureAt: observer.lastCaptureAt,
    lastCaptureReason: observer.lastCaptureReason,
    lastErrorAt: observer.lastErrorAt,
    lastError: observer.lastError,
    latestFrame: stripFrame(observer.latestFrame),
    frames: observer.frames.map(stripFrame)
  };
}

function setFeedback(text, kind = "") {
  state.feedback = {
    text: String(text || "").trim(),
    kind
  };
}

async function requireLegalConsent() {
  const accepted = await window.MMWLegalConsent?.requireAccepted?.();
  if (accepted) return true;
  setFeedback(window.MMWLegalConsent?.REQUIRED_MESSAGE || "请先同意用户协议。", "error");
  render();
  return false;
}

function applyLegalConsentLock() {
  const locked = !state.legalConsent.accepted;
  document.body.classList.toggle("is-consent-pending", locked);
  const mask = document.querySelector("#consentMask");
  if (mask) mask.hidden = !locked;
  document.querySelectorAll(".popup button, .popup input, .popup textarea").forEach((control) => {
    if (control.closest("#legalConsentDialog")) return;
    if (control.matches("#consentMask")) return;
    control.disabled = locked;
  });
}
