const MEDIA_EXTENSIONS = new Set([
  "3g2", "3gp", "aac", "apng", "avif", "avi", "bmp", "flac", "gif", "jpeg", "jpg",
  "m4a", "m4s", "m4v", "mkv", "mov", "mp3", "mp4", "mpeg", "mpd", "m3u8", "oga",
  "ogg", "ogv", "png", "svg", "ts", "wav", "webm", "webp"
]);
const PLAYLIST_EXTENSIONS = new Set(["m3u8", "mpd"]);
const PAGE_EXTENSIONS = new Set(["asp", "aspx", "htm", "html", "php", "shtml", "xhtml"]);
const VIDEO_KINDS = new Set(["video", "playlist"]);
const TRUSTED_MEDIA_HOSTS = /(^|\.)((douyinvod|douyinpic|bytev|pstatp|ixigua|snssdk|365yg)\.com|douyinstatic\.com)$/i;
const MAX_ITEMS_PER_TAB = 800;
const DOWNLOAD_RULE_ID_BASE = 42000;
const DOWNLOAD_RULE_TTL_MS = 8000;
const CARD_META_TTL_MS = 8000;
const CARD_META_PATCH_WINDOW_MS = 8000;
const PROBE_SAMPLE_BYTES = 262144;
const PROBE_HEAD_TIMEOUT_MS = 3500;
const PROBE_SAMPLE_TIMEOUT_MS = 5000;
const CHROME_DOWNLOAD_START_TIMEOUT_MS = 15000;
const REFERER_RULE_ID_MAIN = 41001;
const REFERER_RULE_ID_BACKUP = 41002;
const DOWNLOADER_RULE_ID_BASE = 51000;
const DOWNLOADER_RULE_ID_RANGE = 10000;
const BACKUP_REQUEST_PREFIX = "backupDownloadRequest_";
const BACKUP_REQUEST_TTL_MS = 10 * 60 * 1000;
const DEFAULT_API_BASE_URL = "https://mmwgzt.xyz";
try {
  importScripts("legal-config.js");
} catch (_) {}
try {
  importScripts("features/source-trace/largest-source-selector.js");
} catch (_) {}
try {
  importScripts("features/feature-access/plugin-feature-access.js");
} catch (_) {}
try {
  importScripts("features/backend-connectivity/backend-connectivity-ui.js");
} catch (_) {}
try {
  importScripts("features/qianchuan-agent/qianchuan-dispatch-runtime.js");
} catch (_) {}
const LEGAL_CONFIG = globalThis.MMW_LEGAL_CONSENT_CONFIG || {};
const ACCOUNT_SECURITY_AGREEMENT_VERSION = String(LEGAL_CONFIG.version || "");
const AUTH_REFRESH_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000;
const PLUGIN_HEARTBEAT_ALARM = "pluginActivityHeartbeat";
const PLUGIN_HEARTBEAT_PERIOD_MINUTES = 1;
const PLUGIN_HEARTBEAT_MIN_GAP_MS = 45 * 1000;
const QIANCHUAN_REMOTE_PULSE_ALARM = "qianchuanAgentRemotePulse";
const QIANCHUAN_REMOTE_PULSE_PERIOD_MINUTES = 0.5;
const QIANCHUAN_REMOTE_PULSE_INTERVAL_MS = 4000;
const QIANCHUAN_AGENT_CONTENT_SCRIPTS = [
  "features/qianchuan-agent/qianchuan-page-context.js",
  "features/qianchuan-agent/qianchuan-readonly-observer.js",
  "features/qianchuan-agent/qianchuan-remote-control.js",
  "features/qianchuan-agent/qianchuan-operation-state-machine.js",
  "features/qianchuan-agent/qianchuan-takeover-controller.js",
  "features/qianchuan-agent/qianchuan-action-executor.js",
  "features/qianchuan-agent/qianchuan-agent-planner.js",
  "features/qianchuan-agent/qianchuan-monitor-rules.js",
  "features/qianchuan-agent/qianchuan-plan-control-cooldown.js",
  "features/qianchuan-agent/qianchuan-workflow-template.js",
  "features/qianchuan-agent/qianchuan-workflow-runtime.js",
  "features/qianchuan-agent/qianchuan-personal-preferences.js",
  "features/qianchuan-agent/qianchuan-task-patrol.js",
  "features/qianchuan-agent/qianchuan-command-confirmation.js",
  "features/qianchuan-agent/qianchuan-usage-notice.js",
  "features/qianchuan-agent/qianchuan-agent.js"
];
const QIANCHUAN_AGENT_CONTENT_CSS = ["features/qianchuan-agent/qianchuan-agent.css"];
const qianchuanAgentReattachTabs = new Set();
const CURRENT_PLUGIN_VERSION = chrome.runtime.getManifest().version;
const qianchuanDispatchRuntime = createQianchuanDispatchRuntime();

function createQianchuanDispatchRuntime() {
  try {
    return globalThis.MMWQianchuanDispatchRuntime?.createRuntime?.({
      storage: chrome.storage.local,
      storageOnChanged: chrome.storage.onChanged,
      authStorageKey: "pluginAuth",
      crypto: globalThis.crypto,
      fetch: globalThis.fetch.bind(globalThis),
      extensionId: chrome.runtime.id,
      commanderEnabled: false,
      getGate: getQianchuanDispatchGate,
      isAuthUsable,
      audit: (event, detail) => recordAdminDebugEvent(`qianchuan-dispatch-${event}`, detail, "warn")
    }) || null;
  } catch {
    return null;
  }
}

async function getQianchuanDispatchGate() {
  let auth = await getPluginAuthState({ refresh: false });
  if (!isAuthUsable(auth)) return { ok: false, auth };
  if (Date.now() - Number(auth.checkedAt || 0) >= 55_000) {
    try {
      auth = await refreshPluginAuth(auth);
    } catch {
      return { ok: false, auth: normalizeAuthState(null) };
    }
  }
  return { ok: isAuthUsable(auth), auth };
}

chrome.storage.session.setAccessLevel({
  accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS"
}).catch((error) => {
  console.warn("电商工具会话存储授权失败", error);
});

const PENDING_PAGE_TASKS_KEY = "pendingPageTasks";
const OFFSCREEN_DOCUMENT_URL = "offscreen.html";
const SHOP_MOVE_AI_PUBLISH_URL = "https://fxg.jinritemai.com/ffa/g/create";
const SHOP_MOVE_AI_MODEL_HOOK_FILE = "features/shop-move/publish-model-hook.js";
const SHOP_MOVE_AI_PUBLISH_SKILL_FILE = "features/shop-move/publish-skill.js";
const SHOP_MOVE_AI_RUNNER_FILE = "features/shop-move/ai-publish-runner.js";
const SHOP_MOVE_SOURCE_CAPTURE_MAIN_FILE = "features/shop-move/source-capture-main.js";
const SHOP_MOVE_SOURCE_CAPTURE_BRIDGE_FILE = "features/shop-move/source-capture-bridge.js";
const SHOP_MOVE_SOURCE_CAPTURE_MATCHES = [
  "https://haohuo.jinritemai.com/*",
  "https://fxg.jinritemai.com/*",
  "https://buyin.jinritemai.com/*",
  "https://www.douyin.com/*",
  "https://douyin.com/*",
  "https://*.douyin.com/*",
  "https://iesdouyin.com/*",
  "https://*.iesdouyin.com/*"
];
const SHOP_MOVE_PARSER_HTML_LIMIT = 2 * 1024 * 1024;
const SHOP_MOVE_RENDER_PARSE_TIMEOUT_MS = 45000;
const SHOP_MOVE_RENDER_PARSE_MIN_WAIT_MS = 6500;
const SHOP_MOVE_PRODUCT_PARSE_TIMEOUT_MS = 70000;
const SHOP_MOVE_SOURCE_CAPTURE_MAX_RECORDS = 120;
const SHOP_MOVE_SOURCE_CAPTURE_BODY_LIMIT = 900 * 1024;
const SHOP_MOVE_IMAGE_PROXY_MAX_BYTES = 8 * 1024 * 1024;
const SHOP_MOVE_MAIN_IMAGE_LIMIT = 5;
const SHOP_MOVE_DETAIL_IMAGE_LIMIT = 60;
const SHOP_MOVE_PARSE_RESULT_CACHE_TTL_MS = 10 * 60 * 1000;
const SHOP_MOVE_DOUYIN_SKU_API_URL = "https://live.douyin.com/aweme/v1/web/ecom/product/sku/list/";
const SHOP_MOVE_DOUYIN_SKU_RULE_ID = 44080;
const ECOMMERCE_TOOL_WORKBENCH_URL = "https://fxg.jinritemai.com/ffa/mshop/homepage/index";
const ECOMMERCE_TOOL_AI_SCENES = new Set([
  "ai-chat",
  "chat",
  "douyin-category",
  "search-word",
  "ai-image"
]);
const ECOMMERCE_LOCAL_ACTIONS = new Set(["image_md5", "image_watermark", "image_frame"]);
const ECOMMERCE_MOVE_FEATURE_KEYS = new Set([
  "toPinduoduo", "toTaobao", "toTmall", "toTgc", "toDouyin", "toJingdong",
  "toXiaohongshu", "toXianyu", "to1688", "toKuaishou", "toWeidian", "toDewu",
  "toTemu", "toAlibaba", "toSMT", "toShopee", "toTikTok", "toOzon",
  "batchToPinduoduo", "batchToTaobao", "batchToTmall", "batchToTgc",
  "batchToDouyin", "batchToJingdong", "batchToXiaohongshu", "batchToXianyu",
  "batchTo1688", "batchToKuaishou", "batchToWeidian", "batchToDewu",
  "batchToTemu", "batchToAlibaba", "batchToSMT", "batchToShopee",
  "batchToTikTok", "batchToOzon"
]);
const ECOMMERCE_MOVE_MAX_LINKS = 500;
const ECOMMERCE_CLOUD_ENDPOINTS = new Set([
  "/api/ecommerce/product-cache",
  "/api/ecommerce/product-cache/list",
  "/api/ecommerce/product-cache/upsert",
  "/api/ecommerce/product-cache/delete",
  "/api/ecommerce/move-history",
  "/api/ecommerce/move-history/list",
  "/api/ecommerce/move-history/upsert",
  "/api/ecommerce/move-history/delete",
  "/api/ecommerce/category-mappings",
  "/api/ecommerce/category-mappings/resolve",
  "/api/ecommerce/category-mappings/upsert",
  "/api/ecommerce/category-mappings/confirm",
  "/api/ecommerce/category-mappings/delete",
  "/api/ecommerce/attribute-mappings",
  "/api/ecommerce/attribute-mappings/resolve",
  "/api/ecommerce/attribute-mappings/upsert",
  "/api/ecommerce/attribute-mappings/confirm",
  "/api/ecommerce/attribute-mappings/delete"
]);
const AGENT_BROWSER_CONTROL_SCRIPT = "browser-control.js";
const AGENT_BROWSER_CONTROL_STORAGE_KEY = "mmwAgentBrowserControlEnabled";
const AGENT_BROWSER_BRIDGE_CLIENT_ID_STORAGE_KEY = "mmwAgentBrowserBridgeClientId";
const AGENT_BROWSER_LOCAL_BRIDGE_ORIGIN = "http://127.0.0.1:18766";
const AGENT_BROWSER_LOCAL_BRIDGE_BASE_URL = "http://127.0.0.1:18766/v1/agent-browser";
const AGENT_BROWSER_LOCAL_BRIDGE_START_HINT = "desktop-tauri\\python_bridge\\start_agent_browser_bridge.bat";
const AGENT_BROWSER_LOCAL_BRIDGE_REQUEST_TIMEOUT_MS = 8000;
const AGENT_BROWSER_LOCAL_BRIDGE_POLL_TIMEOUT_MS = 25000;
const AGENT_BROWSER_LOCAL_BRIDGE_CONNECTED_IDLE_MS = 20000;
const AGENT_BROWSER_LOCAL_BRIDGE_RETRY_MS = 2500;
const AGENT_BROWSER_LOCAL_BRIDGE_TAB_LIMIT = 40;
const AGENT_BROWSER_DEBUGGER_PROTOCOL_VERSION = "1.3";
const ADMIN_REFERENCE_CAPTURE_ENABLED_KEY = "adminDebugReferenceCaptureEnabled";
const ADMIN_SCREEN_OBSERVER_ENABLED_KEY = "adminDebugScreenObserverEnabled";
const ADMIN_SCREEN_OBSERVER_MAX_FRAMES = 12;
const ADMIN_SCREEN_OBSERVER_CAPTURE_GAP_MS = 1800;
const ADMIN_REFERENCE_NETWORK_META_LIMIT = 1600;
const ADMIN_REFERENCE_NETWORK_HEADER_LIMIT = 24;
const ADMIN_REFERENCE_NETWORK_KEYWORDS = [
  "dangxun",
  "dxcdn",
  "hsq",
  "husky",
  "qianchuan",
  "jinritemai",
  "oceanengine",
  "uni-prom",
  "overall",
  "eval_code_url",
  "registeredUserScript",
  "userScripts"
];
const AGENT_BROWSER_LOW_LEVEL_ACTIONS = new Set(["click", "type", "scroll", "key", "move"]);
const AGENT_BROWSER_CONTENT_ACTIONS = new Set(["state", "snapshot", "click", "type", "scroll", "key", "move", "highlight", "wait"]);
const AGENT_BROWSER_BACKGROUND_ACTIONS = new Set([
  "navigate",
  "back",
  "forward",
  "reload",
  "screenshot",
  "new_tab",
  "open_tab",
  "list_tabs",
  "activate_tab",
  "close_tab"
]);
const AGENT_BROWSER_LOCAL_ONLY_ACTIONS = new Set([
  "plugin_status",
  "plugin_visual_state",
  "plugin_logs",
  "plugin_logs_clear",
  "plugin_reference_capture_start",
  "plugin_reference_capture_stop"
]);

globalThis.MMWRequireEcommerceToolFeatureAccess = requireEcommerceToolFeatureAccess;

importScripts("admin-debug.js");
importScripts("features/ecommerce-original/sync-outbox.js");
importScripts("features/ecommerce-flow-recorder/recorder-core.js");
importScripts("features/ecommerce-flow-recorder/recorder-background.js");
importScripts("features/ecommerce-original/background-adapter.js");
importScripts("dist/background/original-commerce-runtime.js");

const BUILTIN_RULES = [
  {
    id: "baiying_douyin",
    name: "选品广场",
    domain: "buyin.jinritemai.com/dashboard/merch-picking-library",
    selector: "#rc-tabs-0-panel-content > div > div:nth-of-type(2) > div:nth-of-type(2) > div > div > div > div:nth-of-type(6) > div > div:nth-of-type(1) > div > div:nth-of-type(2) > div",
    builtin: true
  }
];

const STORAGE_KEYS = {
  compassJump: "compassJumpEnabled",
  douyinDownload: "douyinDetailDownloadEnabled",
  activation: "activationInfo",
  entitlement: "pluginEntitlement",
  auth: "pluginAuth",
  apiBaseUrl: "pluginApiBaseUrl",
  deviceId: "pluginDeviceId",
  agentControl: AGENT_BROWSER_CONTROL_STORAGE_KEY,
  titleRules: "titleRules",
  settingsVersion: "settingsVersion"
};
const REMOTE_LOGIN_CODE = "remote_login";
const REMOTE_LOGIN_MESSAGE = "该账号异地登录，若非本人确认请及时修改密码";
const REMOTE_LOGIN_EVENT = "MMW_ACCOUNT_REMOTE_LOGIN";
const REMOTE_LOGIN_STORAGE_KEY = "mmwAccountRemoteLogin";
const DEFAULT_SETTINGS_VERSION = "2.10.93";
const LOGIN_GATED_MESSAGE_TYPES = new Set([
  "DOWNLOAD_SELECTED",
  "FLOATING_DOWNLOAD_SELECTED",
  "DOWNLOAD_VIDEO_TITLE",
  "SOURCE_TRACE",
  "SOURCE_TRACE_BATCH_DOWNLOAD",
  "SHOP_MOVE_BOOTSTRAP",
  "SHOP_MOVE_SAVE_CONFIG",
  "SHOP_MOVE_CAPTURE_CURRENT_SHOP",
  "SHOP_MOVE_CAPTURE_SHOP_TEMPLATES",
  "SHOP_MOVE_PARSE_PRODUCTS",
  "SHOP_MOVE_CREATE_JOB",
  "SHOP_MOVE_LIST_JOBS",
  "SHOP_MOVE_LIST_RECORDS",
  "SHOP_MOVE_OPEN_SUPPORT_RECORDS",
  "SHOP_MOVE_AI_PUBLISH_START",
  "VD_FETCH_VIDEO",
  "VD_FETCH_VIDEO_SAFE",
  "VD_CREATE_BACKUP_DOWNLOAD"
]);
const MEMBER_GATED_MESSAGE_TYPES = new Set([
  "START_TITLE_SELECTOR"
]);

const tabResources = new Map();
const tabFingerprints = new Map();
const tabActiveCardMeta = new Map();
const requestHeadersById = new Map();
const requestCardMetaById = new Map();
const referenceNetworkRequestMetaById = new Map();
const shopMoveAiPublishTasks = new Map();
const shopMoveSourceCaptureRecords = new Map();
const shopMoveRenderedParseTabs = new Map();
const shopMoveParseResultCache = new Map();
const compassCaptureCache = new Map();
const referenceCaptureTabMeta = new Map();
let pluginHeartbeatPromise = null;
let pluginHeartbeatLastAttemptAt = 0;
let qianchuanRemotePulseTimer = 0;
let offscreenDocumentPromise = null;
const agentBrowserLocalBridgeState = {
  clientId: "",
  running: false,
  available: false,
  registered: false,
  connected: false,
  lastSeenAt: 0,
  lastCommandAt: 0,
  lastCommand: "",
  lastErrorAt: 0,
  lastError: ""
};
let agentBrowserLocalBridgeLoopPromise = null;
let agentBrowserLocalBridgeLoopId = 0;
let agentBrowserLocalBridgeAbortController = null;
const AGENT_BROWSER_RUNTIME_LOG_LIMIT = 400;
const AGENT_BROWSER_RUNTIME_LOG_DEFAULT_LIMIT = 120;
const agentBrowserRuntimeLogs = [];
const adminScreenObserverState = {
  loaded: false,
  enabled: false,
  inFlight: false,
  timer: 0,
  sessionId: "",
  sessionName: "",
  captureCount: 0,
  lastCaptureAt: 0,
  lastCaptureReason: "",
  lastErrorAt: 0,
  lastError: "",
  frames: []
};

function ensureAdminScreenObserverLoop() {
  isAdminScreenObserverEnabled(true)
    .then((enabled) => {
      adminScreenObserverState.enabled = enabled === true;
      if (enabled) scheduleAdminScreenObserverLoop(900);
      else clearAdminScreenObserverTimer();
    })
    .catch(() => {});
}

ensureDefaults()
  .then(() => restoreAgentBrowserControlOpenTabs().catch(() => {}))
  .then(() => ensureAgentBrowserLocalBridgeLoop().catch(() => {}))
  .then(() => ensureAdminScreenObserverLoop())
  .catch(() => {});
ensurePluginHeartbeatAlarm().catch(() => {});
ensureQianchuanRemotePulse().catch(() => {});
chrome.runtime.onInstalled.addListener(() => {
  ensureDefaults()
    .then(() => restoreAgentBrowserControlOpenTabs().catch(() => {}))
    .then(() => ensureAgentBrowserLocalBridgeLoop().catch(() => {}))
    .then(() => ensureAdminScreenObserverLoop())
    .catch(() => {});
  ensurePluginHeartbeatAlarm().catch(() => {});
  ensureQianchuanRemotePulse().catch(() => {});
});
chrome.runtime.onStartup?.addListener(() => {
  ensurePluginHeartbeatAlarm().catch(() => {});
  ensureQianchuanRemotePulse().catch(() => {});
  restoreAgentBrowserControlOpenTabs().catch(() => {});
  ensureAgentBrowserLocalBridgeLoop().catch(() => {});
  ensureAdminScreenObserverLoop();
});
chrome.alarms?.onAlarm?.addListener((alarm) => {
  if (alarm?.name === PLUGIN_HEARTBEAT_ALARM) {
    sendPluginHeartbeat().catch(() => {});
  }
  if (alarm?.name === QIANCHUAN_REMOTE_PULSE_ALARM) {
    pulseQianchuanAgentTabs().catch(() => {});
  }
});

chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    recordReferenceNetworkEvent("reference-network-request", details).catch(() => {});
  },
  { urls: ["<all_urls>"] },
  ["requestBody"]
);

chrome.webRequest.onSendHeaders.addListener(
  (details) => {
    trackReferenceNetworkHeaders(details);
    if (details.tabId < 0 || !details.requestId || !details.url || isIgnoredScheme(details.url)) {
      return;
    }
    const headers = pickRequestHeaders(details.requestHeaders || []);
    if (headers) {
      if (requestHeadersById.size > 2000) requestHeadersById.clear();
      requestHeadersById.set(details.requestId, headers);
    }
    const cardMeta = getRecentCardMeta(details.tabId, "");
    if (shouldCaptureRequestCardMeta(details, cardMeta)) {
      if (requestCardMetaById.size > 2000) requestCardMetaById.clear();
      requestCardMetaById.set(details.requestId, cardMeta);
    }
  },
  { urls: ["<all_urls>"] },
  ["requestHeaders", "extraHeaders"]
);

chrome.webRequest.onResponseStarted.addListener(
  async (details) => {
    recordReferenceNetworkEvent("reference-network-response", details).catch(() => {});
    if (details.tabId < 0 || !details.url || isIgnoredScheme(details.url)) {
      return;
    }
    const headers = requestHeadersById.get(details.requestId);
    const requestCardMeta = requestCardMetaById.get(details.requestId);
    requestHeadersById.delete(details.requestId);
    requestCardMetaById.delete(details.requestId);
    const parsed = classifyResource(details.url, details.responseHeaders, details.type);
    if (!parsed) {
      return;
    }
    const tab = await chrome.tabs.get(details.tabId).catch(() => null);
    const recentCardMeta = getRecentCardMeta(details.tabId, tab?.url || "");
    const cardMeta = VIDEO_KINDS.has(parsed.kind)
      ? requestCardMeta || pickRecentCardMetaForResponse(details.url, recentCardMeta)
      : {};
    addResource(details.tabId, {
      ...parsed,
      id: makeId(details.url, details.requestId),
      url: details.url,
      source: "network",
      ...cardMeta,
      pageTitle: tab?.title || "",
      pageUrl: tab?.url || "",
      requestHeaders: headers || fallbackRequestHeaders(tab?.url),
      time: Date.now()
    });
  },
  { urls: ["<all_urls>"] },
  ["responseHeaders"]
);

chrome.webRequest.onErrorOccurred.addListener(
  (details) => {
    recordReferenceNetworkEvent("reference-network-error", details).catch(() => {});
    referenceNetworkRequestMetaById.delete(details.requestId);
    requestHeadersById.delete(details.requestId);
    requestCardMetaById.delete(details.requestId);
  },
  { urls: ["<all_urls>"] }
);

chrome.tabs.onCreated.addListener((tab) => {
  updateReferenceCaptureTabMeta(tab.id, tab);
  recordReferenceTabEvent("reference-tab-created", tab.id, tab).catch(() => {});
  captureAdminScreenObserverFrame("tab-created", tab.id, null, { force: true }).catch(() => {});
});

chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
  const parseMeta = shopMoveRenderedParseTabs.get(tabId);
  if (parseMeta) {
    parseMeta.closed = true;
    parseMeta.closedAt = Date.now();
    parseMeta.stage = parseMeta.stage || "closed";
    recordAdminDebugEvent("shop-move-render-tab-removed", {
      ...summarizeShopMoveParseTabRuntime(parseMeta, tabId),
      reason: "tab_removed",
      window_id: removeInfo?.windowId || 0,
      is_window_closing: removeInfo?.isWindowClosing === true
    }, "warn");
  }
  const tabMeta = referenceCaptureTabMeta.get(tabId) || {};
  recordReferenceTabEvent("reference-tab-removed", tabId, tabMeta).catch(() => {});
  referenceCaptureTabMeta.delete(tabId);
  tabResources.delete(tabId);
  tabFingerprints.delete(tabId);
  tabActiveCardMeta.delete(tabId);
  shopMoveSourceCaptureRecords.delete(tabId);
  clearPendingPageTasksForTab(tabId).catch(() => {});
});

chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
  if (changeInfo.url || changeInfo.title || changeInfo.status) {
    updateReferenceCaptureTabMeta(tabId, changeInfo);
    if (changeInfo.url || changeInfo.status === "loading" || changeInfo.status === "complete") {
      recordReferenceTabEvent("reference-tab-updated", tabId, {
        ...referenceCaptureTabMeta.get(tabId),
        changeInfo
      }).catch(() => {});
      captureAdminScreenObserverFrame(
        changeInfo.status === "complete" ? "tab-complete" : "tab-updated",
        tabId,
        null,
        { force: changeInfo.status === "complete" }
      ).catch(() => {});
    }
  }
  const parseMeta = shopMoveRenderedParseTabs.get(tabId);
  if (parseMeta) {
    if (changeInfo.url) parseMeta.lastTabUrl = changeInfo.url;
    if (changeInfo.status) parseMeta.lastTabStatus = changeInfo.status;
    if (changeInfo.title) parseMeta.lastTabTitle = changeInfo.title;
    if (changeInfo.url || changeInfo.title || changeInfo.status) {
      parseMeta.lastTabChangeAt = Date.now();
    }
    if (changeInfo.url || changeInfo.status === "loading" || changeInfo.status === "complete") {
      recordAdminDebugEvent("shop-move-render-tab-updated", {
        ...summarizeShopMoveParseTabRuntime(parseMeta, tabId),
        change: {
          url: changeInfo.url || "",
          status: changeInfo.status || "",
          title: changeInfo.title || ""
        }
      });
    }
  }
  if (changeInfo.status === "loading") {
    tabResources.delete(tabId);
    tabFingerprints.delete(tabId);
    tabActiveCardMeta.delete(tabId);
    if (!shopMoveRenderedParseTabs.has(tabId)) {
      shopMoveSourceCaptureRecords.delete(tabId);
    }
  }
  if (changeInfo.url || changeInfo.status === "complete") {
    ensureAgentBrowserControlForTab(tabId).catch(() => {});
  }
});

chrome.tabs.onActivated?.addListener((activeInfo) => {
  if (activeInfo?.tabId) {
    ensureAgentBrowserControlForTab(activeInfo.tabId).catch(() => {});
    captureAdminScreenObserverFrame("tab-activated", activeInfo.tabId, null, { force: true }).catch(() => {});
  }
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (handleCompatMessage(message, sender, sendResponse)) {
    return true;
  }

  if (message?.action === "MMW_ORIGINAL_PARSE_PRODUCT") {
    const url = String(message.url || message.links || message.text || "").trim();
    requestShopMoveParseProducts({
      links: url,
      config: isObject(message.config) ? message.config : {}
    })
      .then((data) => sendResponse({ success: true, data }))
      .catch((error) => sendResponse({
        success: false,
        error: error.message || "商品信息解析失败"
      }));
    return true;
  }

  if (!message || typeof message.type !== "string") {
    return false;
  }

  if (handleAuthMessage(message, sendResponse)) {
    return true;
  }

  if (message?.type === "PLUGIN_FEATURE_CHECK") {
    const featureKey = String(message.featureKey || message.feature_key || "").trim();
    getPluginAuthState({ refresh: true })
      .then((auth) => sendResponse({
        ok: true,
        allowed: hasPluginFeatureAccess(auth, featureKey),
        reason: pluginFeatureDeniedReason(auth, featureKey),
        featureKey
      }))
      .catch((error) => sendResponse({
        ok: false,
        allowed: false,
        error: error.message || "插件功能权限检查失败"
      }));
    return true;
  }

  if (handleAdminDebugMessage(message, sender, sendResponse)) {
    return true;
  }

  if (message.type === "ECOMMERCE_TOOL_STATUS") {
    getEcommerceToolStatusPayload()
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取电商工具状态失败" }));
    return true;
  }

  if (message.type === "ECOMMERCE_TOOL_AI_REQUEST") {
    requestEcommerceToolAi(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "电商工具 AI 调用失败",
        authRequired: error.authRequired === true,
        auth: error.auth
      }));
    return true;
  }

  if (message.type === "MMW_ECOMMERCE_BACKGROUND" && message.action === "cloud_request") {
    requestNativeEcommerceCloud(message.payload || {})
      .then((data) => sendResponse({ ok: true, data }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "电商云端业务请求失败",
        authRequired: error.authRequired === true,
        auth: error.auth
      }));
    return true;
  }

  if (message.type === "MMW_ECOMMERCE_BACKGROUND" && message.action === "ai_request") {
    const payload = isObject(message.payload) ? message.payload : {};
    requestEcommerceToolAi({
      scene: payload.scene || payload.tool || payload.feature || "ai-chat",
      prompt: payload.prompt || payload.text || payload.message,
      payload: isObject(payload.payload) ? payload.payload : payload
    })
      .then((data) => sendResponse({ ok: true, data }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "电商 AI 请求失败",
        authRequired: error.authRequired === true,
        auth: error.auth
      }));
    return true;
  }

  if (message.type === "ECOMMERCE_LOCAL_ACTION_STATUS") {
    getEcommerceLocalActionStatus()
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "获取本地图片计费状态失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "ECOMMERCE_LOCAL_ACTION_CONSUME") {
    consumeEcommerceLocalAction(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "本地图片功能结算失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "ECOMMERCE_MOVE_CONSUME") {
    withEcommerceMoveTimeout(consumeEcommerceMove(message), 18000)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: normalizeEcommerceMoveErrorMessage(error),
        authRequired: error.authRequired === true,
        auth: error.auth
      }));
    return true;
  }

  if (message.type === "COMPASS_JUMP_EXECUTE") {
    executeCompassJumpTask(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "罗盘视频跳转授权失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "COMPASS_CAPTURE_RESOLVE") {
    resolveCompassCapture(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "罗盘视频识别失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "QIANCHUAN_BILLING_REQUEST") {
    requestQianchuanBilling(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "千川投放会员或计费校验失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_ACCESS") {
    requestQianchuanAgentAccess()
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch(() => sendResponse({ ok: true, payload: { allowed: false } }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_STATUS") {
    requestQianchuanAgentStatus(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川 Agent 状态读取失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_REAL_CLICK") {
    executeQianchuanAgentRealClick(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川页面真实点击失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_REAL_ACTION") {
    executeQianchuanAgentRealAction(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川页面真实操作失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_PLAN_STEP") {
    requestQianchuanAgentStepPlan(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川单步规划失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_READONLY_OBSERVE") {
    requestQianchuanAgentReadonlyObservation(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "千川只读画面识别失败",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_CHAT") {
    requestQianchuanAgentChat(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({
        ok: false,
        error: error.message || "千川 Agent 暂时无法回复",
        authRequired: error.authRequired === true
      }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_STATE") {
    requestQianchuanAgentCloudState(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "训练记忆读取失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_MEMORY") {
    requestQianchuanAgentMemory(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "训练记忆保存失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_AUDIT") {
    requestQianchuanAgentAudit(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "操作记录同步失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_SETTINGS") {
    requestQianchuanAgentSettings(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "托管设置保存失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_SKILL") {
    requestQianchuanAgentSkill(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "投放技能设置保存失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_PREFERENCES") {
    requestQianchuanAgentPreferences(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "个人习惯保存失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_MONITOR_RULE") {
    requestQianchuanAgentMonitorRule(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "盯盘规则更新失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_CONVERSATION") {
    requestQianchuanAgentConversation(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川会话同步失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_WEIXIN") {
    requestQianchuanAgentWeixin(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "微信通知操作失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_WORKER") {
    requestQianchuanAgentWorker(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川页面注册失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_DATA_REPORT") {
    requestQianchuanAgentDataReport(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川数据通知入队失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_AGENT_NOTIFICATION") {
    requestQianchuanAgentNotification(message, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "千川提醒入队失败" }));
    return true;
  }

  if (message.type === "QIANCHUAN_DISPATCH_OBSERVE") {
    if (!qianchuanDispatchRuntime) {
      sendResponse({ ok: false, error: "千川 dispatch transport 未加载" });
      return false;
    }
    qianchuanDispatchRuntime.observe({
      accountKey: message.accountKey,
      accountName: message.accountName,
      scope: message.scope,
      pageSupported: message.pageSupported === true
    }, sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch(() => sendResponse({ ok: false, error: "千川 dispatch transport 暂不可用" }));
    return true;
  }

  if (message.type === "SHOP_MOVE_SOURCE_CAPTURE_RECORD") {
    requireEcommerceToolFeatureAccess()
      .then(() => {
        storeShopMoveSourceCaptureRecord(sender, message.record);
        sendResponse({ ok: true });
      })
      .catch((error) => sendResponse({ ok: false, error: error.message || "电商工具未开放" }));
    return true;
  }

  if (message.type === "SHOP_MOVE_FETCH_IMAGE_DATA_URL") {
    requireEcommerceToolFeatureAccess()
      .then(() => fetchShopMoveImageDataUrl(message.url))
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "图片读取失败" }));
    return true;
  }

  if (message.type === "SHOP_MOVE_UPLOAD_IMAGES_IN_MAIN") {
    requireEcommerceToolFeatureAccess()
      .then(() => uploadShopMoveImagesInMainWorld(sender, message.urls || message.images || [], message.maxCount))
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "图片上传失败" }));
    return true;
  }

  if (LOGIN_GATED_MESSAGE_TYPES.has(message.type) || MEMBER_GATED_MESSAGE_TYPES.has(message.type)) {
    const gatePromise = MEMBER_GATED_MESSAGE_TYPES.has(message.type) ? requireActiveAuth() : requireLoginAuth();
    gatePromise.then((gate) => {
      if (!gate.ok) {
        sendResponse({
          ok: false,
          authRequired: true,
          error: gate.message,
          auth: sanitizeAuthForUi(gate.auth)
        });
        return;
      }
      dispatchMessage(message, sender, sendResponse);
    });
    return true;
  }

  return dispatchMessage(message, sender, sendResponse);
});

function updateReferenceCaptureTabMeta(tabId, tab = {}) {
  if (!Number.isInteger(tabId)) return;
  const previous = referenceCaptureTabMeta.get(tabId) || {};
  referenceCaptureTabMeta.set(tabId, {
    tabId,
    windowId: Number.isInteger(tab.windowId) ? tab.windowId : previous.windowId,
    openerTabId: Number.isInteger(tab.openerTabId) ? tab.openerTabId : previous.openerTabId,
    url: tab.url || tab.pendingUrl || previous.url || "",
    title: tab.title || previous.title || "",
    status: tab.status || previous.status || "",
    lastSeenAt: Date.now()
  });
}

async function isReferenceCaptureEnabled() {
  const values = await chrome.storage.local.get([ADMIN_REFERENCE_CAPTURE_ENABLED_KEY]).catch(() => ({}));
  return values[ADMIN_REFERENCE_CAPTURE_ENABLED_KEY] === true;
}

function trackReferenceNetworkHeaders(details = {}) {
  if (!details?.requestId || !details?.url || isIgnoredScheme(details.url)) return;
  if (!isLikelyReferenceNetworkUrl(details)) return;
  if (referenceNetworkRequestMetaById.size > 1600) referenceNetworkRequestMetaById.clear();
  const previous = referenceNetworkRequestMetaById.get(details.requestId) || {};
  referenceNetworkRequestMetaById.set(details.requestId, {
    ...previous,
    requestHeaders: summarizeWebRequestHeaders(details.requestHeaders || [])
  });
}

async function recordReferenceNetworkEvent(event, details = {}) {
  if (!details?.requestId || !details?.url || isIgnoredScheme(details.url)) return;
  if (!isLikelyReferenceNetworkUrl(details)) return;
  if (!await isReferenceCaptureEnabled()) return;
  const status = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  if (status?.referenceCaptureEnabled !== true) return;
  const match = matchReferenceNetworkCapture(details, status.referenceCaptureRules || {});
  if (!match.matched) return;

  const tab = Number.isInteger(details.tabId) && details.tabId >= 0
    ? await chrome.tabs.get(details.tabId).catch(() => null)
    : null;
  const storedMeta = referenceNetworkRequestMetaById.get(details.requestId) || {};
  if (event !== "reference-network-request") referenceNetworkRequestMetaById.delete(details.requestId);
  const payload = {
    requestId: String(details.requestId || ""),
    method: String(details.method || "GET").toUpperCase(),
    url: truncateAdminReferenceNetworkText(details.url),
    type: String(details.type || ""),
    tabId: Number.isInteger(details.tabId) ? details.tabId : undefined,
    frameId: Number.isInteger(details.frameId) ? details.frameId : undefined,
    parentFrameId: Number.isInteger(details.parentFrameId) ? details.parentFrameId : undefined,
    initiator: truncateAdminReferenceNetworkText(details.initiator || ""),
    documentUrl: truncateAdminReferenceNetworkText(details.documentUrl || ""),
    originUrl: truncateAdminReferenceNetworkText(details.originUrl || ""),
    pageUrl: truncateAdminReferenceNetworkText(tab?.url || details.documentUrl || details.initiator || ""),
    pageTitle: truncateAdminReferenceNetworkText(tab?.title || ""),
    statusCode: Number.isFinite(Number(details.statusCode)) ? Number(details.statusCode) : undefined,
    statusLine: truncateAdminReferenceNetworkText(details.statusLine || ""),
    ip: truncateAdminReferenceNetworkText(details.ip || ""),
    fromCache: details.fromCache === true,
    error: truncateAdminReferenceNetworkText(details.error || ""),
    requestBody: summarizeWebRequestBody(details.requestBody),
    requestHeaders: storedMeta.requestHeaders || summarizeWebRequestHeaders(details.requestHeaders || []),
    responseHeaders: summarizeWebRequestHeaders(details.responseHeaders || []),
    referenceMatch: match
  };
  const entry = {
    source: "background",
    event,
    level: event === "reference-network-error" ? "warn" : "info",
    tabId: Number.isInteger(details.tabId) && details.tabId >= 0 ? details.tabId : undefined,
    pageUrl: payload.pageUrl,
    session: status.referenceCaptureSession || null,
    payload
  };
  pushAgentBrowserRuntimeLog(entry);
  await MMW_ADMIN_DEBUG.add(entry, { force: true });
}

function matchReferenceNetworkCapture(details = {}, rules = {}) {
  const url = String(details.url || "");
  const initiator = String(details.initiator || "");
  const documentUrl = String(details.documentUrl || "");
  const originUrl = String(details.originUrl || "");
  const text = `${url}\n${initiator}\n${documentUrl}\n${originUrl}`.toLowerCase();
  const hits = [];
  collectReferenceNetworkHits("builtin", ADMIN_REFERENCE_NETWORK_KEYWORDS, text, hits);
  collectReferenceNetworkHits("url", rules.urlKeywords, text, hits);
  collectReferenceNetworkHits("manifest", rules.manifestMatches, text, hits);
  if (!hits.length && /\.m?js(?:[?#]|$)/i.test(url) && /dangxun|dxcdn|hsq|qianchuan|jinritemai|oceanengine/i.test(text)) {
    hits.push({ type: "script", term: "dynamic-js" });
  }
  return {
    matched: hits.length > 0,
    hits: hits.slice(0, 16)
  };
}

function isLikelyReferenceNetworkUrl(details = {}) {
  const value = `${details.url || ""}\n${details.initiator || ""}\n${details.documentUrl || ""}\n${details.originUrl || ""}`.toLowerCase();
  return ADMIN_REFERENCE_NETWORK_KEYWORDS.some((term) => value.includes(String(term).toLowerCase()));
}

function collectReferenceNetworkHits(type, terms, text, hits) {
  if (!Array.isArray(terms) || !text) return;
  for (const term of terms) {
    const needle = String(term || "").trim().toLowerCase();
    if (!needle) continue;
    const normalizedNeedle = needle.replace(/^\*:\/\/|\*|\/*$/g, "");
    if (!normalizedNeedle) continue;
    if (text.includes(normalizedNeedle)) hits.push({ type, term: String(term).slice(0, 160) });
    if (hits.length >= 20) return;
  }
}

function summarizeWebRequestHeaders(headers = []) {
  if (!Array.isArray(headers) || !headers.length) return [];
  return headers.slice(0, ADMIN_REFERENCE_NETWORK_HEADER_LIMIT).map((header) => {
    const name = String(header?.name || "").slice(0, 120);
    const lowerName = name.toLowerCase();
    const sensitive = /token|authorization|cookie|password|passwd|secret|session|csrf|credential|access[_-]?key|refresh[_-]?key/i.test(lowerName);
    return {
      name,
      value: sensitive ? "[redacted]" : truncateAdminReferenceNetworkText(header?.value || "")
    };
  });
}

function summarizeWebRequestBody(requestBody) {
  if (!requestBody) return null;
  if (requestBody.error) return { error: truncateAdminReferenceNetworkText(requestBody.error) };
  if (requestBody.formData && typeof requestBody.formData === "object") {
    const formData = {};
    for (const [key, values] of Object.entries(requestBody.formData).slice(0, 80)) {
      const sensitive = /token|authorization|cookie|password|passwd|secret|session|csrf|credential|access[_-]?key|refresh[_-]?key/i.test(key);
      formData[key] = sensitive
        ? "[redacted]"
        : (Array.isArray(values) ? values.slice(0, 6).map(truncateAdminReferenceNetworkText) : truncateAdminReferenceNetworkText(values));
    }
    return { type: "formData", value: formData };
  }
  if (Array.isArray(requestBody.raw) && requestBody.raw.length) {
    return {
      type: "raw",
      parts: requestBody.raw.slice(0, 8).map((part) => ({
        bytes: part?.bytes?.byteLength || part?.bytes?.length || 0,
        file: truncateAdminReferenceNetworkText(part?.file || "")
      }))
    };
  }
  return null;
}

function truncateAdminReferenceNetworkText(value) {
  const text = String(value || "");
  return text.length > ADMIN_REFERENCE_NETWORK_META_LIMIT
    ? `${text.slice(0, ADMIN_REFERENCE_NETWORK_META_LIMIT)}...[truncated ${text.length - ADMIN_REFERENCE_NETWORK_META_LIMIT}]`
    : text;
}

async function recordReferenceTabEvent(event, tabId, tab = {}) {
  if (!await isReferenceCaptureEnabled()) return;
  const changeInfo = tab.changeInfo && typeof tab.changeInfo === "object" ? tab.changeInfo : {};
  const entry = {
    source: "background",
    event,
    level: "info",
    tabId,
    pageUrl: tab.url || changeInfo.url || "",
    payload: {
      tabId,
      windowId: tab.windowId,
      openerTabId: tab.openerTabId,
      status: tab.status || changeInfo.status || "",
      url: tab.url || changeInfo.url || "",
      title: tab.title || "",
      changed: {
        url: changeInfo.url || "",
        status: changeInfo.status || "",
        title: changeInfo.title || ""
      }
    }
  };
  pushAgentBrowserRuntimeLog(entry);
  await MMW_ADMIN_DEBUG.add(entry, { force: true });
}

async function isAdminScreenObserverEnabled(refresh = false) {
  if (!refresh && adminScreenObserverState.loaded) return adminScreenObserverState.enabled === true;
  const values = await chrome.storage.local.get([ADMIN_SCREEN_OBSERVER_ENABLED_KEY]).catch(() => ({}));
  adminScreenObserverState.loaded = true;
  adminScreenObserverState.enabled = values[ADMIN_SCREEN_OBSERVER_ENABLED_KEY] === true;
  return adminScreenObserverState.enabled === true;
}

function clearAdminScreenObserverTimer() {
  if (!adminScreenObserverState.timer) return;
  clearTimeout(adminScreenObserverState.timer);
  adminScreenObserverState.timer = 0;
}

function resetAdminScreenObserverSession(session) {
  const sessionId = String(session?.id || "").trim();
  if (!sessionId || adminScreenObserverState.sessionId === sessionId) return;
  adminScreenObserverState.sessionId = sessionId;
  adminScreenObserverState.sessionName = String(session?.name || "").trim();
  adminScreenObserverState.captureCount = 0;
  adminScreenObserverState.lastCaptureAt = 0;
  adminScreenObserverState.lastCaptureReason = "";
  adminScreenObserverState.lastErrorAt = 0;
  adminScreenObserverState.lastError = "";
  adminScreenObserverState.frames = [];
}

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

function getAdminScreenObserverVisualState(options = {}) {
  const frameLimit = Math.max(1, Math.min(ADMIN_SCREEN_OBSERVER_MAX_FRAMES, Number(options.limit) || 6));
  const includeLatestDataUrl = options.includeLatestDataUrl !== false;
  const latest = adminScreenObserverState.frames.length
    ? adminScreenObserverState.frames[adminScreenObserverState.frames.length - 1]
    : null;
  return {
    enabled: adminScreenObserverState.enabled === true,
    capturing: adminScreenObserverState.enabled === true && adminScreenObserverState.inFlight === true,
    sessionId: adminScreenObserverState.sessionId,
    sessionName: adminScreenObserverState.sessionName,
    captureGapMs: ADMIN_SCREEN_OBSERVER_CAPTURE_GAP_MS,
    maxFrames: ADMIN_SCREEN_OBSERVER_MAX_FRAMES,
    frameCount: adminScreenObserverState.frames.length,
    captureCount: adminScreenObserverState.captureCount,
    lastCaptureAt: adminScreenObserverState.lastCaptureAt,
    lastCaptureReason: adminScreenObserverState.lastCaptureReason,
    lastErrorAt: adminScreenObserverState.lastErrorAt,
    lastError: adminScreenObserverState.lastError,
    latestFrame: summarizeAdminScreenObserverFrame(latest, { includeDataUrl: includeLatestDataUrl }),
    frames: adminScreenObserverState.frames
      .slice(-frameLimit)
      .map((frame) => summarizeAdminScreenObserverFrame(frame, { includeDataUrl: options.includeFrameData === true }))
  };
}

async function logAdminScreenObserverEvent(event, payload = {}, level = "info") {
  const entry = {
    source: "background",
    event,
    level,
    tabId: Number(payload.tabId) || undefined,
    pageUrl: String(payload.pageUrl || "").trim(),
    payload
  };
  pushAgentBrowserRuntimeLog(entry);
  await MMW_ADMIN_DEBUG.add(entry, { force: true });
}

async function resolveAdminScreenObserverTargetTab(preferredTabId = null, adminStatus = null) {
  const tried = new Set();
  const tryTab = async (value) => {
    const numericTabId = Number(value);
    if (!Number.isInteger(numericTabId) || numericTabId <= 0 || tried.has(numericTabId)) return null;
    tried.add(numericTabId);
    const tab = await chrome.tabs.get(numericTabId).catch(() => null);
    if (!tab?.id) return null;
    const url = String(tab.url || tab.pendingUrl || "").trim();
    if (!/^https?:\/\//i.test(url)) return null;
    return tab;
  };

  let tab = await tryTab(preferredTabId);
  if (tab?.id && tab.active) return tab;

  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
  if (activeTab?.id) {
    const activeMeta = referenceCaptureTabMeta.get(activeTab.id);
    if (activeMeta || /^https?:\/\//i.test(String(activeTab.url || ""))) {
      return activeTab;
    }
  }

  const activeSessionId = String(adminStatus?.referenceCaptureSession?.id || "").trim();
  const sessionSummary = Array.isArray(adminStatus?.referenceCaptureSessions)
    ? adminStatus.referenceCaptureSessions.find((item) => String(item?.id || "").trim() === activeSessionId)
    : null;
  const sessionTabs = Array.isArray(sessionSummary?.tabs) ? sessionSummary.tabs : [];
  for (const tabId of sessionTabs) {
    tab = await tryTab(tabId);
    if (tab?.id) return tab;
  }

  const metas = Array.from(referenceCaptureTabMeta.values())
    .sort((left, right) => (Number(right?.lastSeenAt || 0) || 0) - (Number(left?.lastSeenAt || 0) || 0));
  for (const meta of metas) {
    tab = await tryTab(meta?.tabId);
    if (tab?.id) return tab;
  }
  return null;
}

async function captureAdminScreenObserverFrame(reason = "loop", preferredTabId = null, adminStatus = null, options = {}) {
  if (!await isAdminScreenObserverEnabled()) return null;
  const status = adminStatus || await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  const session = status?.referenceCaptureEnabled === true ? status?.referenceCaptureSession : null;
  if (!session?.id) return null;
  const force = options.force === true;
  const now = Date.now();
  if (!force && adminScreenObserverState.inFlight) return null;
  if (!force && adminScreenObserverState.lastCaptureAt && now - adminScreenObserverState.lastCaptureAt < ADMIN_SCREEN_OBSERVER_CAPTURE_GAP_MS) {
    return null;
  }
  resetAdminScreenObserverSession(session);
  const tab = await resolveAdminScreenObserverTargetTab(preferredTabId, status);
  if (!tab?.id || !Number.isInteger(tab.windowId)) return null;

  adminScreenObserverState.inFlight = true;
  try {
    const dataUrl = await chromeTabCallback(
      (done) => chrome.tabs.captureVisibleTab(tab.windowId, { format: "jpeg", quality: 45 }, done),
      "画面监听截图失败"
    );
    const frameTime = Date.now();
    const tabMeta = referenceCaptureTabMeta.get(tab.id) || {};
    const frame = {
      id: `observer_${frameTime}_${Math.random().toString(36).slice(2, 8)}`,
      time: frameTime,
      iso: new Date(frameTime).toISOString(),
      reason: String(reason || "loop").trim(),
      sessionId: String(session.id || "").trim(),
      tabId: tab.id,
      windowId: tab.windowId,
      pageUrl: String(tab.url || tab.pendingUrl || tabMeta.url || "").trim(),
      title: String(tab.title || tabMeta.title || "").trim(),
      status: String(tab.status || tabMeta.status || "").trim(),
      mimeType: "image/jpeg",
      dataUrl
    };
    adminScreenObserverState.frames.push(frame);
    if (adminScreenObserverState.frames.length > ADMIN_SCREEN_OBSERVER_MAX_FRAMES) {
      adminScreenObserverState.frames.splice(0, adminScreenObserverState.frames.length - ADMIN_SCREEN_OBSERVER_MAX_FRAMES);
    }
    adminScreenObserverState.captureCount += 1;
    adminScreenObserverState.lastCaptureAt = frameTime;
    adminScreenObserverState.lastCaptureReason = frame.reason;
    adminScreenObserverState.lastError = "";
    adminScreenObserverState.lastErrorAt = 0;
    await logAdminScreenObserverEvent("reference-screen-frame", {
      frameId: frame.id,
      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.dataUrl.length
    });
    return frame;
  } catch (error) {
    adminScreenObserverState.lastError = error.message || "画面监听截图失败";
    adminScreenObserverState.lastErrorAt = Date.now();
    await logAdminScreenObserverEvent("reference-screen-frame-error", {
      reason: String(reason || "loop").trim(),
      sessionId: String(session.id || "").trim(),
      tabId: tab.id,
      windowId: tab.windowId,
      pageUrl: String(tab.url || tab.pendingUrl || "").trim(),
      error: adminScreenObserverState.lastError
    }, "warn");
    return null;
  } finally {
    adminScreenObserverState.inFlight = false;
  }
}

function scheduleAdminScreenObserverLoop(delayMs = ADMIN_SCREEN_OBSERVER_CAPTURE_GAP_MS) {
  clearAdminScreenObserverTimer();
  if (adminScreenObserverState.enabled !== true) return;
  adminScreenObserverState.timer = setTimeout(() => {
    adminScreenObserverState.timer = 0;
    runAdminScreenObserverLoop().catch(() => {});
  }, Math.max(250, Number(delayMs) || ADMIN_SCREEN_OBSERVER_CAPTURE_GAP_MS));
}

async function runAdminScreenObserverLoop() {
  if (!await isAdminScreenObserverEnabled()) {
    clearAdminScreenObserverTimer();
    return;
  }
  const adminStatus = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  if (adminStatus?.referenceCaptureEnabled === true && adminStatus?.referenceCaptureSession?.id) {
    await captureAdminScreenObserverFrame("loop", null, adminStatus).catch(() => {});
  }
  scheduleAdminScreenObserverLoop();
}

async function setAdminScreenObserverEnabled(enabled) {
  const cleanEnabled = enabled === true;
  await chrome.storage.local.set({ [ADMIN_SCREEN_OBSERVER_ENABLED_KEY]: cleanEnabled }).catch(() => {});
  adminScreenObserverState.loaded = true;
  adminScreenObserverState.enabled = cleanEnabled;
  if (!cleanEnabled) {
    clearAdminScreenObserverTimer();
  } else {
    scheduleAdminScreenObserverLoop(600);
  }
  return getAdminScreenObserverVisualState({ includeLatestDataUrl: true });
}

async function requireEcommerceLocalActionAuth() {
  return requireEcommerceToolFeatureAccess();
}

async function getEcommerceLocalActionStatus() {
  const gate = await requireEcommerceLocalActionAuth();
  const response = await requestJson(
    gate.auth.baseUrl,
    "GET",
    "/api/ecommerce-tool/remaining",
    null,
    gate.auth.token,
    10000
  );
  const payload = unwrapPayload(response);
  if (!isObject(payload?.local_actions)) throw new Error("服务端未返回本地图片计费状态");
  return payload.local_actions;
}

async function consumeEcommerceLocalAction(message = {}) {
  const gate = await requireEcommerceLocalActionAuth();
  const action = String(message.action || message.feature || message.feature_key || "").trim();
  if (!ECOMMERCE_LOCAL_ACTIONS.has(action)) throw new Error("不支持的本地图片功能");
  const requestId = String(
    message.request_id
      || message.requestId
      || globalThis.crypto?.randomUUID?.()
      || `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
  ).trim();
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/ecommerce-tool/local-action/consume",
    { action, request_id: requestId },
    gate.auth.token,
    15000
  );
  const payload = unwrapPayload(response);
  return payload;
}

async function consumeEcommerceMove(message = {}) {
  const gate = await requireEcommerceToolFeatureAccess({ active: true });
  const featureKey = String(message.feature_key || message.featureKey || "").trim();
  if (!ECOMMERCE_MOVE_FEATURE_KEYS.has(featureKey)) throw new Error("不支持的搬家功能");
  const links = (Array.isArray(message.links) ? message.links : [])
    .map((link) => String(link || "").trim())
    .filter(Boolean);
  const isBatch = featureKey.startsWith("batchTo");
  if (isBatch && !links.length) throw new Error("批量搬家缺少链接清单，请更新插件后重试");
  if (!isBatch && links.length > 1) throw new Error("单品搬家每次只能提交 1 条链接");
  const linkCount = isBatch ? links.length : 1;
  if (linkCount > ECOMMERCE_MOVE_MAX_LINKS) {
    throw new Error(`单次最多提交 ${ECOMMERCE_MOVE_MAX_LINKS} 条搬家链接，请分批操作`);
  }
  const reportedCount = Number(message.link_count ?? message.linkCount ?? linkCount);
  if (!Number.isInteger(reportedCount) || reportedCount !== linkCount) {
    throw new Error("搬家链接数量与链接清单不一致，请重新提交");
  }
  const requestId = String(
    message.request_id
      || message.requestId
      || globalThis.crypto?.randomUUID?.()
      || `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
  ).trim();
  const device = await getPluginDeviceInfo();
  try {
    const response = await requestJson(
      gate.auth.baseUrl,
      "POST",
      "/api/ecommerce-tool/move/consume",
      {
        feature_key: featureKey,
        request_id: requestId,
        link_count: linkCount,
        links,
        device_id: device.deviceId,
        device_name: device.deviceName
      },
      gate.auth.token,
      15000
    );
    const payload = unwrapPayload(response);
    return payload;
  } catch (error) {
    if (/会员|登录|账号|授权|到期|绑定设备/.test(String(error?.message || ""))) {
      error.authRequired = true;
      error.auth = sanitizeAuthForUi(gate.auth);
    }
    throw error;
  }
}

function withEcommerceMoveTimeout(promise, timeoutMs = 18000) {
  let timer;
  const timeout = new Promise((_, reject) => {
    timer = setTimeout(
      () => reject(new Error("搬家功能校验超时，请重新加载插件后重试")),
      Math.max(1000, Number(timeoutMs) || 18000)
    );
  });
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}

function normalizeEcommerceMoveErrorMessage(error) {
  const message = String(error?.message || error || "").trim();
  if (/HTTP\s*404|404\s*Not\s*Found|not\s*found|页面未找到/i.test(message)) {
    return "服务器搬家计费接口尚未更新，请先更新服务器后重试";
  }
  return message || "搬家功能计费校验失败";
}

async function requestQianchuanBilling(message = {}, sender = null) {
  const gate = await requireLoginAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "千川投放仅限有效会员使用");
    error.authRequired = true;
    throw error;
  }
  const action = String(message.action || "status").trim().toLowerCase();
  if (!new Set(["status", "authorize", "report"]).has(action)) {
    throw new Error("不支持的千川投放计费操作");
  }
  if (action === "authorize") {
    requirePaidTaskSenderHost(sender, new Set(["qianchuan.jinritemai.com"]));
    return authorizePaidTask({
      auth: gate.auth,
      taskId: message.task_id || message.taskId || newPaidTaskId("qc-plan"),
      feature: "qianchuan_full_domain",
      operation: "create_plan",
      taskPayload: message.task_payload || message.taskPayload
    });
  }
  if (action === "report") {
    requirePaidTaskSenderHost(sender, new Set(["qianchuan.jinritemai.com"]));
    return reportPaidTask({
      auth: gate.auth,
      taskId: message.task_id || message.taskId,
      requestDigest: message.request_digest || message.requestDigest,
      resultStatus: message.result_status || message.resultStatus,
      resultPayload: message.result_payload || message.resultPayload || {}
    });
  }
  const body = { action };
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/billing",
    body,
    gate.auth.token,
    15000
  );
  const payload = unwrapPayload(response);
  return payload;
}

async function executeCompassJumpTask(message = {}, sender = null) {
  requirePaidTaskSenderHost(sender, new Set([
    "buyin.jinritemai.com",
    "compass.jinritemai.com",
    "cc.oceanengine.com"
  ]));
  const gate = await requireLoginAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "罗盘跳转仅限有效会员使用");
    error.authRequired = true;
    throw error;
  }
  const pageUrl = new URL(String(sender?.tab?.url || sender?.url || ""));
  const authorization = await authorizePaidTask({
    auth: gate.auth,
    taskId: message.task_id || message.taskId || newPaidTaskId("compass"),
    feature: "compass_jump",
    operation: "open_video",
    taskPayload: {
      video_id: String(message.video_id || message.videoId || "").trim(),
      page_host: pageUrl.hostname.toLowerCase()
    }
  });
  const targetUrl = new URL(String(authorization?.action?.target_url || ""));
  if (targetUrl.protocol !== "https:" || targetUrl.hostname !== "www.douyin.com" || !/^\/video\/[0-9]{5,32}\/?$/.test(targetUrl.pathname)) {
    throw new Error("服务端返回的罗盘跳转目标无效");
  }
  await createAuthorizedTab(targetUrl.href, sender?.tab?.id);
  return {
    task_id: authorization.task_id,
    request_digest: authorization.request_digest,
    authorized_at: authorization.authorized_at
  };
}

async function resolveCompassCapture(message = {}, sender = null) {
  requirePaidTaskSenderHost(sender, new Set([
    "buyin.jinritemai.com",
    "compass.jinritemai.com",
    "cc.oceanengine.com"
  ]));
  const gate = await requireLoginAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "罗盘跳转仅限有效会员使用");
    error.authRequired = true;
    throw error;
  }
  const requestUrl = String(message.request_url || message.requestUrl || "").trim();
  const leaves = Array.isArray(message.leaves) ? message.leaves.slice(0, 800) : [];
  const cacheKey = await paidTaskDigest({
    auth_token: String(gate.auth.token || ""),
    request_url: requestUrl,
    leaves
  });
  const cached = compassCaptureCache.get(cacheKey);
  if (cached && cached.expiresAt > Date.now()) return cached.payload;
  const device = await getPluginDeviceInfo();
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/compass/resolve",
    {
      request_url: requestUrl,
      leaves,
      device_id: device.deviceId,
      device_name: device.deviceName,
      plugin_version: CURRENT_PLUGIN_VERSION
    },
    gate.auth.token,
    12000
  );
  const payload = unwrapPayload(response);
  const ttlMs = Math.max(15, Math.min(300, Number(payload?.cache_ttl_seconds || 120))) * 1000;
  compassCaptureCache.set(cacheKey, { payload, expiresAt: Date.now() + ttlMs });
  if (compassCaptureCache.size > 160) {
    const now = Date.now();
    for (const [key, entry] of compassCaptureCache) {
      if (entry.expiresAt <= now || compassCaptureCache.size > 120) compassCaptureCache.delete(key);
    }
  }
  return payload;
}

async function authorizePaidTask({ auth, taskId, feature, operation, taskPayload }) {
  const device = await getPluginDeviceInfo();
  const requestDigest = await paidTaskDigest(taskPayload);
  const response = await requestJson(
    auth.baseUrl,
    "POST",
    "/api/plugin/task-authorizations",
    {
      action: "authorize",
      task_id: String(taskId || "").trim(),
      feature: String(feature || "").trim(),
      operation: String(operation || "").trim(),
      device_id: device.deviceId,
      device_name: device.deviceName,
      plugin_version: CURRENT_PLUGIN_VERSION,
      request_digest: requestDigest,
      task_payload: taskPayload
    },
    auth.token,
    15000
  );
  const payload = unwrapPayload(response);
  if (String(payload?.request_digest || "") !== requestDigest) {
    throw new Error("服务端任务摘要校验失败，已停止执行");
  }
  if (Number(payload?.expires_at || 0) * 1000 <= Date.now()) {
    throw new Error("任务授权已过期，请重试");
  }
  if (payload?.prepared_result_digest) {
    const preparedResultJson = String(payload?.prepared_result_json || "");
    const preparedDigest = preparedResultJson
      ? await paidTaskTextDigest(preparedResultJson)
      : await paidTaskDigest(payload.action || {});
    if (String(payload.prepared_result_digest) !== preparedDigest) {
      throw new Error("服务端计划结果摘要校验失败，已停止执行");
    }
    if (preparedResultJson) {
      try {
        const action = JSON.parse(preparedResultJson);
        if (!action || typeof action !== "object" || Array.isArray(action)) throw new Error("invalid action");
        payload.action = action;
      } catch {
        throw new Error("服务端计划结果格式无效，已停止执行");
      }
    }
  }
  return payload;
}

async function reportPaidTask({ auth, taskId, requestDigest, resultStatus, resultPayload }) {
  const device = await getPluginDeviceInfo();
  const response = await requestJson(
    auth.baseUrl,
    "POST",
    "/api/plugin/task-authorizations",
    {
      action: "report",
      task_id: String(taskId || "").trim(),
      device_id: device.deviceId,
      device_name: device.deviceName,
      plugin_version: CURRENT_PLUGIN_VERSION,
      request_digest: String(requestDigest || "").trim(),
      result_status: String(resultStatus || "").trim(),
      result_digest: await paidTaskDigest(resultPayload)
    },
    auth.token,
    10000
  );
  return unwrapPayload(response);
}

function requirePaidTaskSenderHost(sender, allowedHosts) {
  let host = "";
  try {
    host = new URL(String(sender?.tab?.url || sender?.url || "")).hostname.toLowerCase();
  } catch {}
  if (!host || !allowedHosts.has(host)) throw new Error("付费任务来源页面无效");
  return host;
}

function newPaidTaskId(prefix) {
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2, 14)}`;
  return `${String(prefix || "task").replace(/[^A-Za-z0-9_.:-]/g, "-")}-${nonce}`;
}

async function paidTaskDigest(value) {
  return paidTaskTextDigest(stableTaskJson(value));
}

async function paidTaskTextDigest(value) {
  const bytes = new TextEncoder().encode(String(value ?? ""));
  const digest = await crypto.subtle.digest("SHA-256", bytes);
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
}

function stableTaskJson(value) {
  if (value === null) return "null";
  if (Array.isArray(value)) return `[${value.map((item) => stableTaskJson(item)).join(",")}]`;
  if (typeof value === "object") {
    const entries = Object.keys(value)
      .filter((key) => value[key] !== undefined && typeof value[key] !== "function")
      .sort()
      .map((key) => `${JSON.stringify(key)}:${stableTaskJson(value[key])}`);
    return `{${entries.join(",")}}`;
  }
  if (typeof value === "number" && !Number.isFinite(value)) throw new Error("任务请求包含无效数字");
  return JSON.stringify(value);
}

function createAuthorizedTab(url, openerTabId) {
  return new Promise((resolve, reject) => {
    const options = { url, active: true };
    if (Number.isInteger(openerTabId) && openerTabId > 0) options.openerTabId = openerTabId;
    chrome.tabs.create(options, (tab) => {
      if (chrome.runtime.lastError || !tab) {
        reject(new Error(chrome.runtime.lastError?.message || "授权跳转页打开失败"));
        return;
      }
      resolve(tab);
    });
  });
}

const QIANCHUAN_AGENT_SUPPORTED_SCOPES = new Set([
  "overall_promotion",
  "live_room",
  "multiplier_product",
  "multiplier_live_room"
]);

function normalizeQianchuanAgentScope(value) {
  const scope = String(value || "").trim().toLowerCase();
  if (!QIANCHUAN_AGENT_SUPPORTED_SCOPES.has(scope)) {
    throw new Error("当前仅支持全域投放和乘方的推商品、推直播间页面");
  }
  return scope;
}

function normalizeQianchuanAgentAccountKey(value) {
  const accountKey = String(value || "").trim().slice(0, 128);
  if (!/^[A-Za-z0-9_.:@-]+$/.test(accountKey)) {
    throw new Error("未识别到千川投放账号，请先进入全域投放或乘方的推商品、推直播间页面");
  }
  return accountKey;
}

async function requestQianchuanAgentStatus() {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const [local, mobileSync] = await Promise.all([
    qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/status", { method: "GET", timeoutMs: 4000 }),
    qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/mobile-sync/status", { method: "GET", timeoutMs: 4000 })
      .catch((error) => ({ enabled: false, connected: false, lastError: error?.message || "小程序同步状态不可用" }))
  ]);
  return {
    ...local,
    loggedIn: true,
    miniapp: {
      enabled: mobileSync?.enabled === true,
      connected: mobileSync?.connected === true,
      lastSyncAt: Math.max(0, Number(mobileSync?.lastSyncAt) || 0),
      lastError: String(mobileSync?.lastError || "").slice(0, 180)
    }
  };
}

async function requestQianchuanAgentCloudState(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const accountKey = normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key);
  const scope = normalizeQianchuanAgentScope(message.scope);
  const query = new URLSearchParams({ account_key: accountKey, scope });
  const latestUserMessage = Array.isArray(message.messages)
    ? [...message.messages].reverse().find((item) => item && item.role === "user" && item.content)
    : null;
  if (latestUserMessage) query.set("query", String(latestUserMessage.content).slice(0, 1200));
  const response = await requestJson(
    gate.auth.baseUrl,
    "GET",
    `/api/plugin/qianchuan/agent-state?${query}`,
    null,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentChat(message = {}) {
  const state = await requestQianchuanAgentCloudState(message);
  const accountKey = normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key);
  const scope = normalizeQianchuanAgentScope(message.scope);
  const latestUserText = Array.isArray(message.messages)
    ? String([...message.messages].reverse().find((item) => item?.role === "user")?.content || "")
    : "";
  const operationRequest = /暂停|恢复|开启|打开|定位|调整|修改|预算|roi|切换|进入|违规管理|确认执行|全托管|ai\s*接管/i.test(latestUserText);
  return qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/chat", {
    method: "POST",
    timeoutMs: operationRequest ? 190000 : 100000,
    body: {
      accountKey,
      scope,
      sessionKey: String(message.sessionKey || message.session_key || accountKey).slice(0, 96),
      messages: Array.isArray(message.messages) ? message.messages.slice(-12) : [],
      context: isObject(message.context) ? message.context : {},
      skillContext: isObject(state?.skills?.context) ? state.skills.context : {},
      preferenceContext: isObject(state?.preferences?.context) ? state.preferences.context : {},
      source: String(message.source || "plugin").slice(0, 24),
      externalMessageId: String(message.externalMessageId || "").slice(0, 128),
      modelId: String(message.modelId || "").slice(0, 160),
      fallbackFromModelId: String(message.fallbackFromModelId || "").slice(0, 160),
      persistConversation: message.persistConversation !== false
    }
  });
}

async function requestQianchuanAgentStepPlan(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "千川 Agent 仅限有效会员使用");
    error.authRequired = true;
    throw error;
  }
  const visualDataUrl = String(message.visualDataUrl || "");
  const body = {
    request_id: String(message.requestId || message.request_id || "").slice(0, 128),
    accountKey: normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key),
    scope: normalizeQianchuanAgentScope(message.scope),
    task: isObject(message.task) ? message.task : {},
    observation: isObject(message.observation) ? message.observation : {},
    allowedSteps: Array.isArray(message.allowedSteps) ? message.allowedSteps.slice(0, 8) : [],
    visualDataUrl: /^data:image\/jpeg;base64,[A-Za-z0-9+/=]+$/.test(visualDataUrl) && visualDataUrl.length <= 1200000
      ? visualDataUrl
      : ""
  };
  try {
    const response = await requestJson(
      gate.auth.baseUrl,
      "POST",
      "/api/plugin/qianchuan/agent-plan-step",
      body,
      gate.auth.token,
      45000,
      { notifyMaintenance: false }
    );
    refreshPluginAuth(gate.auth).catch(() => {});
    return { ...unwrapPayload(response), transport: "backend_model_gateway" };
  } catch (error) {
    if (!canFallbackQianchuanStepPlan(error)) throw error;
  }
  const fallback = await qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/plan-step", {
    method: "POST",
    timeoutMs: 40000,
    body: {
      ...body,
      modelId: String(message.modelId || "").slice(0, 160),
    }
  });
  return { ...fallback, transport: "local_bridge_fallback" };
}

async function requestQianchuanAgentReadonlyObservation(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "千川 Agent 仅限有效会员使用");
    error.authRequired = true;
    throw error;
  }
  const visualDataUrl = String(message.visualDataUrl || "");
  const body = {
    request_id: String(message.requestId || message.request_id || "").slice(0, 128),
    accountKey: normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key),
    scope: normalizeQianchuanAgentScope(message.scope),
    metricIds: Array.isArray(message.metricIds) ? message.metricIds.slice(0, 8) : [],
    domEvidence: String(message.domEvidence || "").slice(0, 2400),
    pageMeta: isObject(message.pageMeta) ? message.pageMeta : {},
    visualDataUrl: /^data:image\/jpeg;base64,[A-Za-z0-9+/=]+$/.test(visualDataUrl) && visualDataUrl.length <= 1200000
      ? visualDataUrl
      : ""
  };
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/readonly-observe",
    body,
    gate.auth.token,
    45000,
    { notifyMaintenance: false }
  );
  refreshPluginAuth(gate.auth).catch(() => {});
  return { ...unwrapPayload(response), transport: "backend_model_gateway" };
}

function canFallbackQianchuanStepPlan(error) {
  const status = Number(error?.status || 0);
  const code = String(error?.code || "").trim();
  if (status === 404) return true;
  return new Set([
    "plugin_executor_model_unavailable",
    "route_unavailable",
    "model_unavailable",
    "provider_secret_unavailable",
    "all_routes_failed"
  ]).has(code);
}

async function requestQianchuanAgentMemory(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const body = isObject(message.payload) ? { ...message.payload } : {};
  body.action = String(body.action || message.action || "save").trim().toLowerCase();
  body.account_key = normalizeQianchuanAgentAccountKey(body.account_key || body.accountKey || message.accountKey);
  body.scope = normalizeQianchuanAgentScope(body.scope || message.scope);
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/agent-memory",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentAudit(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const body = isObject(message.payload) ? { ...message.payload } : {};
  body.account_key = normalizeQianchuanAgentAccountKey(body.account_key || body.accountKey || message.accountKey);
  body.scope = normalizeQianchuanAgentScope(body.scope || message.scope);
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/agent-audit",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentSettings(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const body = isObject(message.payload) ? { ...message.payload } : {};
  body.account_key = normalizeQianchuanAgentAccountKey(body.account_key || body.accountKey || message.accountKey);
  body.scope = normalizeQianchuanAgentScope(body.scope || message.scope);
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/agent-settings",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentSkill(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const body = isObject(message.payload) ? { ...message.payload } : {};
  body.account_key = normalizeQianchuanAgentAccountKey(body.account_key || body.accountKey || message.accountKey);
  body.scope = normalizeQianchuanAgentScope(body.scope || message.scope);
  body.mode = body.mode === "personal" ? "personal" : "workbench";
  body.share_personal_insights = body.share_personal_insights === true || body.sharePersonalInsights === true;
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/agent-skill",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentPreferences(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const source = isObject(message.payload) ? message.payload : {};
  const body = {
    dialogue_habit: String(source.dialogue_habit ?? source.dialogueHabit ?? "").slice(0, 1000),
    operation_habit: String(source.operation_habit ?? source.operationHabit ?? "").slice(0, 1000)
  };
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/agent-preferences",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentMonitorRule(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const body = isObject(message.payload) ? { ...message.payload } : {};
  body.action = String(body.action || message.action || "save").trim().toLowerCase();
  body.account_key = normalizeQianchuanAgentAccountKey(body.account_key || body.accountKey || message.accountKey);
  body.scope = normalizeQianchuanAgentScope(body.scope || message.scope);
  const response = await requestJson(
    gate.auth.baseUrl,
    "POST",
    "/api/plugin/qianchuan/monitor-rule",
    body,
    gate.auth.token,
    12000
  );
  return unwrapPayload(response);
}

async function requestQianchuanAgentConversation(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const accountKey = normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key);
  const scope = normalizeQianchuanAgentScope(message.scope);
  const action = String(message.action || "history").trim().toLowerCase();
  const paths = {
    history: "/v1/qianchuan-agent/conversation/history",
    poll: "/v1/qianchuan-agent/conversation/poll",
    ack: "/v1/qianchuan-agent/conversation/ack",
    event: "/v1/qianchuan-agent/conversation/event",
    fail: "/v1/qianchuan-agent/conversation/fail"
  };
  if (!paths[action]) throw new Error("不支持的千川会话操作");
  const approval = action === "event" && isObject(message.approval)
    ? {
        approvalId: String(message.approval.approvalId || "").slice(0, 160),
        proposalId: String(message.approval.proposalId || "").slice(0, 160),
        code: String(message.approval.code || "").slice(0, 12),
        status: String(message.approval.status || "pending").slice(0, 24),
        decision: String(message.approval.decision || "").slice(0, 16),
        createdAt: Number(message.approval.createdAt || 0),
        expiresAt: Number(message.approval.expiresAt || 0),
        title: String(message.approval.title || "").slice(0, 120),
        summary: String(message.approval.summary || "").slice(0, 500)
      }
    : null;
  return qianchuanAgentLocalBridgeRequest(paths[action], {
    method: "POST",
    timeoutMs: 8000,
    body: {
      accountKey,
      scope,
      message: action === "event" ? String(message.message || "").slice(0, 6000) : "",
      externalId: String(message.externalId || "").slice(0, 128),
      approval,
      messageId: action === "fail" ? String(message.messageId || "").slice(0, 128) : "",
      error: action === "fail" ? String(message.error || "").slice(0, 600) : "",
      retryable: message.retryable !== false,
      messageIds: action === "ack" && Array.isArray(message.messageIds)
        ? message.messageIds.slice(0, 20).map((value) => String(value || "").slice(0, 128))
        : []
    }
  });
}

async function requestQianchuanAgentWeixin(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const accountKey = normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key);
  const scope = normalizeQianchuanAgentScope(message.scope);
  const action = String(message.action || "status").trim().toLowerCase();
  if (action === "notify") {
    return deliverQianchuanAgentWeixinNotification(
      accountKey,
      scope,
      String(message.message || "").slice(0, 3000)
    );
  }
  const paths = {
    status: "/v1/qianchuan-agent/weixin-binding/status",
    bind: "/v1/qianchuan-agent/weixin-binding/bind",
    poll: "/v1/qianchuan-agent/weixin-commands/poll",
    ack: "/v1/qianchuan-agent/weixin-commands/ack",
    fail: "/v1/qianchuan-agent/weixin-commands/fail"
  };
  if (!paths[action]) throw new Error("不支持的微信指令或通知操作");
  return qianchuanAgentLocalBridgeRequest(paths[action], {
    method: "POST",
    timeoutMs: 8000,
    body: {
      accountKey,
      scope,
      commandId: action === "fail" ? String(message.commandId || "").slice(0, 96) : "",
      error: action === "fail" ? String(message.error || "").slice(0, 600) : "",
      retryable: message.retryable !== false,
      commandIds: action === "ack" && Array.isArray(message.commandIds)
        ? message.commandIds.slice(0, 20).map((value) => String(value || "").slice(0, 96))
        : []
    }
  });
}

async function deliverQianchuanAgentWeixinNotification(accountKey, scope, message) {
  const started = await qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/weixin-notify/start", {
    method: "POST",
    timeoutMs: 8000,
    body: { accountKey, scope, message }
  });
  const jobId = String(started?.job?.jobId || "").trim();
  if (!jobId) throw new Error("MMW 客户端未返回微信通知任务");
  const deadline = Date.now() + 65000;
  while (Date.now() < deadline) {
    const response = await qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/weixin-notify/status", {
      method: "POST",
      timeoutMs: 8000,
      body: { jobId }
    });
    const job = response?.job || {};
    if (job.status === "sent") return { ok: true, sent: true, ...(job.result || {}) };
    if (job.status === "failed") throw new Error(job.error || "微信通知发送失败");
    await new Promise((resolve) => setTimeout(resolve, 1200));
  }
  throw new Error("微信通知发送超时，未确认送达，请勿重复提交");
}

async function requestQianchuanAgentWorker(message = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message || "请先登录 MMW工作台账号");
    error.authRequired = true;
    throw error;
  }
  const action = String(message.action || "register").trim().toLowerCase();
  if (action === "list") {
    return qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/workers", {
      method: "GET",
      timeoutMs: 5000
    });
  }
  if (action !== "register") throw new Error("不支持的千川页面注册操作");
  return qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/workers/register", {
    method: "POST",
    timeoutMs: 5000,
    body: {
      accountKey: normalizeQianchuanAgentAccountKey(message.accountKey || message.account_key),
      accountName: String(message.accountName || message.account_name || "").trim().slice(0, 160),
      scope: normalizeQianchuanAgentScope(message.scope),
      instanceId: String(message.instanceId || message.instance_id || "").trim().slice(0, 128),
      extensionId: chrome.runtime.id,
      pageVisibility: message.pageVisibility === "visible" ? "visible" : "hidden",
      pluginVersion: String(message.pluginVersion || message.plugin_version || "").trim().slice(0, 32)
    }
  });
}

async function requestQianchuanAgentDataReport(message = {}, sender = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) throw new Error(gate.message || "千川 Agent 当前不可用");
  requireQianchuanReportSender(sender);
  const report = isObject(message.report) ? message.report : {};
  return qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/data-reports/ingest", {
    method: "POST",
    timeoutMs: 8000,
    body: {
      ...report,
      accountKey: normalizeQianchuanAgentAccountKey(report.accountKey),
      scope: normalizeQianchuanAgentScope(report.scope),
      workerInstanceId: String(report.workerInstanceId || "").trim().slice(0, 128),
      extensionId: chrome.runtime.id
    }
  });
}

async function requestQianchuanAgentNotification(message = {}, sender = {}) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) throw new Error(gate.message || "千川 Agent 当前不可用");
  requireQianchuanReportSender(sender);
  const notification = isObject(message.notification) ? message.notification : {};
  return qianchuanAgentLocalBridgeRequest("/v1/qianchuan-agent/notifications/ingest", {
    method: "POST",
    timeoutMs: 8000,
    body: {
      ...notification,
      accountKey: normalizeQianchuanAgentAccountKey(notification.accountKey),
      scope: normalizeQianchuanAgentScope(notification.scope),
      workerInstanceId: String(notification.workerInstanceId || "").trim().slice(0, 128),
      extensionId: chrome.runtime.id
    }
  });
}

function requireQianchuanReportSender(sender) {
  if (!Number.isInteger(sender?.tab?.id) || sender.frameId !== 0) throw new Error("千川数据来源无效");
  try {
    const url = new URL(String(sender.url || sender.tab.url || ""));
    if (url.protocol !== "https:" || url.hostname !== "qianchuan.jinritemai.com") throw new Error();
  } catch {
    throw new Error("千川数据来源无效");
  }
}

async function qianchuanAgentLocalBridgeRequest(path, options = {}) {
  const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 8000);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(`${AGENT_BROWSER_LOCAL_BRIDGE_ORIGIN}${path}`, {
      method: String(options.method || "GET").toUpperCase(),
      cache: "no-store",
      headers: options.body ? { "content-type": "application/json" } : undefined,
      body: options.body ? JSON.stringify(options.body) : undefined,
      signal: controller.signal
    });
    const text = await response.text();
    let data = {};
    if (text) {
      try {
        data = JSON.parse(text);
      } catch {
        throw new Error(text.slice(0, 240) || "本机千川 Agent 返回内容无效");
      }
    }
    if (!response.ok || data?.ok === false) {
      throw new Error(data?.error || data?.message || `本机千川 Agent 请求失败 (${response.status})`);
    }
    return data;
  } catch (error) {
    if (controller.signal.aborted) {
      throw new Error("MMW 客户端或 Claw 响应超时，请确认客户端保持在线");
    }
    throw error;
  } finally {
    clearTimeout(timer);
  }
}

async function getEcommerceToolStatusPayload() {
  const gate = await requireEcommerceToolFeatureAccess();
  const auth = gate.auth;
  const payload = {
    version: CURRENT_PLUGIN_VERSION,
    workbenchUrl: ECOMMERCE_TOOL_WORKBENCH_URL,
    baseUrl: auth.baseUrl,
    loggedIn: auth.loggedIn,
    active: isAuthUsable(auth),
    auth: sanitizeAuthForUi(auth),
    remaining: null,
    pointsBalance: Number(auth.account?.points_balance || 0) || 0
  };
  if (!auth.loggedIn || !auth.token) return payload;
  try {
    const response = await requestJson(auth.baseUrl, "GET", "/api/ecommerce-tool/remaining", null, auth.token, 10000);
    const remaining = unwrapPayload(response);
    payload.remaining = remaining;
    if (isObject(remaining)) {
      payload.pointsBalance = Number(remaining.points_balance || remaining.balance || payload.pointsBalance) || 0;
      payload.billing = isObject(remaining.billing) ? remaining.billing : null;
      payload.tokenRemainingEstimate = Number(remaining.token_remaining_estimate || 0) || 0;
    }
  } catch (error) {
    payload.remainingError = error.message || "后端余额读取失败";
  }
  return payload;
}

async function requestNativeEcommerceCloud(payload = {}) {
  const gate = await requireEcommerceToolFeatureAccess();

  const endpoint = String(payload.path || payload.endpoint || "").trim();
  if (!ECOMMERCE_CLOUD_ENDPOINTS.has(endpoint)) {
    throw new Error("不支持的电商云端业务接口");
  }
  const method = String(payload.method || "GET").trim().toUpperCase();
  if (method !== "GET" && method !== "POST") {
    throw new Error("电商云端业务仅支持 GET 和 POST");
  }

  let requestPath = endpoint;
  if (method === "GET" && isObject(payload.query)) {
    const query = new URLSearchParams();
    for (const [key, value] of Object.entries(payload.query)) {
      if (value === undefined || value === null || value === "") continue;
      if (Array.isArray(value)) {
        for (const item of value) query.append(key, String(item));
      } else {
        query.set(key, String(value));
      }
    }
    const encoded = query.toString();
    if (encoded) requestPath += `?${encoded}`;
  }

  const body = method === "POST"
    ? (isObject(payload.body) ? payload.body : isObject(payload.data) ? payload.data : {})
    : null;
  const response = await requestJson(
    gate.auth.baseUrl,
    method,
    requestPath,
    body,
    gate.auth.token,
    Number(payload.timeoutMs || payload.timeout || 20000)
  );
  return unwrapPayload(response);
}

async function requestEcommerceToolAi(message) {
  const gate = await requireEcommerceToolFeatureAccess();

  const scene = normalizeEcommerceToolAiScene(message.scene || message.tool || message.action);
  const sourcePayload = isObject(message.payload) ? message.payload : {};
  const prompt = firstDefined(
    message.prompt,
    message.text,
    message.message,
    sourcePayload.prompt,
    sourcePayload.text,
    sourcePayload.message,
    sourcePayload.content
  );
  const body = { ...sourcePayload };
  if (prompt && !body.prompt) body.prompt = String(prompt);
  if (prompt && !body.message) body.message = String(prompt);
  if (!body.prompt && !body.message && !body.messages) {
    body.prompt = "请给出一段适合电商运营场景的商品优化建议。";
  }

  const response = await requestJson(gate.auth.baseUrl, "POST", `/api/ecommerce-tool/${scene}`, body, gate.auth.token, 130000);
  return unwrapPayload(response);
}

function normalizeEcommerceToolAiScene(value) {
  const scene = String(value || "ai-chat").trim();
  if (!ECOMMERCE_TOOL_AI_SCENES.has(scene)) {
    throw new Error("不支持的电商工具 AI 场景");
  }
  return scene;
}

function handleCompatMessage(message, sender, sendResponse) {
  if (!message || typeof message !== "object" || typeof message.name !== "string") {
    return false;
  }

  if (message.name === "newPageTask") {
    handleNewPageTaskCompat(message.body || {}, sender, sendResponse);
    return true;
  }

  if (message.name === "reportError") {
    recordAdminDebugEvent("compat-report-error", {
      sender: summarizeDebugSender(sender),
      body: message.body || {}
    }, "warn");
    console.warn("[mmw-plugin] qingtai reportError:", message.body || {});
    sendResponse({ ok: true });
    return true;
  }

  if (message.name === "setNewIcon") {
    sendResponse({ ok: true });
    return true;
  }

  if (message.name === "aix_datatool_background_trans") {
    handleAixDataToolMessage(message.body || {}, sender, sendResponse);
    return true;
  }

  return false;
}

function handleAixDataToolMessage(body, sender, sendResponse) {
  dispatchAixDataToolAction(body?.action, body?.data || {}, sender)
    .then((payload) => sendResponse(payload))
    .catch((error) => {
      sendResponse({ success: false, error: error.message || String(error), message: error.message || String(error) });
    });
}

async function dispatchAixDataToolAction(action, data, sender) {
  switch (action) {
    case "get_extension_id":
      return { extensionId: chrome.runtime.id };
    case "getTabId":
      return sender?.tab?.id || await getActiveTabId();
    case "get_storage":
    case "getStorage":
      return getAixStorageValue(data);
    case "set_storage":
    case "setStorage":
      return setAixStorageValue(data);
    case "remove_storage":
    case "removeStorage":
      return removeAixStorageValue(data);
    case "close_tab":
      return closeAixTab(data);
    case "new_tab":
      return createAixTab(data);
    case "openDownloadSetting":
      return createAixTab({ url: "chrome://settings/downloads", active: true });
    case "proxy":
      return proxyAixRequest(data);
    case "download":
      return startAixCompatDownload(data);
    default:
      return { error: `Unknown action: ${action}` };
  }
}

function getAixStorageValue(data) {
  return new Promise((resolve) => {
    if (!data?.key) {
      resolve({ error: "key is required" });
      return;
    }
    chrome.storage.local.get(data.key, (result) => {
      resolve(result[data.key]);
    });
  });
}

function setAixStorageValue(data) {
  return new Promise((resolve) => {
    if (!data || typeof data.key === "undefined") {
      resolve({ error: "key is required" });
      return;
    }
    chrome.storage.local.set({ [data.key]: data.value }, () => {
      resolve(chrome.runtime.lastError ? { error: chrome.runtime.lastError.message } : { success: true });
    });
  });
}

function removeAixStorageValue(data) {
  return new Promise((resolve) => {
    if (!data?.key) {
      resolve({ error: "key is required" });
      return;
    }
    chrome.storage.local.remove(data.key, () => {
      resolve(chrome.runtime.lastError ? { error: chrome.runtime.lastError.message } : { success: true });
    });
  });
}

function getActiveTabId() {
  return new Promise((resolve) => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      resolve(tabs?.[0]?.id || { error: "No active tabs" });
    });
  });
}

function closeAixTab(data) {
  return new Promise((resolve) => {
    if (!data?.tabId) {
      resolve({ error: "tabId is required" });
      return;
    }
    chrome.tabs.remove(data.tabId, () => {
      if (chrome.runtime.lastError) {
        resolve({ error: chrome.runtime.lastError.message });
        return;
      }
      chrome.tabs.query({ currentWindow: true }, (tabs) => {
        if (tabs?.length === 1) {
          chrome.windows.getCurrent({}, (win) => {
            if (win?.id) chrome.windows.remove(win.id, () => resolve({ success: true }));
            else resolve({ success: true });
          });
          return;
        }
        resolve({ success: true });
      });
    });
  });
}

function createAixTab(data) {
  return new Promise((resolve) => {
    const createProperties = Object.fromEntries(Object.entries({
      url: data?.url,
      active: data?.active !== false,
      windowId: data?.windowId,
      index: data?.index,
      pinned: data?.pinned || false,
      openerTabId: data?.openerTabId
    }).filter(([, value]) => typeof value !== "undefined"));
    chrome.tabs.create(createProperties, (tab) => {
      resolve(chrome.runtime.lastError ? { error: chrome.runtime.lastError.message } : tab);
    });
  });
}

async function proxyAixRequest(data) {
  const method = String(data?.method || "get").toUpperCase();
  const url = String(data?.url || "").trim();
  if (!url) return { error: "url is required" };

  const options = {
    method,
    headers: { "Content-Type": "application/json" }
  };
  if (data?.json && Object.keys(data.json).length > 0) {
    options.body = JSON.stringify(data.json);
  } else if (data?.data && Object.keys(data.data).length > 0) {
    options.body = new URLSearchParams(data.data).toString();
    options.headers["Content-Type"] = "application/x-www-form-urlencoded";
  }

  const response = await fetch(url, options);
  const contentType = response.headers.get("Content-Type") || "";
  let responseData;
  if (contentType.includes("application/json")) responseData = await response.json();
  else if (contentType.includes("text/") || contentType.includes("javascript")) responseData = await response.text();
  else responseData = await response.blob();

  return {
    ok: response.ok,
    status: response.status,
    statusText: response.statusText,
    data: responseData
  };
}

function startAixCompatDownload(data) {
  return new Promise((resolve) => {
    const url = String(data?.url || "").trim();
    if (!url) {
      resolve({ success: false, message: "url is required", error: "url is required" });
      return;
    }

    const filename = normalizeAixDownloadFilename(data?.filename, url);
    chrome.downloads.download({
      url,
      filename,
      conflictAction: "uniquify",
      saveAs: false
    }, (downloadId) => {
      if (chrome.runtime.lastError || !downloadId) {
        const message = chrome.runtime.lastError?.message || "Unknown error";
        resolve({ success: false, message, error: message });
        return;
      }

      let settled = false;
      const finish = (payload) => {
        if (settled) return;
        settled = true;
        chrome.downloads.onChanged.removeListener(onChanged);
        resolve(payload);
      };
      const onChanged = (delta) => {
        if (delta.id !== downloadId) return;
        if (delta.state?.current === "interrupted") {
          finish({ success: false, id: downloadId, message: delta.error?.current || "Download interrupted" });
          return;
        }
        if (delta.state?.current === "in_progress") {
          finish({ success: true, id: downloadId });
        }
      };

      chrome.downloads.onChanged.addListener(onChanged);
      setTimeout(() => finish({ success: true, id: downloadId }), 1000);
    });
  });
}

function normalizeAixDownloadFilename(filename, url) {
  const fallbackExtension = getExtension(url) || "mp4";
  const raw = String(filename || `video_${Date.now()}.${fallbackExtension}`).trim();
  const parts = raw.split(/[\\/]+/).filter(Boolean);
  const safeParts = parts.map((part) => sanitizeFileName(part)).filter(Boolean);
  let result = safeParts.join("/");
  if (!result) result = `video_${Date.now()}.${fallbackExtension}`;
  if (!getExtension(result)) result = `${result}.${fallbackExtension}`;
  return result;
}

function recordAdminDebugEvent(event, payload, level = "info") {
  if (shouldSkipAdminDebugEvent(event, payload)) return;
  pushAgentBrowserRuntimeLog({
    source: "background",
    event,
    level,
    payload
  });
  try {
    getPluginAuthState({ refresh: false })
      .then((auth) => {
        if (!auth.loggedIn || !hasPointsAdminAccess(auth)) return null;
        return MMW_ADMIN_DEBUG.add({
          source: "background",
          event,
          level,
          payload
        });
      })
      .catch(() => {});
  } catch {}
}

function shouldSkipAdminDebugEvent(event, payload) {
  if (event !== "runtime-message") return false;
  const type = String(payload?.type || payload?.message?.type || "");
  return new Set([
    "GET_RESOURCES",
    "CONTENT_RESOURCES",
    "CONTENT_CARD_META",
    "PLUGIN_AUTH_STATUS",
    "ECOMMERCE_TOOL_STATUS",
    "SHOP_MOVE_AI_PUBLISH_STATUS",
    "SHOP_MOVE_AI_PUBLISH_PROGRESS",
    "VD_VIDEO_COUNT"
  ]).has(type);
}

function summarizeDebugSender(sender) {
  return {
    tabId: sender?.tab?.id,
    tabUrl: sender?.tab?.url || "",
    frameId: sender?.frameId,
    origin: sender?.origin || ""
  };
}

function summarizeDebugMessage(message) {
  if (!isObject(message)) return message;
  if (message.type === "CONTENT_RESOURCES") {
    return {
      type: message.type,
      count: Array.isArray(message.resources) ? message.resources.length : 0,
      sample: Array.isArray(message.resources) ? message.resources.slice(0, 5) : []
    };
  }
  if (message.type === "DOWNLOAD_SELECTED" || message.type === "FLOATING_DOWNLOAD_SELECTED" || message.type === "SOURCE_TRACE_BATCH_DOWNLOAD") {
    return {
      ...message,
      items: Array.isArray(message.items) ? message.items.slice(0, 10) : [],
      groups: Array.isArray(message.groups) ? message.groups.slice(0, 10).map((group) => Array.isArray(group) ? group.slice(0, 3) : group) : undefined
    };
  }
  return message;
}

function pushAgentBrowserRuntimeLog(entry = {}) {
  try {
    const now = Date.now();
    const raw = isObject(entry) ? entry : { payload: entry };
    const sanitize = typeof MMW_ADMIN_DEBUG?.sanitizeValue === "function"
      ? MMW_ADMIN_DEBUG.sanitizeValue
      : ((value) => value);
    const normalized = {
      id: `${now}-${Math.random().toString(16).slice(2, 8)}`,
      time: now,
      iso: new Date(now).toISOString(),
      level: String(raw.level || "info").slice(0, 24),
      source: String(raw.source || "plugin").slice(0, 80),
      event: String(raw.event || raw.label || "log").slice(0, 120),
      pageUrl: String(raw.pageUrl || raw.url || "").slice(0, 1200),
      tabId: Number.isFinite(Number(raw.tabId)) ? Number(raw.tabId) : undefined,
      payload: sanitize(raw.payload ?? raw.data ?? raw.message ?? raw, 0)
    };
    agentBrowserRuntimeLogs.push(normalized);
    if (agentBrowserRuntimeLogs.length > AGENT_BROWSER_RUNTIME_LOG_LIMIT) {
      agentBrowserRuntimeLogs.splice(0, agentBrowserRuntimeLogs.length - AGENT_BROWSER_RUNTIME_LOG_LIMIT);
    }
    return normalized;
  } catch {
    return null;
  }
}

function filterAgentBrowserLogEntries(logs, options = {}, maxLimit = AGENT_BROWSER_RUNTIME_LOG_LIMIT) {
  const cleanMaxLimit = Math.max(1, Number(maxLimit) || AGENT_BROWSER_RUNTIME_LOG_LIMIT);
  const defaultLimit = Math.max(1, Math.min(cleanMaxLimit, AGENT_BROWSER_RUNTIME_LOG_DEFAULT_LIMIT));
  const limit = Math.max(1, Math.min(cleanMaxLimit, Number(options.limit) || defaultLimit));
  const since = Math.max(0, Number(options.since) || 0);
  const sourceFilter = String(options.source || "").trim().toLowerCase();
  const eventFilter = String(options.event || "").trim().toLowerCase();
  const levelFilter = String(options.level || "").trim().toLowerCase();
  const sessionIdFilter = String(options.sessionId || "").trim();
  const tabIdFilter = Number(options.tabId);
  const hasTabIdFilter = Number.isFinite(tabIdFilter);
  let filtered = Array.isArray(logs) ? logs.slice() : [];
  if (since > 0) filtered = filtered.filter((item) => Number(item?.time || 0) >= since);
  if (sourceFilter) filtered = filtered.filter((item) => String(item?.source || "").toLowerCase().includes(sourceFilter));
  if (eventFilter) filtered = filtered.filter((item) => String(item?.event || "").toLowerCase().includes(eventFilter));
  if (levelFilter) filtered = filtered.filter((item) => String(item?.level || "").toLowerCase() === levelFilter);
  if (sessionIdFilter) filtered = filtered.filter((item) => String(item?.sessionId || "") === sessionIdFilter);
  if (hasTabIdFilter) filtered = filtered.filter((item) => Number(item?.tabId) === tabIdFilter);
  return filtered.slice(-limit);
}

function filterAgentBrowserRuntimeLogs(logs, options = {}) {
  return filterAgentBrowserLogEntries(logs, options, AGENT_BROWSER_RUNTIME_LOG_LIMIT);
}

function summarizeShopMoveSourceCaptureRecordList(records) {
  const list = Array.isArray(records) ? records : [];
  const kinds = {};
  for (const record of list) {
    const kind = String(record?.kind || "unknown").trim() || "unknown";
    kinds[kind] = (kinds[kind] || 0) + 1;
  }
  const lastRecord = list.length ? list[list.length - 1] : null;
  return {
    count: list.length,
    kinds,
    lastAt: Number(lastRecord?.at || 0) || 0,
    sampleUrls: list.slice(-3).map((record) => String(record?.url || "")).filter(Boolean)
  };
}

function summarizeShopMoveParseTabRuntime(parseTabState, tabId) {
  const state = isObject(parseTabState) ? parseTabState : {};
  const numericTabId = Number(tabId) || 0;
  const tabMeta = referenceCaptureTabMeta.get(numericTabId) || {};
  const recordSummary = summarizeShopMoveSourceCaptureRecordList(shopMoveSourceCaptureRecords.get(numericTabId) || []);
  return {
    tab_id: numericTabId,
    source_item_id: state.sourceItemId || "",
    target_url: state.url || "",
    stage: state.stage || "",
    reused_tab: state.reusedTab === true,
    closed: state.closed === true,
    started_at: Number(state.startedAt || 0) || 0,
    elapsed_ms: Math.max(0, Date.now() - Number(state.startedAt || Date.now())),
    closed_at: Number(state.closedAt || 0) || 0,
    last_tab_url: state.lastTabUrl || tabMeta.url || "",
    last_tab_status: state.lastTabStatus || tabMeta.status || "",
    last_tab_title: state.lastTabTitle || tabMeta.title || "",
    last_tab_change_at: Number(state.lastTabChangeAt || 0) || 0,
    last_snapshot_at: Number(state.lastSnapshotAt || 0) || 0,
    last_message: state.lastMessage || "",
    last_script_error: state.lastScriptError || "",
    record_count: recordSummary.count,
    capture_kinds: recordSummary.kinds,
    last_record_at: recordSummary.lastAt,
    sample_record_urls: recordSummary.sampleUrls,
    package_state: isObject(state.lastPackageState) ? state.lastPackageState : null
  };
}

async function buildAgentBrowserAdminDebugPayload(adminStatus, options = {}) {
  if (!adminStatus) return null;
  adminScreenObserverState.loaded = true;
  adminScreenObserverState.enabled = adminStatus.screenObserverEnabled === true;
  return {
    enabled: adminStatus.enabled === true,
    pageProbeEnabled: adminStatus.pageProbeEnabled === true,
    screenObserverEnabled: adminStatus.screenObserverEnabled === true,
    referenceCaptureEnabled: adminStatus.referenceCaptureEnabled === true,
    count: Number(adminStatus.count || 0) || 0,
    maxLogs: Number(adminStatus.maxLogs || 0) || 0,
    referenceCaptureSession: adminStatus.referenceCaptureSession || null,
    referenceCaptureSessions: Array.isArray(adminStatus.referenceCaptureSessions) ? adminStatus.referenceCaptureSessions.slice(0, 12) : [],
    referenceCaptureRules: adminStatus.referenceCaptureRules || {},
    referenceCapturePackage: adminStatus.referenceCapturePackage || {},
    screenObserver: getAdminScreenObserverVisualState({
      includeLatestDataUrl: options.includeLatestDataUrl !== false,
      includeFrameData: options.includeFrameData === true,
      limit: options.frameLimit
    })
  };
}

async function getAgentBrowserPluginStatus(payload = {}) {
  const enabled = await isAgentBrowserControlEnabled().catch(() => false);
  const currentTab = await resolveAgentBrowserControlTab(null, payload.tabId, { required: false }).catch(() => null);
  const adminStatus = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  const aiTask = getLatestShopMoveAiPublishTask(payload.taskId);
  const parseTabs = Array.from(shopMoveRenderedParseTabs.entries()).map(([tabId, state]) => (
    summarizeShopMoveParseTabRuntime(state, tabId)
  ));
  const sourceCaptureTabs = Array.from(shopMoveSourceCaptureRecords.entries())
    .filter(([tabId]) => !shopMoveRenderedParseTabs.has(tabId))
    .map(([tabId, records]) => {
      const tabMeta = referenceCaptureTabMeta.get(tabId) || {};
      return {
        tab_id: Number(tabId) || 0,
        tab_url: tabMeta.url || "",
        tab_status: tabMeta.status || "",
        tab_title: tabMeta.title || "",
        ...summarizeShopMoveSourceCaptureRecordList(records)
      };
    })
    .slice(0, 20);
  return {
    version: CURRENT_PLUGIN_VERSION,
    now: Date.now(),
    agentControlEnabled: enabled,
    localBridge: snapshotAgentBrowserLocalBridgeState(),
    currentTab: currentTab ? summarizeAgentBrowserTab(currentTab) : null,
    runtimeLogs: {
      count: agentBrowserRuntimeLogs.length,
      maxLogs: AGENT_BROWSER_RUNTIME_LOG_LIMIT,
      last: filterAgentBrowserRuntimeLogs(agentBrowserRuntimeLogs, { limit: 10 })
    },
    adminDebug: await buildAgentBrowserAdminDebugPayload(adminStatus, { includeLatestDataUrl: false, frameLimit: 6 }),
    aiPublish: {
      count: shopMoveAiPublishTasks.size,
      task: aiTask ? shopMoveAiTaskPayload(aiTask) : null
    },
    parseTabs,
    sourceCaptureTabs,
    parseResultCache: summarizeShopMoveParseResultCache(Number(payload.limit) || 20)
  };
}

async function getAgentBrowserPluginLogs(payload = {}) {
  const runtimeLogs = filterAgentBrowserRuntimeLogs(agentBrowserRuntimeLogs, payload);
  const adminStatus = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  const aiTask = getLatestShopMoveAiPublishTask(payload.taskId);
  const adminMaxLogs = Math.max(1, Number(adminStatus?.maxLogs || 0) || 2000);
  return {
    runtime: {
      total: agentBrowserRuntimeLogs.length,
      maxLogs: AGENT_BROWSER_RUNTIME_LOG_LIMIT,
      logs: runtimeLogs
    },
    adminDebug: adminStatus ? {
      ...(await buildAgentBrowserAdminDebugPayload(adminStatus, {
        includeLatestDataUrl: payload.includeLatestFrameData === true,
        includeFrameData: payload.includeFrameData === true,
        frameLimit: payload.frameLimit || payload.limit
      })),
      total: Number(adminStatus.count || 0) || 0,
      maxLogs: Number(adminStatus.maxLogs || 0) || 0,
      logs: filterAgentBrowserLogEntries(adminStatus.logs, payload, adminMaxLogs)
    } : null,
    aiPublish: {
      count: shopMoveAiPublishTasks.size,
      task: aiTask ? shopMoveAiTaskPayload(aiTask) : null
    }
  };
}

async function getAgentBrowserPluginVisualState(payload = {}) {
  const adminStatus = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  if (payload.captureNow === true) {
    await captureAdminScreenObserverFrame(payload.reason || "manual", payload.tabId, adminStatus, { force: true }).catch(() => {});
  }
  return {
    version: CURRENT_PLUGIN_VERSION,
    now: Date.now(),
    adminDebug: await buildAgentBrowserAdminDebugPayload(adminStatus, {
      includeLatestDataUrl: payload.includeLatestDataUrl !== false,
      includeFrameData: payload.includeFrameData === true,
      frameLimit: payload.limit
    })
  };
}

async function clearAgentBrowserPluginLogs(payload = {}) {
  const runtimeCleared = agentBrowserRuntimeLogs.length;
  agentBrowserRuntimeLogs.length = 0;
  let adminDebug = { cleared: false };
  if (payload.clearAdminDebug === true) {
    const status = await MMW_ADMIN_DEBUG.clear().catch(() => null);
    adminDebug = {
      cleared: Boolean(status),
      count: Number(status?.count || 0) || 0
    };
  }
  return {
    runtimeCleared,
    adminDebug
  };
}

async function startAgentBrowserReferenceCapture(payload = {}) {
  if (payload.clearLogs !== false) await MMW_ADMIN_DEBUG.clear();
  await MMW_ADMIN_DEBUG.setEnabled(true);
  await MMW_ADMIN_DEBUG.setPageProbeEnabled(true);
  const startedAt = new Date().toISOString();
  const rules = isObject(payload.rules) ? payload.rules : {
    packageName: "千川素材上传",
    packageVersion: CURRENT_PLUGIN_VERSION,
    manifestMatches: ["qianchuan.jinritemai.com", "jinritemai.com", "oceanengine.com"],
    urlKeywords: ["qianchuan", "upload", "material", "creative", "video", "media", "asset", "file", "tos"],
    bodyKeywords: ["material_id", "materialId", "video_id", "videoId", "creative_id", "creativeId", "vid"],
    messageKeywords: ["material", "upload", "video"],
    notes: ["千川原生素材上传接口与返回素材标识捕获"]
  };
  const packageSummary = isObject(payload.packageSummary) ? payload.packageSummary : {
    name: "千川素材上传",
    version: CURRENT_PLUGIN_VERSION,
    loadedAt: startedAt
  };
  const session = isObject(payload.session) ? payload.session : {
    id: `qianchuan_material_${Date.now()}`,
    name: "千川素材上传抓包",
    packageName: packageSummary.name,
    packageVersion: packageSummary.version,
    startedAt
  };
  const status = await MMW_ADMIN_DEBUG.setReferenceCapture({
    enabled: true,
    rules,
    packageSummary,
    session
  });
  return {
    started: true,
    ...(await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: false, frameLimit: 4 }))
  };
}

async function stopAgentBrowserReferenceCapture(payload = {}) {
  const current = await MMW_ADMIN_DEBUG.getStatus().catch(() => null);
  const session = current?.referenceCaptureSession;
  const status = await MMW_ADMIN_DEBUG.setReferenceCapture({
    enabled: false,
    rules: current?.referenceCaptureRules || {},
    packageSummary: current?.referenceCapturePackage || {},
    session: session ? { ...session, endedAt: new Date().toISOString() } : null
  });
  return {
    stopped: true,
    ...(await buildAgentBrowserAdminDebugPayload(status, {
      includeLatestDataUrl: payload.includeLatestDataUrl === true,
      frameLimit: payload.limit || 4
    }))
  };
}

async function runAgentBrowserLocalOnlyAction(action, payload = {}) {
  switch (action) {
    case "plugin_status":
      return getAgentBrowserPluginStatus(payload);
    case "plugin_visual_state":
      return getAgentBrowserPluginVisualState(payload);
    case "plugin_logs":
      return getAgentBrowserPluginLogs(payload);
    case "plugin_logs_clear":
      return clearAgentBrowserPluginLogs(payload);
    case "plugin_reference_capture_start":
      return startAgentBrowserReferenceCapture(payload);
    case "plugin_reference_capture_stop":
      return stopAgentBrowserReferenceCapture(payload);
    default:
      throw new Error(`不支持的本地调试动作：${action}`);
  }
}

function dispatchMessage(message, sender, sendResponse) {
  recordAdminDebugEvent("runtime-message", {
    type: message.type,
    sender: summarizeDebugSender(sender),
    message: summarizeDebugMessage(message)
  });

  if (message.type === "CONTENT_RESOURCES") {
    handleContentResources(message, sender, sendResponse);
    return true;
  }
  if (message.type === "CONTENT_CARD_META") {
    handleContentCardMeta(message, sender, sendResponse);
    return true;
  }
  if (message.type === "GET_RESOURCES") {
    handleGetResources(sender, sendResponse);
    return true;
  }
  if (message.type === "INJECT_DOUYIN_HOOK") {
    sendResponse({ ok: true, migrated: "aix-douyin" });
    return true;
  }
  if (message.type === "CLEAR_TAB_RESOURCES") {
    handleClearResources(sender, sendResponse);
    return true;
  }
  if (message.type === "MMW_OPEN_LEGAL_CONSENT") {
    promptLegalConsentDialog(sender);
    sendResponse({ ok: true });
    return true;
  }
  if (message.type === "MMW_AGENT_CONTROL_GET_STATE") {
    getAgentBrowserControlState(sender, message.tabId)
      .then((result) => sendResponse({ ok: true, result }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取AI接管状态失败" }));
    return true;
  }
  if (message.type === "MMW_AGENT_CONTROL_SET_ENABLED") {
    setAgentBrowserControlEnabled(message.enabled === true, sender, message.tabId)
      .then((result) => sendResponse({ ok: true, result }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "切换AI接管失败" }));
    return true;
  }
  if (message.type === "MMW_AGENT_CONTROL_RUN") {
    runAgentBrowserControl(message, sender)
      .then((result) => sendResponse({ ok: true, result }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "AI接管动作失败" }));
    return true;
  }
  if (message.type === "MMW_AGENT_CONTROL_BACKGROUND_ACTION") {
    runAgentBrowserBackgroundAction(message.action, message.payload || {}, sender)
      .then((result) => sendResponse({ ok: true, result }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "后台浏览器动作失败" }));
    return true;
  }
  if (message.type === "DOWNLOAD_SELECTED") {
    handleDownloadSelected(message.items || [], sendResponse, message.mode || "all");
    return true;
  }
  if (message.type === "FLOATING_DOWNLOAD_SELECTED") {
    handleFloatingDownloadSelected(message.groups || message.items || [], sendResponse, {
      downloadTitles: message.downloadTitles === true
    });
    return true;
  }
  if (message.type === "DOWNLOAD_VIDEO_TITLE") {
    handleVideoTitleDownload(message, sendResponse);
    return true;
  }
  if (message.type === "START_TITLE_SELECTOR") {
    startTitleSelector(message, sender, sendResponse);
    return true;
  }
  if (message.type === "GET_PLUGIN_ENTITLEMENT") {
    getPluginEntitlement().then((entitlement) => sendResponse({ ok: true, entitlement }));
    return true;
  }
  if (message.type === "GET_PLUGIN_UPDATE_INFO") {
    getPluginUpdateInfo()
      .then((updateInfo) => sendResponse({ ok: true, updateInfo }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取插件更新信息失败" }));
    return true;
  }
  if (message.type === "GET_TUTORIAL_NAVIGATION") {
    getTutorialNavigation()
      .then((tutorialNavigation) => sendResponse({ ok: true, tutorialNavigation }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取教程导航失败" }));
    return true;
  }
  if (message.type === "SOURCE_TRACE") {
    requestSourceTrace(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "源视频溯源失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_BOOTSTRAP") {
    requestShopMoveBootstrap()
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取搬家配置失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_SAVE_CONFIG") {
    requestShopMoveSaveConfig(message.config || {})
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "保存搬家配置失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_CAPTURE_CURRENT_SHOP") {
    requestShopMoveCaptureCurrentShop(sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取当前店铺信息失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_CAPTURE_SHOP_TEMPLATES") {
    requestShopMoveCaptureShopTemplates(sender)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "刷新店铺模板失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_PARSE_PRODUCTS") {
    requestShopMoveParseProducts(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "解析商品信息失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_RECOVER_PARSE_RESULTS") {
    requestShopMoveRecoverParseResults(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "恢复商品解析结果失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_CREATE_JOB") {
    requestShopMoveCreateJob(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "提交搬家任务失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_LIST_JOBS") {
    requestShopMoveJobs(message.limit)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取搬家任务失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_LIST_RECORDS") {
    requestShopMoveRecords(message.limit)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "获取搬家记录失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_OPEN_SUPPORT_RECORDS") {
    openShopMoveSupportRecords()
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "打开用户后台解析记录失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_AI_PUBLISH_START") {
    requestShopMoveAiPublishStart(message)
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "启动AI浏览器上架失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_AI_PUBLISH_STATUS") {
    requireEcommerceToolFeatureAccess()
      .then(() => sendResponse({ ok: true, payload: getShopMoveAiPublishStatus(message.taskId) }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "电商工具未开放" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_AI_PUBLISH_CANCEL") {
    requireEcommerceToolFeatureAccess()
      .then(() => cancelShopMoveAiPublishTask(message.taskId))
      .then((payload) => sendResponse({ ok: true, payload }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "取消AI浏览器上架失败" }));
    return true;
  }
  if (message.type === "SHOP_MOVE_AI_PUBLISH_PROGRESS") {
    requireEcommerceToolFeatureAccess()
      .then(() => {
        updateShopMoveAiPublishProgress(message);
        sendResponse({ ok: true });
      })
      .catch((error) => sendResponse({ ok: false, error: error.message || "电商工具未开放" }));
    return true;
  }
  if (message.type === "SOURCE_TRACE_BATCH_DOWNLOAD") {
    handleSourceTraceBatchDownload(message.items || [], sendResponse, {
      downloadTitles: message.downloadTitles === true
    });
    return true;
  }
  if (message.type === "VD_CREATE_BACKUP_DOWNLOAD") {
    createBackupDownload(message, sendResponse);
    return true;
  }
  if (message.type === "VD_GET_BACKUP_DOWNLOAD_REQUEST") {
    getBackupDownloadRequest(message, sendResponse);
    return true;
  }
  if (message.type === "VD_CLEAR_BACKUP_DOWNLOAD_REQUEST") {
    clearBackupDownloadRequest(message, sendResponse);
    return true;
  }
  if (message.type === "VD_SET_REFERER_RULE") {
    setRefererRule(REFERER_RULE_ID_MAIN, message.url, message.referer || "https://www.douyin.com/", ["main_frame", "sub_frame"])
      .then(() => sendResponse({ ok: true }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "设置 Referer 失败" }));
    return true;
  }
  if (message.type === "VD_PREPARE_BACKUP_FETCH") {
    setDownloadHeadersRule(
      getDownloaderRuleId(message.ruleId || message.requestId),
      message.url,
      normalizeDownloadHeaders(message.requestHeaders, message.referer || "https://www.douyin.com/")
    )
      .then(() => sendResponse({ ok: true }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "准备下载失败" }));
    return true;
  }
  if (message.type === "VD_CLEAR_DOWNLOAD_HEADERS") {
    clearRule(getDownloaderRuleId(message.ruleId || message.requestId)).then(() => sendResponse({ ok: true }));
    return true;
  }
  if (message.type === "VD_VIDEO_COUNT" && sender.tab?.id) {
    updateVideoBadge(sender.tab.id, Number(message.count) || 0);
    return false;
  }
  if (message.type === "VD_OPEN_URLS" || message.type === "OPEN_DOUYIN_VIDEO") {
    openUrls(getMessageUrls(message), sendResponse);
    return true;
  }
  if (message.type === "VD_FETCH_VIDEO") {
    fetchVideo(message, sendResponse);
    return true;
  }
  if (message.type === "VD_FETCH_VIDEO_SAFE") {
    fetchVideoSafe(message, sender, sendResponse);
    return true;
  }
  if (message.type === "OFFSCREEN_SAVE_BLOB_URL") {
    saveOffscreenBlobUrl(message, sendResponse);
    return true;
  }
  if (message.type === "OFFSCREEN_VIDEO_RESULT") {
    handleOffscreenVideoResult(message, sendResponse);
    return true;
  }
  return false;
}

async function getAgentBrowserControlState(sender = null, preferredTabId = null) {
  const enabled = await isAgentBrowserControlEnabled();
  if (enabled) ensureAgentBrowserLocalBridgeLoop().catch(() => {});
  const tab = await resolveAgentBrowserControlTab(sender, preferredTabId, { required: false });
  let pageState = null;
  let bridgeReady = false;
  if (tab?.id && isAgentBrowserControllableUrl(tab.url)) {
    pageState = await ensureAgentBrowserControlScript(tab.id).catch(() => null);
    bridgeReady = pageState?.ok === true;
  }
  const localBridge = snapshotAgentBrowserLocalBridgeState();
  return {
    enabled,
    version: CURRENT_PLUGIN_VERSION,
    extensionId: chrome.runtime.id,
    tab: tab ? summarizeAgentBrowserTab(tab) : null,
    bridgeReady,
    connected: pageState?.result?.connected === true || localBridge.connected === true,
    page: pageState?.ok ? pageState.result : null,
    localBridge,
    engine: {
      snapshot: "page-bridge",
      input: "browser-debugger",
      navigation: "chrome-tabs"
    },
    protocol: {
      windowRequestType: "MMW_AGENT_BROWSER_CONTROL",
      windowResultType: "MMW_AGENT_BROWSER_CONTROL_RESULT",
      windowPingType: "MMW_AGENT_BROWSER_CONTROL_PING",
      localBridgeBaseUrl: AGENT_BROWSER_LOCAL_BRIDGE_BASE_URL,
      localBridgeStartHint: AGENT_BROWSER_LOCAL_BRIDGE_START_HINT
    }
  };
}

async function setAgentBrowserControlEnabled(enabled, sender = null, preferredTabId = null) {
  await chrome.storage.local.set({ [AGENT_BROWSER_CONTROL_STORAGE_KEY]: enabled === true });
  if (enabled === true) ensureAgentBrowserLocalBridgeLoop().catch(() => {});
  else stopAgentBrowserLocalBridgeLoop();
  const sync = await syncAgentBrowserControlOpenTabs(enabled === true).catch((error) => ({
    touched: 0,
    failed: 1,
    error: error.message || String(error)
  }));
  const state = await getAgentBrowserControlState(sender, preferredTabId);
  return { ...state, sync };
}

async function runAgentBrowserControl(message, sender) {
  const action = normalizeAgentBrowserAction(message.action);
  if (!action) throw new Error("action is required");
  if (action === "state") return getAgentBrowserControlState(sender, message.tabId);
  const enabled = await isAgentBrowserControlEnabled();
  if (!enabled) throw new Error("AI接管未开启");
  ensureAgentBrowserLocalBridgeLoop().catch(() => {});
  if (AGENT_BROWSER_BACKGROUND_ACTIONS.has(action)) {
    const result = await runAgentBrowserBackgroundAction(action, message.payload || {}, sender, message.tabId);
    const touchedTabId = pickAgentBrowserActionTabId(result, message.payload || {}, message.tabId);
    await touchAgentBrowserControlTab(touchedTabId, action).catch(() => {});
    return result;
  }
  if (AGENT_BROWSER_LOW_LEVEL_ACTIONS.has(action)) {
    const result = await runAgentBrowserLowLevelAction(action, message.payload || {}, sender, message.tabId);
    const touchedTabId = pickAgentBrowserActionTabId(result, message.payload || {}, message.tabId);
    await touchAgentBrowserControlTab(touchedTabId, action).catch(() => {});
    return result;
  }
  if (!AGENT_BROWSER_CONTENT_ACTIONS.has(action)) {
    throw new Error(`不支持的AI接管动作：${action}`);
  }
  const tab = await resolveAgentBrowserControlTab(sender, message.tabId);
  await ensureAgentBrowserControlScript(tab.id);
  const response = await sendAgentBrowserControlMessage(tab.id, {
    type: "MMW_AGENT_CONTROL_ACTION",
    action,
    payload: message.payload || {}
  });
  if (!response?.ok) throw new Error(response?.error || "页面动作执行失败");
  await touchAgentBrowserControlTab(tab.id, action).catch(() => {});
  return response.result || {};
}

async function runAgentBrowserBackgroundAction(rawAction, payload = {}, sender = null, preferredTabId = null) {
  const action = normalizeAgentBrowserAction(rawAction);
  if (!AGENT_BROWSER_BACKGROUND_ACTIONS.has(action)) {
    throw new Error(`不支持的后台浏览器动作：${action}`);
  }
  const enabled = await isAgentBrowserControlEnabled();
  if (!enabled) throw new Error("AI接管未开启");
  if (action === "list_tabs") {
    const limit = Math.max(1, Math.min(AGENT_BROWSER_LOCAL_BRIDGE_TAB_LIMIT, Number(payload.limit) || AGENT_BROWSER_LOCAL_BRIDGE_TAB_LIMIT));
    const tabs = await chrome.tabs.query(payload.currentWindow === true ? { currentWindow: true } : {});
    const filtered = (Array.isArray(tabs) ? tabs : [])
      .filter((item) => item?.id && (!payload.controllableOnly || isAgentBrowserControllableUrl(item.url)))
      .slice(0, limit)
      .map((item) => summarizeAgentBrowserTab(item));
    return { tabs: filtered, total: filtered.length };
  }
  if (action === "new_tab" || action === "open_tab") {
    const url = normalizeAgentBrowserUrl(payload.url);
    const tab = await chrome.tabs.create({ url, active: payload.active !== false });
    return { tab: summarizeAgentBrowserTab(tab) };
  }
  if (action === "activate_tab") {
    const tab = await resolveAgentBrowserControlTab(sender, payload.tabId ?? preferredTabId);
    const updated = await chrome.tabs.update(tab.id, { active: true });
    if (updated?.windowId) await chrome.windows.update(updated.windowId, { focused: true }).catch(() => null);
    return { tab: summarizeAgentBrowserTab(updated || tab) };
  }
  if (action === "close_tab") {
    const tab = await resolveAgentBrowserControlTab(sender, payload.tabId ?? preferredTabId);
    const summary = summarizeAgentBrowserTab(tab);
    await chrome.tabs.remove(tab.id);
    return { closed: true, tab: summary };
  }
  const tab = await resolveAgentBrowserControlTab(sender, preferredTabId);
  switch (action) {
    case "navigate": {
      const url = normalizeAgentBrowserUrl(payload.url);
      const updated = await chrome.tabs.update(tab.id, { url, active: payload.active !== false });
      return { tab: summarizeAgentBrowserTab(updated), url };
    }
    case "back":
      await goAgentBrowserHistory(tab.id, "back");
      return { tabId: tab.id, action };
    case "forward":
      await goAgentBrowserHistory(tab.id, "forward");
      return { tabId: tab.id, action };
    case "reload":
      await chrome.tabs.reload(tab.id, { bypassCache: payload.bypassCache === true });
      return { tabId: tab.id, action };
    case "screenshot": {
      if (!tab.active) await chrome.tabs.update(tab.id, { active: true });
      if (tab.windowId) await chrome.windows.update(tab.windowId, { focused: true }).catch(() => null);
      const dataUrl = await chromeTabCallback(
        (done) => chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, done),
        "截图失败"
      );
      return {
        tab: summarizeAgentBrowserTab(tab),
        mimeType: "image/png",
        dataUrl
      };
    }
    default:
      throw new Error(`不支持的后台浏览器动作：${action}`);
  }
}

async function isAgentBrowserControlEnabled() {
  const values = await chrome.storage.local.get([AGENT_BROWSER_CONTROL_STORAGE_KEY]).catch(() => ({}));
  return values[AGENT_BROWSER_CONTROL_STORAGE_KEY] === true;
}

async function resolveAgentBrowserControlTab(sender = null, preferredTabId = null, options = {}) {
  const required = options.required !== false;
  const numericTabId = Number(preferredTabId);
  if (Number.isInteger(numericTabId) && numericTabId > 0) {
    const tab = await chrome.tabs.get(numericTabId).catch(() => null);
    if (tab?.id) return tab;
  }
  if (sender?.tab?.id) return sender.tab;
  const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
  const tab = Array.isArray(tabs) ? tabs.find((item) => item?.id) : null;
  if (tab?.id) return tab;
  if (required) throw new Error("未找到可接管的浏览器标签页");
  return null;
}

async function ensureAgentBrowserControlScript(tabId) {
  const current = await sendAgentBrowserControlMessage(tabId, { type: "MMW_AGENT_CONTROL_GET_STATE" }).catch(() => null);
  if (current?.ok) return current;
  await chrome.scripting.executeScript({
    target: { tabId },
    files: [AGENT_BROWSER_CONTROL_SCRIPT]
  });
  return sendAgentBrowserControlMessage(tabId, { type: "MMW_AGENT_CONTROL_GET_STATE" });
}

async function restoreAgentBrowserControlOpenTabs() {
  if (!await isAgentBrowserControlEnabled()) {
    return { touched: 0, failed: 0 };
  }
  ensureAgentBrowserLocalBridgeLoop().catch(() => {});
  return syncAgentBrowserControlOpenTabs(true);
}

async function ensureAgentBrowserControlForTab(tabId) {
  if (!tabId || !await isAgentBrowserControlEnabled()) return null;
  const tab = await chrome.tabs.get(tabId).catch(() => null);
  if (!tab?.id || !isAgentBrowserControllableUrl(tab.url)) return null;
  const pageState = await ensureAgentBrowserControlScript(tab.id).catch(() => null);
  if (pageState?.ok) {
    await sendAgentBrowserControlMessage(tab.id, {
      type: "MMW_AGENT_CONTROL_SET_ENABLED",
      enabled: true
    }).catch(() => null);
  }
  return pageState;
}

async function syncAgentBrowserControlOpenTabs(enabled) {
  const tabs = await chrome.tabs.query({}).catch(() => []);
  let touched = 0;
  let failed = 0;
  for (const tab of tabs) {
    if (!tab?.id || !isAgentBrowserControllableUrl(tab.url)) continue;
    try {
      if (enabled) {
        await ensureAgentBrowserControlScript(tab.id);
      }
      await sendAgentBrowserControlMessage(tab.id, {
        type: "MMW_AGENT_CONTROL_SET_ENABLED",
        enabled: enabled === true
      }).catch(() => null);
      touched += 1;
    } catch {
      failed += 1;
    }
  }
  return { touched, failed };
}

function sendAgentBrowserControlMessage(tabId, message) {
  return new Promise((resolve, reject) => {
    chrome.tabs.sendMessage(tabId, message, (response) => {
      const error = chrome.runtime.lastError;
      if (error) {
        reject(new Error(error.message || "页面接管脚本未响应"));
        return;
      }
      resolve(response);
    });
  });
}

function snapshotAgentBrowserLocalBridgeState() {
  const state = { ...agentBrowserLocalBridgeState };
  if (state.connected && state.lastCommandAt && Date.now() - state.lastCommandAt > AGENT_BROWSER_LOCAL_BRIDGE_CONNECTED_IDLE_MS) {
    state.connected = false;
  }
  state.baseUrl = AGENT_BROWSER_LOCAL_BRIDGE_BASE_URL;
  state.startHint = AGENT_BROWSER_LOCAL_BRIDGE_START_HINT;
  return state;
}

function pickAgentBrowserActionTabId(result, payload = {}, preferredTabId = null) {
  const candidates = [
    result?.tab?.id,
    result?.tabId,
    payload?.tabId,
    preferredTabId
  ];
  for (const candidate of candidates) {
    const numeric = Number(candidate);
    if (Number.isInteger(numeric) && numeric > 0) return numeric;
  }
  return null;
}

async function touchAgentBrowserControlTab(tabId, action = "") {
  const numericTabId = Number(tabId);
  if (!Number.isInteger(numericTabId) || numericTabId <= 0) return null;
  const tab = await chrome.tabs.get(numericTabId).catch(() => null);
  if (!tab?.id || !isAgentBrowserControllableUrl(tab.url)) return null;
  await ensureAgentBrowserControlScript(tab.id).catch(() => null);
  return sendAgentBrowserControlMessage(tab.id, {
    type: "MMW_AGENT_CONTROL_TOUCH_CONNECTION",
    action
  }).catch(() => null);
}

async function runAgentBrowserLowLevelAction(action, payload = {}, sender = null, preferredTabId = null) {
  const tab = await resolveAgentBrowserControlTab(sender, preferredTabId);
  switch (action) {
    case "click": {
      const target = await resolveAgentBrowserLowLevelTarget(tab.id, payload, { requireTarget: true });
      await withAgentBrowserDebugger(tab.id, async (debugTarget) => {
        await dispatchAgentBrowserPointerMove(debugTarget, target.point);
        await dispatchAgentBrowserMouseClick(debugTarget, target.point);
      });
      return {
        clicked: true,
        tabId: tab.id,
        point: target.point,
        rect: target.rect || null,
        target: target.target || null,
        engine: "browser-debugger"
      };
    }
    case "move": {
      const target = await resolveAgentBrowserLowLevelTarget(tab.id, payload, { requireTarget: false, useViewportCenter: true });
      await withAgentBrowserDebugger(tab.id, (debugTarget) => dispatchAgentBrowserPointerMove(debugTarget, target.point));
      return {
        moved: true,
        tabId: tab.id,
        point: target.point,
        engine: "browser-debugger"
      };
    }
    case "scroll": {
      const targetPayload = normalizeAgentBrowserScrollTargetPayload(payload);
      const target = await resolveAgentBrowserLowLevelTarget(tab.id, targetPayload, {
        requireTarget: false,
        useViewportCenter: true,
        ignorePayloadPoint: !hasAgentBrowserExplicitPoint(payload)
      });
      const deltaX = Number(payload?.deltaX ?? payload?.x ?? 0) || 0;
      const deltaY = Number(payload?.deltaY ?? payload?.y ?? 0) || 0;
      await withAgentBrowserDebugger(tab.id, async (debugTarget) => {
        await dispatchAgentBrowserPointerMove(debugTarget, target.point);
        await dispatchAgentBrowserWheel(debugTarget, target.point, deltaX, deltaY);
      });
      return {
        scrolled: true,
        tabId: tab.id,
        point: target.point,
        deltaX,
        deltaY,
        engine: "browser-debugger"
      };
    }
    case "key": {
      const key = String(payload?.key || payload?.text || "").trim();
      if (!key) throw new Error("key is required");
      await withAgentBrowserDebugger(tab.id, (debugTarget) => dispatchAgentBrowserKeyPress(debugTarget, {
        key,
        code: payload?.code,
        ctrlKey: payload?.ctrlKey === true,
        altKey: payload?.altKey === true,
        shiftKey: payload?.shiftKey === true || payload?.shift === true,
        metaKey: payload?.metaKey === true
      }));
      return {
        key,
        tabId: tab.id,
        engine: "browser-debugger"
      };
    }
    case "type": {
      const text = String(payload?.text ?? payload?.value ?? "");
      const shouldResolveTarget = hasAgentBrowserTargetSpec(payload) || hasAgentBrowserPoint(payload);
      const target = shouldResolveTarget
        ? await resolveAgentBrowserLowLevelTarget(tab.id, payload, { requireTarget: true })
        : null;
      if (target?.disabled) throw new Error("目标已禁用");
      await withAgentBrowserDebugger(tab.id, async (debugTarget) => {
        if (target?.point) {
          await dispatchAgentBrowserPointerMove(debugTarget, target.point);
          await dispatchAgentBrowserMouseClick(debugTarget, target.point);
        }
        if (payload?.clear !== false) {
          await dispatchAgentBrowserKeyPress(debugTarget, {
            key: "a",
            code: "KeyA",
            ctrlKey: true
          });
          await dispatchAgentBrowserKeyPress(debugTarget, { key: "Backspace", code: "Backspace" });
        }
        if (text) {
          await chromeDebuggerSendCommand(debugTarget, "Input.insertText", { text }, "浏览器级输入失败");
        }
      });
      return {
        typed: true,
        tabId: tab.id,
        length: text.length,
        target: target?.target || null,
        engine: "browser-debugger"
      };
    }
    case "screenshot": {
      const clip = payload?.clip && typeof payload.clip === "object" ? payload.clip : {};
      const x = Number(clip.left ?? clip.x);
      const y = Number(clip.top ?? clip.y);
      const width = Number(clip.width);
      const height = Number(clip.height);
      if (![x, y, width, height].every(Number.isFinite) || x < 0 || y < 0 || width < 8 || height < 8 || width > 4096 || height > 4096) {
        throw new Error("视觉观察区域无效。");
      }
      const image = await withAgentBrowserDebugger(tab.id, (debugTarget) => chromeDebuggerSendCommand(
        debugTarget,
        "Page.captureScreenshot",
        {
          format: "jpeg",
          quality: 45,
          fromSurface: true,
          captureBeyondViewport: false,
          clip: { x, y, width, height, scale: 1 }
        },
        "千川目标区域截图失败"
      ));
      if (!image?.data) throw new Error("千川目标区域截图为空。");
      return {
        captured: true,
        tabId: tab.id,
        mimeType: "image/jpeg",
        dataUrl: `data:image/jpeg;base64,${image.data}`,
        clip: { left: x, top: y, width, height },
        engine: "browser-debugger"
      };
    }
    default:
      throw new Error(`不支持的浏览器级接管动作：${action}`);
  }
}

async function executeQianchuanAgentRealClick(message, sender) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message);
    error.authRequired = true;
    throw error;
  }
  const tabId = Number(sender?.tab?.id);
  const url = new URL(String(sender?.tab?.url || ""));
  if (!Number.isInteger(tabId) || tabId <= 0 || url.hostname !== "qianchuan.jinritemai.com") {
    throw new Error("真实点击仅允许在当前千川页面执行。");
  }
  const action = String(message?.action || "").trim();
  if (!new Set(["open_target", "pause", "resume"]).has(action)) throw new Error("该操作不允许使用真实点击。");
  const x = Number(message?.x);
  const y = Number(message?.y);
  if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0 || x > 10000 || y > 10000) {
    throw new Error("真实点击坐标无效。");
  }
  const result = await runAgentBrowserLowLevelAction("click", { x, y }, sender, tabId);
  return { ...result, action, targetId: String(message?.target_id || "") };
}

async function executeQianchuanAgentRealAction(message, sender) {
  const gate = await requireQianchuanAgentMemberAuth();
  if (!gate.ok) {
    const error = new Error(gate.message);
    error.authRequired = true;
    throw error;
  }
  const tabId = Number(sender?.tab?.id);
  let hostname = "";
  try { hostname = new URL(String(sender?.tab?.url || "")).hostname; } catch {}
  if (!Number.isInteger(tabId) || tabId <= 0 || hostname !== "qianchuan.jinritemai.com") {
    throw new Error("AI 接管仅允许在发起请求的当前千川标签页执行。");
  }
  const businessAction = String(message?.business_action || message?.businessAction || "").trim();
  const allowedBusinessActions = new Set([
    "open_target", "pause", "resume", "set_budget", "set_roi", "search_target",
    "confirm_platform", "navigate_scene", "navigate_tools_account_violations", "readonly_observe"
  ]);
  if (!allowedBusinessActions.has(businessAction)) throw new Error("该千川业务动作不允许使用 AI 接管。");
  const action = String(message?.action || "").trim().toLowerCase();
  if (!new Set(["click", "move", "type", "key", "screenshot"]).has(action)) throw new Error("该浏览器级动作不在千川接管白名单内。");
  if (businessAction === "readonly_observe" && action !== "screenshot") {
    throw new Error("只读观测只允许截取当前千川画面，不能点击或输入。");
  }
  const payload = { ...(message?.payload && typeof message.payload === "object" ? message.payload : {}) };
  if (action === "click" || action === "move" || action === "type") {
    const x = Number(payload.x);
    const y = Number(payload.y);
    if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0 || x > 10000 || y > 10000) {
      throw new Error("AI 接管目标坐标无效。");
    }
  }
  if (action === "type") {
    payload.text = String(payload.text ?? payload.value ?? "").slice(0, 256);
    if (!payload.text) throw new Error("AI 接管输入内容为空。");
  }
  if (action === "key" && !new Set(["Enter", "Escape", "Tab", "Backspace"]).has(String(payload.key || ""))) {
    throw new Error("该按键不在千川接管白名单内。");
  }
  const result = await runAgentBrowserLowLevelAction(action, payload, sender, tabId);
  return { ...result, businessAction, lowLevelAction: action };
}

async function resolveAgentBrowserLowLevelTarget(tabId, payload = {}, options = {}) {
  const pointPayload = normalizeAgentBrowserPointPayload(payload);
  if (!options.ignorePayloadPoint && hasAgentBrowserPoint(pointPayload)) {
    return {
      point: {
        x: Math.max(0, Number(pointPayload.x) || 0),
        y: Math.max(0, Number(pointPayload.y) || 0)
      },
      rect: null,
      target: null,
      editable: false,
      disabled: false,
      frameChain: []
    };
  }
  if (hasAgentBrowserTargetSpec(payload)) {
    await ensureAgentBrowserControlScript(tabId);
    const response = await sendAgentBrowserControlMessage(tabId, {
      type: "MMW_AGENT_CONTROL_RESOLVE_TARGET",
      payload
    });
    if (!response?.ok) throw new Error(response?.error || "定位页面目标失败");
    if (response.result?.point) return response.result;
  }
  if (options.useViewportCenter) {
    return { point: await getAgentBrowserViewportCenter(tabId), rect: null, target: null, editable: false, disabled: false, frameChain: [] };
  }
  if (options.requireTarget) {
    throw new Error("未找到可操作的页面目标");
  }
  return null;
}

async function getAgentBrowserViewportCenter(tabId) {
  await ensureAgentBrowserControlScript(tabId);
  const response = await sendAgentBrowserControlMessage(tabId, { type: "MMW_AGENT_CONTROL_GET_STATE" }).catch(() => null);
  const width = Number(response?.result?.viewport?.width) || 1280;
  const height = Number(response?.result?.viewport?.height) || 720;
  return {
    x: Math.max(0, Math.round(width / 2)),
    y: Math.max(0, Math.round(height / 2))
  };
}

function hasAgentBrowserTargetSpec(payload = {}) {
  return Boolean(String(payload?.ref || "").trim() || String(payload?.selector || "").trim() || String(payload?.text || "").trim());
}

function hasAgentBrowserPoint(payload = {}) {
  return Number.isFinite(Number(payload?.x)) && Number.isFinite(Number(payload?.y));
}

function normalizeAgentBrowserPointPayload(payload = {}) {
  if (payload?.point && Number.isFinite(Number(payload.point.x)) && Number.isFinite(Number(payload.point.y))) {
    return { ...payload, x: Number(payload.point.x), y: Number(payload.point.y) };
  }
  if (Number.isFinite(Number(payload?.clientX)) && Number.isFinite(Number(payload?.clientY))) {
    return { ...payload, x: Number(payload.clientX), y: Number(payload.clientY) };
  }
  return payload;
}

function hasAgentBrowserExplicitPoint(payload = {}) {
  if (payload?.point && Number.isFinite(Number(payload.point.x)) && Number.isFinite(Number(payload.point.y))) return true;
  return Number.isFinite(Number(payload?.clientX)) && Number.isFinite(Number(payload?.clientY));
}

function normalizeAgentBrowserScrollTargetPayload(payload = {}) {
  if (payload?.point && Number.isFinite(Number(payload.point.x)) && Number.isFinite(Number(payload.point.y))) {
    return { ...payload, x: Number(payload.point.x), y: Number(payload.point.y) };
  }
  if (Number.isFinite(Number(payload?.clientX)) && Number.isFinite(Number(payload?.clientY))) {
    return { ...payload, x: Number(payload.clientX), y: Number(payload.clientY) };
  }
  return payload;
}

async function withAgentBrowserDebugger(tabId, runner) {
  const debugTarget = { tabId: Number(tabId) };
  await chromeDebuggerCallback(
    (done) => chrome.debugger.attach(debugTarget, AGENT_BROWSER_DEBUGGER_PROTOCOL_VERSION, done),
    "浏览器级接管附加失败，请关闭其他调试器后重试"
  );
  try {
    return await runner(debugTarget);
  } finally {
    await chromeDebuggerCallback(
      (done) => chrome.debugger.detach(debugTarget, done),
      "浏览器级接管释放失败"
    ).catch(() => null);
  }
}

function chromeDebuggerCallback(invoker, fallbackMessage) {
  return new Promise((resolve, reject) => {
    invoker((result) => {
      const error = chrome.runtime.lastError;
      if (error) {
        reject(new Error(error.message || fallbackMessage));
        return;
      }
      resolve(result);
    });
  });
}

function chromeDebuggerSendCommand(debugTarget, method, params = {}, fallbackMessage = "") {
  return chromeDebuggerCallback(
    (done) => chrome.debugger.sendCommand(debugTarget, method, params, done),
    fallbackMessage || `${method} failed`
  );
}

async function dispatchAgentBrowserPointerMove(debugTarget, point) {
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchMouseEvent", {
    type: "mouseMoved",
    x: Math.max(0, Math.round(Number(point?.x) || 0)),
    y: Math.max(0, Math.round(Number(point?.y) || 0)),
    button: "none",
    buttons: 0,
    pointerType: "mouse"
  }, "浏览器级移动失败");
}

async function dispatchAgentBrowserMouseClick(debugTarget, point) {
  const x = Math.max(0, Math.round(Number(point?.x) || 0));
  const y = Math.max(0, Math.round(Number(point?.y) || 0));
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchMouseEvent", {
    type: "mousePressed",
    x,
    y,
    button: "left",
    buttons: 1,
    clickCount: 1,
    pointerType: "mouse"
  }, "浏览器级按下失败");
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchMouseEvent", {
    type: "mouseReleased",
    x,
    y,
    button: "left",
    buttons: 0,
    clickCount: 1,
    pointerType: "mouse"
  }, "浏览器级点击失败");
}

async function dispatchAgentBrowserWheel(debugTarget, point, deltaX = 0, deltaY = 0) {
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchMouseEvent", {
    type: "mouseWheel",
    x: Math.max(0, Math.round(Number(point?.x) || 0)),
    y: Math.max(0, Math.round(Number(point?.y) || 0)),
    deltaX: Number(deltaX) || 0,
    deltaY: Number(deltaY) || 0,
    pointerType: "mouse"
  }, "浏览器级滚动失败");
}

async function dispatchAgentBrowserKeyPress(debugTarget, payload = {}) {
  const descriptor = describeAgentBrowserKey(payload.key || "", payload.code);
  const modifiers = computeAgentBrowserKeyModifiers(payload);
  const keyDown = {
    type: descriptor.text && !modifiers ? "keyDown" : "rawKeyDown",
    key: descriptor.key,
    code: descriptor.code,
    windowsVirtualKeyCode: descriptor.windowsVirtualKeyCode,
    nativeVirtualKeyCode: descriptor.windowsVirtualKeyCode,
    modifiers
  };
  if (descriptor.text && !modifiers) {
    keyDown.text = descriptor.text;
    keyDown.unmodifiedText = descriptor.text;
  }
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchKeyEvent", keyDown, "浏览器级按键失败");
  await chromeDebuggerSendCommand(debugTarget, "Input.dispatchKeyEvent", {
    type: "keyUp",
    key: descriptor.key,
    code: descriptor.code,
    windowsVirtualKeyCode: descriptor.windowsVirtualKeyCode,
    nativeVirtualKeyCode: descriptor.windowsVirtualKeyCode,
    modifiers
  }, "浏览器级按键释放失败");
}

function describeAgentBrowserKey(rawKey, rawCode = "") {
  const key = String(rawKey || "").trim();
  if (!key) throw new Error("key is required");
  const special = {
    Enter: { key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 },
    Tab: { key: "Tab", code: "Tab", windowsVirtualKeyCode: 9 },
    Escape: { key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 },
    Esc: { key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 },
    Backspace: { key: "Backspace", code: "Backspace", windowsVirtualKeyCode: 8 },
    Delete: { key: "Delete", code: "Delete", windowsVirtualKeyCode: 46 },
    ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", windowsVirtualKeyCode: 37 },
    ArrowRight: { key: "ArrowRight", code: "ArrowRight", windowsVirtualKeyCode: 39 },
    ArrowUp: { key: "ArrowUp", code: "ArrowUp", windowsVirtualKeyCode: 38 },
    ArrowDown: { key: "ArrowDown", code: "ArrowDown", windowsVirtualKeyCode: 40 },
    Home: { key: "Home", code: "Home", windowsVirtualKeyCode: 36 },
    End: { key: "End", code: "End", windowsVirtualKeyCode: 35 },
    PageUp: { key: "PageUp", code: "PageUp", windowsVirtualKeyCode: 33 },
    PageDown: { key: "PageDown", code: "PageDown", windowsVirtualKeyCode: 34 },
    Space: { key: " ", code: "Space", windowsVirtualKeyCode: 32 },
    " ": { key: " ", code: "Space", windowsVirtualKeyCode: 32 }
  };
  if (special[key]) return special[key];
  if (key.length === 1) {
    const upper = key.toUpperCase();
    const isLetter = /^[A-Z]$/.test(upper);
    const isDigit = /^[0-9]$/.test(key);
    return {
      key,
      code: rawCode || (isLetter ? `Key${upper}` : (isDigit ? `Digit${key}` : "")),
      windowsVirtualKeyCode: upper.charCodeAt(0),
      text: key
    };
  }
  return {
    key,
    code: String(rawCode || key),
    windowsVirtualKeyCode: 0
  };
}

function computeAgentBrowserKeyModifiers(payload = {}) {
  let modifiers = 0;
  if (payload.altKey === true) modifiers |= 1;
  if (payload.ctrlKey === true) modifiers |= 2;
  if (payload.metaKey === true) modifiers |= 4;
  if (payload.shiftKey === true) modifiers |= 8;
  return modifiers;
}

async function getAgentBrowserLocalBridgeClientId() {
  if (agentBrowserLocalBridgeState.clientId) return agentBrowserLocalBridgeState.clientId;
  const values = await chrome.storage.local.get([AGENT_BROWSER_BRIDGE_CLIENT_ID_STORAGE_KEY]).catch(() => ({}));
  const existing = String(values[AGENT_BROWSER_BRIDGE_CLIENT_ID_STORAGE_KEY] || "").trim();
  if (existing) {
    agentBrowserLocalBridgeState.clientId = existing;
    return existing;
  }
  const nextValue = createAgentBrowserLocalBridgeClientId();
  agentBrowserLocalBridgeState.clientId = nextValue;
  await chrome.storage.local.set({ [AGENT_BROWSER_BRIDGE_CLIENT_ID_STORAGE_KEY]: nextValue }).catch(() => {});
  return nextValue;
}

function createAgentBrowserLocalBridgeClientId() {
  if (globalThis.crypto?.randomUUID) return `mmw-${globalThis.crypto.randomUUID()}`;
  return `mmw-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}

function stopAgentBrowserLocalBridgeLoop() {
  agentBrowserLocalBridgeLoopId += 1;
  if (agentBrowserLocalBridgeAbortController) {
    agentBrowserLocalBridgeAbortController.abort();
    agentBrowserLocalBridgeAbortController = null;
  }
  agentBrowserLocalBridgeState.running = false;
  agentBrowserLocalBridgeState.available = false;
  agentBrowserLocalBridgeState.registered = false;
  agentBrowserLocalBridgeState.connected = false;
  agentBrowserLocalBridgeLoopPromise = null;
}

async function ensureAgentBrowserLocalBridgeLoop() {
  if (!await isAgentBrowserControlEnabled()) {
    stopAgentBrowserLocalBridgeLoop();
    return snapshotAgentBrowserLocalBridgeState();
  }
  if (agentBrowserLocalBridgeLoopPromise) return snapshotAgentBrowserLocalBridgeState();
  const loopId = ++agentBrowserLocalBridgeLoopId;
  const controller = new AbortController();
  agentBrowserLocalBridgeAbortController = controller;
  agentBrowserLocalBridgeState.running = true;
  agentBrowserLocalBridgeLoopPromise = runAgentBrowserLocalBridgeLoop(loopId, controller.signal)
    .catch((error) => {
      if (!controller.signal.aborted) {
        agentBrowserLocalBridgeState.lastError = error?.message || String(error);
        agentBrowserLocalBridgeState.lastErrorAt = Date.now();
      }
    })
    .finally(() => {
      if (agentBrowserLocalBridgeLoopId === loopId) {
        agentBrowserLocalBridgeState.running = false;
        agentBrowserLocalBridgeLoopPromise = null;
        if (agentBrowserLocalBridgeAbortController === controller) {
          agentBrowserLocalBridgeAbortController = null;
        }
      }
    });
  return snapshotAgentBrowserLocalBridgeState();
}

async function runAgentBrowserLocalBridgeLoop(loopId, signal) {
  const clientId = await getAgentBrowserLocalBridgeClientId();
  agentBrowserLocalBridgeState.clientId = clientId;
  while (!signal.aborted && loopId === agentBrowserLocalBridgeLoopId) {
    if (!await isAgentBrowserControlEnabled()) break;
    try {
      await registerAgentBrowserLocalBridgeClient(signal);
      const next = await requestAgentBrowserLocalBridgeNextCommand(clientId, signal);
      const now = Date.now();
      if (!next?.command) {
        if (agentBrowserLocalBridgeState.connected && now - agentBrowserLocalBridgeState.lastCommandAt > AGENT_BROWSER_LOCAL_BRIDGE_CONNECTED_IDLE_MS) {
          agentBrowserLocalBridgeState.connected = false;
        }
        continue;
      }
      const command = next.command;
      agentBrowserLocalBridgeState.available = true;
      agentBrowserLocalBridgeState.registered = true;
      agentBrowserLocalBridgeState.connected = true;
      agentBrowserLocalBridgeState.lastSeenAt = now;
      agentBrowserLocalBridgeState.lastCommandAt = now;
      agentBrowserLocalBridgeState.lastCommand = normalizeAgentBrowserAction(command.action || "");
      agentBrowserLocalBridgeState.lastError = "";
      agentBrowserLocalBridgeState.lastErrorAt = 0;
      let ok = true;
      let result = null;
      let errorText = "";
      try {
        result = await handleAgentBrowserLocalBridgeCommand(command);
      } catch (error) {
        ok = false;
        errorText = error?.message || String(error);
      }
      await completeAgentBrowserLocalBridgeCommand(clientId, command.commandId || command.id, {
        ok,
        result,
        error: errorText
      }, signal).catch(() => {});
    } catch (error) {
      if (signal.aborted) break;
      agentBrowserLocalBridgeState.available = false;
      agentBrowserLocalBridgeState.registered = false;
      agentBrowserLocalBridgeState.connected = false;
      agentBrowserLocalBridgeState.lastError = error?.message || String(error);
      agentBrowserLocalBridgeState.lastErrorAt = Date.now();
      await sleepMs(AGENT_BROWSER_LOCAL_BRIDGE_RETRY_MS, signal).catch(() => {});
    }
  }
}

async function registerAgentBrowserLocalBridgeClient(signal) {
  const payload = await buildAgentBrowserLocalBridgeClientPayload();
  const response = await agentBrowserLocalBridgeRequest("/clients/register", {
    method: "POST",
    body: payload,
    signal,
    timeoutMs: AGENT_BROWSER_LOCAL_BRIDGE_REQUEST_TIMEOUT_MS
  });
  agentBrowserLocalBridgeState.available = true;
  agentBrowserLocalBridgeState.registered = response?.ok !== false;
  agentBrowserLocalBridgeState.lastSeenAt = Date.now();
  agentBrowserLocalBridgeState.lastError = "";
  agentBrowserLocalBridgeState.lastErrorAt = 0;
  return response;
}

async function buildAgentBrowserLocalBridgeClientPayload() {
  const state = await getAgentBrowserControlState(null, null);
  return {
    clientId: await getAgentBrowserLocalBridgeClientId(),
    extensionId: chrome.runtime.id,
    version: CURRENT_PLUGIN_VERSION,
    enabled: state.enabled === true,
    bridgeReady: state.bridgeReady === true,
    tab: state.tab || null,
    page: state.page || null,
    capabilities: {
      inputMode: "browser-debugger",
      lowLevelActions: Array.from(AGENT_BROWSER_LOW_LEVEL_ACTIONS),
      contentActions: Array.from(AGENT_BROWSER_CONTENT_ACTIONS),
      backgroundActions: Array.from(AGENT_BROWSER_BACKGROUND_ACTIONS),
      localOnlyActions: Array.from(AGENT_BROWSER_LOCAL_ONLY_ACTIONS)
    }
  };
}

async function requestAgentBrowserLocalBridgeNextCommand(clientId, signal) {
  return agentBrowserLocalBridgeRequest(`/clients/${encodeURIComponent(clientId)}/next?timeoutMs=${AGENT_BROWSER_LOCAL_BRIDGE_POLL_TIMEOUT_MS}`, {
    method: "GET",
    signal,
    timeoutMs: AGENT_BROWSER_LOCAL_BRIDGE_POLL_TIMEOUT_MS + 5000
  });
}

async function completeAgentBrowserLocalBridgeCommand(clientId, commandId, payload, signal) {
  if (!commandId) return null;
  return agentBrowserLocalBridgeRequest(`/clients/${encodeURIComponent(clientId)}/result`, {
    method: "POST",
    body: {
      commandId,
      ok: payload?.ok === true,
      result: payload?.result ?? null,
      error: payload?.error || ""
    },
    signal,
    timeoutMs: AGENT_BROWSER_LOCAL_BRIDGE_REQUEST_TIMEOUT_MS
  });
}

async function handleAgentBrowserLocalBridgeCommand(command) {
  const action = normalizeAgentBrowserAction(command?.action);
  const payload = command?.payload && typeof command.payload === "object" && !Array.isArray(command.payload)
    ? command.payload
    : {};
  const tabId = Number(command?.tabId);
  const normalizedTabId = Number.isInteger(tabId) && tabId > 0 ? tabId : null;
  if (AGENT_BROWSER_LOCAL_ONLY_ACTIONS.has(action)) {
    return runAgentBrowserLocalOnlyAction(action, payload);
  }
  const result = await runAgentBrowserControl({
    action,
    payload,
    tabId: normalizedTabId
  }, null);
  const touchedTabId = pickAgentBrowserActionTabId(result, payload, normalizedTabId);
  if (touchedTabId) {
    await touchAgentBrowserControlTab(touchedTabId, action).catch(() => {});
  }
  return result;
}

async function agentBrowserLocalBridgeRequest(path, options = {}) {
  const method = String(options.method || "GET").toUpperCase();
  const timeoutMs = Math.max(1000, Number(options.timeoutMs) || AGENT_BROWSER_LOCAL_BRIDGE_REQUEST_TIMEOUT_MS);
  const controller = new AbortController();
  const parentSignal = options.signal || null;
  let settled = false;
  const abortFromParent = () => {
    if (!settled) controller.abort();
  };
  if (parentSignal) {
    if (parentSignal.aborted) controller.abort();
    else parentSignal.addEventListener("abort", abortFromParent, { once: true });
  }
  const timeoutId = setTimeout(() => {
    if (!settled) controller.abort();
  }, timeoutMs);
  try {
    const response = await fetch(`${AGENT_BROWSER_LOCAL_BRIDGE_BASE_URL}${path}`, {
      method,
      cache: "no-store",
      headers: options.body ? { "content-type": "application/json" } : undefined,
      body: options.body ? JSON.stringify(options.body) : undefined,
      signal: controller.signal
    });
    const text = await response.text();
    let data = {};
    if (text) {
      try {
        data = JSON.parse(text);
      } catch {
        throw new Error(text.slice(0, 240) || "本地浏览器会话桥返回内容无效");
      }
    }
    if (!response.ok) {
      throw new Error(data?.error || data?.message || `本地浏览器会话桥请求失败 (${response.status})`);
    }
    return data;
  } catch (error) {
    if (controller.signal.aborted) {
      if (parentSignal?.aborted) throw new Error("本地浏览器会话桥轮询已停止");
      throw new Error("本地浏览器会话桥未启动或请求超时");
    }
    throw error;
  } finally {
    settled = true;
    clearTimeout(timeoutId);
    if (parentSignal) parentSignal.removeEventListener("abort", abortFromParent);
  }
}

function sleepMs(ms, signal = null) {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      cleanup();
      resolve();
    }, Math.max(0, Number(ms) || 0));
    const onAbort = () => {
      cleanup();
      reject(new Error("aborted"));
    };
    const cleanup = () => {
      clearTimeout(timeoutId);
      if (signal) signal.removeEventListener("abort", onAbort);
    };
    if (signal) {
      if (signal.aborted) {
        cleanup();
        reject(new Error("aborted"));
        return;
      }
      signal.addEventListener("abort", onAbort, { once: true });
    }
  });
}

async function goAgentBrowserHistory(tabId, direction) {
  const method = direction === "forward" ? chrome.tabs.goForward : chrome.tabs.goBack;
  if (typeof method === "function") {
    await chromeTabCallback((done) => method.call(chrome.tabs, tabId, done), direction === "forward" ? "前进失败" : "后退失败");
    return;
  }
  await chrome.scripting.executeScript({
    target: { tabId },
    func: (nextDirection) => {
      if (nextDirection === "forward") window.history.forward();
      else window.history.back();
    },
    args: [direction]
  });
}

function chromeTabCallback(invoker, fallbackMessage) {
  return new Promise((resolve, reject) => {
    invoker((result) => {
      const error = chrome.runtime.lastError;
      if (error) {
        reject(new Error(error.message || fallbackMessage));
        return;
      }
      resolve(result);
    });
  });
}

function summarizeAgentBrowserTab(tab) {
  return {
    id: tab?.id || 0,
    windowId: tab?.windowId || 0,
    active: tab?.active === true,
    title: tab?.title || "",
    url: tab?.url || "",
    status: tab?.status || ""
  };
}

function normalizeAgentBrowserAction(value) {
  return String(value || "").trim().toLowerCase().replace(/-/g, "_");
}

function normalizeAgentBrowserUrl(rawUrl) {
  const value = String(rawUrl || "").trim();
  if (!value) throw new Error("url is required");
  let url;
  try {
    url = new URL(value);
  } catch {
    throw new Error("浏览器地址无效");
  }
  if (url.protocol === "chrome-extension:" && url.hostname === chrome.runtime.id) {
    return url.toString();
  }
  if (!["http:", "https:"].includes(url.protocol)) {
    throw new Error("AI接管仅允许打开 HTTP/HTTPS 页面");
  }
  return url.toString();
}

function isAgentBrowserControllableUrl(url) {
  return Boolean(url) && /^https?:\/\//i.test(String(url || ""));
}

async function ensureDefaults() {
  const current = await chrome.storage.local.get([
    STORAGE_KEYS.titleRules,
    STORAGE_KEYS.compassJump,
    STORAGE_KEYS.douyinDownload,
    STORAGE_KEYS.activation,
    STORAGE_KEYS.entitlement,
    STORAGE_KEYS.auth,
    STORAGE_KEYS.apiBaseUrl,
    STORAGE_KEYS.settingsVersion
  ]);

  const payload = {};
  const existingRules = Array.isArray(current[STORAGE_KEYS.titleRules]) ? current[STORAGE_KEYS.titleRules] : [];
  const existingIds = new Set(existingRules.map((rule) => rule.id));
  const missingRules = BUILTIN_RULES.filter((rule) => !existingIds.has(rule.id));
  if (missingRules.length) payload[STORAGE_KEYS.titleRules] = [...missingRules, ...existingRules];
  if (current[STORAGE_KEYS.settingsVersion] !== DEFAULT_SETTINGS_VERSION) {
    payload[STORAGE_KEYS.settingsVersion] = DEFAULT_SETTINGS_VERSION;
  }
  if (typeof current[STORAGE_KEYS.compassJump] === "undefined") {
    payload[STORAGE_KEYS.compassJump] = true;
  }
  if (typeof current[STORAGE_KEYS.douyinDownload] === "undefined") payload[STORAGE_KEYS.douyinDownload] = true;

  const auth = normalizeAuthState(current[STORAGE_KEYS.auth]);
  const baseUrl = normalizeBaseUrl(current[STORAGE_KEYS.apiBaseUrl] || DEFAULT_API_BASE_URL);
  const entitlement = entitlementFromAuth(auth);
  if (!current[STORAGE_KEYS.auth]) payload[STORAGE_KEYS.auth] = auth;
  if (current[STORAGE_KEYS.apiBaseUrl] !== baseUrl) payload[STORAGE_KEYS.apiBaseUrl] = baseUrl;
  payload[STORAGE_KEYS.entitlement] = entitlement;
  payload[STORAGE_KEYS.activation] = activationFromEntitlement(entitlement);

  if (Object.keys(payload).length) await chrome.storage.local.set(payload);
}

async function getPluginEntitlement() {
  const auth = await getPluginAuthState({ refresh: false });
  const entitlement = entitlementFromAuth(auth);
  await chrome.storage.local.set({
    [STORAGE_KEYS.entitlement]: entitlement,
    [STORAGE_KEYS.activation]: activationFromEntitlement(entitlement)
  });
  return entitlement;
}

async function getPluginUpdateInfo() {
  try {
    const baseUrl = await getStoredBaseUrl();
    const payload = await requestJson(baseUrl, "GET", "/api/system/plugin-package", null);
    const data = unwrapPayload(payload);
    const rawVersion = String(data?.version || data?.latest_version || data?.file_name || "").trim();
    const latestVersion = normalizeComparableVersion(rawVersion);
    const downloadUrl = String(data?.download_url || "").trim();
    return {
      checkedAt: Date.now(),
      available: Boolean(data?.available),
      currentVersion: CURRENT_PLUGIN_VERSION,
      latestVersion,
      rawVersion,
      fileName: String(data?.file_name || "").trim(),
      downloadUrl,
      hasUpdate: Boolean(latestVersion) && compareVersions(latestVersion, CURRENT_PLUGIN_VERSION) > 0
    };
  } catch (error) {
    return {
      checkedAt: Date.now(),
      available: false,
      currentVersion: CURRENT_PLUGIN_VERSION,
      latestVersion: "",
      rawVersion: "",
      fileName: "",
      downloadUrl: "",
      hasUpdate: false,
      checkFailed: true,
      error: error?.message || "插件更新检查失败"
    };
  }
}

async function getTutorialNavigation() {
  const baseUrl = await getStoredBaseUrl();
  const payload = await requestJson(baseUrl, "GET", "/api/system/tutorial-navigation", null);
  const data = unwrapPayload(payload);
  const url = String(data?.url || "").trim();
  if (url && !/^https?:\/\//i.test(url)) {
    throw new Error("教程导航地址无效");
  }
  return {
    url,
    revision: Number(data?.revision || 0),
    checkedAt: Date.now()
  };
}

function normalizeEntitlement(value) {
  return {
    active: Boolean(value?.active),
    expireTime: Number(value?.expireTime || 0),
    source: value?.source || "remote_account",
    label: value?.label || (value?.active ? "账号会员有效" : "请先登录账号")
  };
}

function activationFromEntitlement(entitlement) {
  return {
    code: "SOFTWARE_ACCOUNT_RESERVED",
    deviceId: "software-account",
    activated: Boolean(entitlement.active),
    expireTime: entitlement.active ? Number(entitlement.expireTime || 0) : Date.now() - 1000,
    source: entitlement.source,
    label: entitlement.label
  };
}

function handleAuthMessage(message, sendResponse) {
  if (message.type === "PLUGIN_AUTH_STATUS") {
    getPluginAuthState({ refresh: message.refresh === true })
      .then((auth) => {
        if (auth?.token) sendPluginHeartbeat().catch(() => {});
        sendResponse({ ok: true, auth: sanitizeAuthForUi(auth) });
      })
      .catch((error) => sendResponse({ ok: false, error: error.message || "账号状态获取失败" }));
    return true;
  }
  if (message.type === "PLUGIN_AUTH_LOGIN") {
    loginPluginAccount(message)
      .then((result) => sendResponse({ ok: true, auth: sanitizeAuthForUi(result.auth), message: result.message }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "登录失败" }));
    return true;
  }
  if (message.type === "PLUGIN_AUTH_LOGOUT") {
    logoutPluginAccount()
      .then((auth) => sendResponse({ ok: true, auth: sanitizeAuthForUi(auth) }))
      .catch((error) => sendResponse({ ok: false, error: error.message || "退出失败" }));
    return true;
  }
  return false;
}

function handleAdminDebugMessage(message, sender, sendResponse) {
  if (!message.type || !String(message.type).startsWith("ADMIN_DEBUG_")) return false;

  requireAdminDebugAccess()
    .then(async (gate) => {
      if (!gate.ok) {
        sendResponse({
          ok: false,
          admin: false,
          error: gate.message,
          auth: sanitizeAuthForUi(gate.auth)
        });
        return;
      }

      if (message.type === "ADMIN_DEBUG_STATUS" || message.type === "ADMIN_DEBUG_GET") {
        const status = await MMW_ADMIN_DEBUG.getStatus();
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_SET_ENABLED") {
        const status = await MMW_ADMIN_DEBUG.setEnabled(message.enabled === true);
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_SET_PAGE_PROBE") {
        const status = await MMW_ADMIN_DEBUG.setPageProbeEnabled(message.enabled === true);
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_SET_SCREEN_OBSERVER") {
        if (message.enabled === true) {
          await MMW_ADMIN_DEBUG.setEnabled(true);
        }
        const status = await MMW_ADMIN_DEBUG.setScreenObserverEnabled(message.enabled === true);
        await setAdminScreenObserverEnabled(message.enabled === true).catch(() => {});
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_SET_REFERENCE_CAPTURE") {
        if (message.enabled === true) {
          await MMW_ADMIN_DEBUG.setEnabled(true);
          await MMW_ADMIN_DEBUG.setPageProbeEnabled(true);
        }
        const status = await MMW_ADMIN_DEBUG.setReferenceCapture({
          enabled: message.enabled === true,
          rules: isObject(message.rules) ? message.rules : {},
          packageSummary: isObject(message.packageSummary) ? message.packageSummary : {},
          session: isObject(message.session) ? message.session : null
        });
        if (status?.screenObserverEnabled === true) {
          scheduleAdminScreenObserverLoop(400);
        }
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_CAPTURE_SCREEN_OBSERVER_FRAME") {
        const status = await MMW_ADMIN_DEBUG.getStatus();
        await captureAdminScreenObserverFrame(message.reason || "manual", message.tabId, status, { force: true }).catch(() => {});
        const latestStatus = await MMW_ADMIN_DEBUG.getStatus();
        const adminPayload = await buildAgentBrowserAdminDebugPayload(latestStatus, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...latestStatus, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_CLEAR") {
        const status = await MMW_ADMIN_DEBUG.clear();
        const adminPayload = await buildAgentBrowserAdminDebugPayload(status, { includeLatestDataUrl: true, frameLimit: 6 });
        sendResponse({ ok: true, admin: true, ...status, ...adminPayload });
        return;
      }

      if (message.type === "ADMIN_DEBUG_LOG") {
        const entry = {
          ...(isObject(message.entry) ? message.entry : { payload: message.entry }),
          tabId: sender?.tab?.id,
          pageUrl: sender?.tab?.url || message.entry?.pageUrl || ""
        };
        pushAgentBrowserRuntimeLog(entry);
        await MMW_ADMIN_DEBUG.add(entry);
        sendResponse({ ok: true, admin: true });
        return;
      }

      sendResponse({ ok: false, admin: true, error: "未知调试指令" });
    })
    .catch((error) => sendResponse({ ok: false, admin: false, error: error.message || "调试权限校验失败" }));
  return true;
}

async function requireAdminDebugAccess() {
  const auth = await getPluginAuthState({ refresh: false });
  if (auth.loggedIn && hasPointsAdminAccess(auth)) {
    return { ok: true, auth };
  }
  return {
    ok: false,
    auth,
    message: "管理员调试仅积分管理员账号可用"
  };
}

function hasPointsAdminAccess(authInput) {
  const auth = normalizeAuthState(authInput);
  const user = isObject(auth.user) ? auth.user : {};
  const account = isObject(auth.account) ? auth.account : {};
  const userType = String(account.user_type || user.user_type || "").toLowerCase();
  return Boolean(
    isTruthyFlag(account.is_points_admin)
    || isTruthyFlag(user.is_points_admin)
    || isTruthyFlag(account.points_admin)
    || isTruthyFlag(user.points_admin)
    || userType === "admin"
    || userType === "administrator"
    || userType.includes("管理员")
  );
}

function isTruthyFlag(value) {
  if (value === true || value === 1) return true;
  return /^(1|true|yes|on)$/i.test(String(value || "").trim());
}

async function requireActiveAuth() {
  const auth = await getPluginAuthState({ refresh: false });
  if (isAuthUsable(auth)) {
    sendPluginHeartbeat().catch(() => {});
    if (Date.now() - Number(auth.checkedAt || 0) > AUTH_REFRESH_INTERVAL_MS) {
      refreshPluginAuth(auth).catch(() => {});
    }
    return { ok: true, auth };
  }
  return {
    ok: false,
    auth,
    message: auth.message || auth.entitlement?.label || "请先登录账号"
  };
}

async function requireQianchuanAgentMemberAuth() {
  const gate = await requireActiveAuth();
  if (gate.ok) return gate;
  return {
    ...gate,
    message: gate.auth?.loggedIn
      ? "千川 Agent 仅限有效会员使用，请先开通或续费会员"
      : "请先登录 MMW工作台会员账号"
  };
}

async function requestQianchuanAgentAccess() {
  const auth = await getPluginAuthState({ refresh: false });
  return {
    allowed: Boolean(isAuthUsable(auth))
  };
}

async function requireLoginAuth() {
  const auth = await getPluginAuthState({ refresh: false });
  if (auth.loggedIn) {
    sendPluginHeartbeat().catch(() => {});
    if (Date.now() - Number(auth.checkedAt || 0) > AUTH_REFRESH_INTERVAL_MS) {
      refreshPluginAuth(auth).catch(() => {});
    }
    return { ok: true, auth };
  }
  return {
    ok: false,
    auth,
    message: "请先登录账号"
  };
}

async function getPluginAuthState(options = {}) {
  const current = await chrome.storage.local.get([STORAGE_KEYS.auth, STORAGE_KEYS.apiBaseUrl]);
  const auth = normalizeAuthState(current[STORAGE_KEYS.auth], current[STORAGE_KEYS.apiBaseUrl]);
  if (options.refresh && auth.token) {
    try {
      return await refreshPluginAuth(auth);
    } catch (error) {
      if (String(error?.code || "") === REMOTE_LOGIN_CODE) {
        return normalizeAuthState(null);
      }
      const failed = normalizeAuthState({
        ...auth,
        message: error.message || "账号状态同步失败"
      }, auth.baseUrl);
      await setPluginAuthState(failed);
      return failed;
    }
  }
  return auth;
}

async function ensurePluginHeartbeatAlarm() {
  if (!chrome.alarms?.create) return;
  await chrome.alarms.create(PLUGIN_HEARTBEAT_ALARM, {
    delayInMinutes: PLUGIN_HEARTBEAT_PERIOD_MINUTES,
    periodInMinutes: PLUGIN_HEARTBEAT_PERIOD_MINUTES
  });
}

async function ensureQianchuanRemotePulse() {
  if (chrome.alarms?.create) {
    await chrome.alarms.create(QIANCHUAN_REMOTE_PULSE_ALARM, {
      delayInMinutes: QIANCHUAN_REMOTE_PULSE_PERIOD_MINUTES,
      periodInMinutes: QIANCHUAN_REMOTE_PULSE_PERIOD_MINUTES
    });
  }
  if (!qianchuanRemotePulseTimer) {
    qianchuanRemotePulseTimer = setInterval(
      () => pulseQianchuanAgentTabs().catch(() => {}),
      QIANCHUAN_REMOTE_PULSE_INTERVAL_MS
    );
  }
  await pulseQianchuanAgentTabs();
}

async function pulseQianchuanAgentTabs() {
  const tabs = await chrome.tabs.query({ url: "https://qianchuan.jinritemai.com/*" });
  const access = await requestQianchuanAgentAccess().catch(() => ({ allowed: false }));
  if (!access.allowed) {
    await Promise.all((tabs || []).map(async (tab) => {
      if (!Number.isInteger(tab.id) || tab.id <= 0) return null;
      try {
        return await chrome.tabs.sendMessage(tab.id, { type: "QIANCHUAN_AGENT_ACCESS_REVOKED" });
      } catch {
        return null;
      }
    }));
    return;
  }
  await Promise.all((tabs || []).map(async (tab) => {
    if (!Number.isInteger(tab.id) || tab.id <= 0) return null;
    try {
      return await chrome.tabs.sendMessage(tab.id, { type: "QIANCHUAN_AGENT_REMOTE_PULSE" });
    } catch {
      return reattachQianchuanAgentTab(tab.id);
    }
  }));
}

async function reattachQianchuanAgentTab(tabId) {
  if (qianchuanAgentReattachTabs.has(tabId)) return null;
  qianchuanAgentReattachTabs.add(tabId);
  try {
    await chrome.scripting.executeScript({
      target: { tabId },
      func: () => {
        document.querySelectorAll("#mmw-qianchuan-agent-root, .mmw-qca-takeover").forEach((node) => node.remove());
        document.documentElement.classList.remove("mmw-qca-is-taken-over", "mmw-qca-managed-loop");
      }
    });
    await chrome.scripting.insertCSS({ target: { tabId }, files: QIANCHUAN_AGENT_CONTENT_CSS });
    await chrome.scripting.executeScript({ target: { tabId }, files: QIANCHUAN_AGENT_CONTENT_SCRIPTS });
    return await chrome.tabs.sendMessage(tabId, { type: "QIANCHUAN_AGENT_REMOTE_PULSE" });
  } catch {
    return null;
  } finally {
    qianchuanAgentReattachTabs.delete(tabId);
  }
}

async function sendPluginHeartbeat(options = {}) {
  const force = options.force === true;
  const now = Date.now();
  if (!force && now - pluginHeartbeatLastAttemptAt < PLUGIN_HEARTBEAT_MIN_GAP_MS) return null;
  if (pluginHeartbeatPromise) return pluginHeartbeatPromise;
  pluginHeartbeatLastAttemptAt = now;
  pluginHeartbeatPromise = (async () => {
    const auth = await getPluginAuthState({ refresh: false });
    if (!auth.token) return null;
    try {
      const query = await pluginDeviceQueryString();
      await requestJson(auth.baseUrl, "GET", `/api/plugin/activity/heartbeat?${query}`, null, auth.token, 5000, { notifyMaintenance: false });
      return true;
    } catch {
      return false;
    }
  })();
  try {
    return await pluginHeartbeatPromise;
  } finally {
    pluginHeartbeatPromise = null;
  }
}

async function getPluginDeviceInfo() {
  const current = await chrome.storage.local.get([STORAGE_KEYS.deviceId]).catch(() => ({}));
  let deviceId = String(current?.[STORAGE_KEYS.deviceId] || "").trim();
  if (!isPluginDeviceId(deviceId)) {
    deviceId = createPluginDeviceId();
    await chrome.storage.local.set({ [STORAGE_KEYS.deviceId]: deviceId });
  }
  const platform = await getRuntimePlatformInfo();
  const platformText = [platform.os, platform.arch].filter(Boolean).join("/");
  return {
    deviceId,
    deviceName: platformText ? `Chrome插件 ${platformText}` : "Chrome插件"
  };
}

async function pluginDeviceQueryString() {
  const device = await getPluginDeviceInfo();
  return new URLSearchParams({
    client_type: "chrome_plugin",
    plugin_mounted: "1",
    plugin_version: CURRENT_PLUGIN_VERSION,
    device_id: device.deviceId,
    device_name: device.deviceName
  }).toString();
}

function isPluginDeviceId(value) {
  return /^mmw-plugin-[a-f0-9]{32}$/.test(String(value || "").trim());
}

function createPluginDeviceId() {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
  return `mmw-plugin-${hex}`;
}

async function getRuntimePlatformInfo() {
  return await new Promise((resolve) => {
    try {
      if (!chrome.runtime?.getPlatformInfo) {
        resolve({});
        return;
      }
      chrome.runtime.getPlatformInfo((info) => {
        if (chrome.runtime.lastError) {
          resolve({});
          return;
        }
        resolve(info || {});
      });
    } catch {
      resolve({});
    }
  });
}

async function loginPluginAccount(message) {
  const username = String(message.username || "").trim();
  const password = String(message.password || "");
  if (!username || !password) throw new Error("请输入账号和密码");

  const baseUrl = normalizeBaseUrl(message.baseUrl || (await getStoredBaseUrl()));
  const device = await getPluginDeviceInfo();
  const payload = await requestJson(baseUrl, "POST", "/api/auth/login", {
    username,
    password,
    device_id: device.deviceId,
    device_name: device.deviceName,
    client_type: "chrome_plugin",
    plugin_version: CURRENT_PLUGIN_VERSION
  });
  const data = unwrapPayload(payload);
  const user = isObject(data?.user) ? data.user : (isObject(data) ? data : {});
  const token = String(data?.token || data?.access_token || payload?.token || payload?.access_token || "").trim();
  if (!token) throw new Error("登录接口没有返回授权信息");

  const account = await fetchPluginAccountStatus(baseUrl, token);

  const auth = buildAuthState({ baseUrl, token, user: { ...user, username: user.username || username }, account });
  await setPluginAuthState(auth);
  sendPluginHeartbeat({ force: true }).catch(() => {});
  return { auth, message: messageFromPayload(payload) || "登录成功" };
}

async function refreshPluginAuth(authInput) {
  const auth = normalizeAuthState(authInput);
  if (!auth.token) return auth;
  const account = await fetchPluginAccountStatus(auth.baseUrl, auth.token, auth.account);
  const refreshed = buildAuthState({
    baseUrl: auth.baseUrl,
    token: auth.token,
    user: auth.user,
    account
  });
  await setPluginAuthState(refreshed);
  sendPluginHeartbeat({ force: true }).catch(() => {});
  return refreshed;
}

async function logoutPluginAccount() {
  const current = await getPluginAuthState({ refresh: false });
  if (current.token) {
    await requestJson(current.baseUrl, "POST", "/api/auth/logout", {}, current.token).catch(() => {});
  }
  const auth = normalizeAuthState(null);
  await setPluginAuthState(auth);
  return auth;
}

function pluginAccountRiskPayload(account) {
  if (!isObject(account)) return null;
  const payload = {};
  for (const key of ["points_balance", "expires_at", "member_status", "license_status", "is_admin", "is_points_admin"]) {
    const value = account[key];
    if (value !== undefined && value !== null && value !== "") payload[key] = value;
  }
  return Object.keys(payload).length ? payload : null;
}

async function checkPluginAccountRisk(baseUrl, token, account) {
  const payload = pluginAccountRiskPayload(account);
  if (!token || !payload) return null;
  const response = await requestJson(baseUrl, "POST", "/api/account/risk-check", payload, token);
  const data = unwrapPayload(response);
  return isObject(data?.risk_warning) ? data.risk_warning : null;
}

async function fetchPluginAccountStatus(baseUrl, token, localAccount = null) {
  const riskWarning = await checkPluginAccountRisk(baseUrl, token, localAccount);
  const query = await pluginDeviceQueryString();
  const payload = await requestJson(baseUrl, "GET", `/api/account/status?${query}`, null, token);
  const data = unwrapPayload(payload);
  const account = isObject(data?.account) ? data.account : (isObject(data) ? data : {});
  if (riskWarning && isObject(account)) account.risk_warning = riskWarning;
  return account;
}

async function requestSourceTrace(message, options = {}) {
  const auth = options.auth || await requireSourceTraceFeatureAccess();
  if (!hasPluginFeatureAccess(auth, "source_trace")) {
    throw new Error(pluginFeatureDeniedReason(auth, "source_trace"));
  }
  const url = String(message.url || message.link || message.pageUrl || "").trim();
  if (!url) throw new Error("请先提供要溯源的抖音链接");
  const payload = await requestJson(auth.baseUrl, "POST", "/api/plugin/source-trace", { url }, auth.token, 45000);
  const data = unwrapPayload(payload);
  return data;
}

async function requireEcommerceToolFeatureAccess(options = {}) {
  const auth = await getPluginAuthState({ refresh: true });
  const activeRequired = options.active === true;
  const accessReady = activeRequired ? isAuthUsable(auth) : auth.loggedIn;
  if (!accessReady) {
    const error = new Error(
      activeRequired
        ? (auth.message || auth.entitlement?.label || "电商工具仅限有效会员使用")
        : "请先登录 MMW工作台账号"
    );
    error.authRequired = true;
    error.auth = sanitizeAuthForUi(auth);
    throw error;
  }
  if (!hasPluginFeatureAccess(auth, "ecommerce_tool")) {
    const error = new Error(pluginFeatureDeniedReason(auth, "ecommerce_tool"));
    error.authRequired = true;
    error.auth = sanitizeAuthForUi(auth);
    throw error;
  }
  sendPluginHeartbeat().catch(() => {});
  return { ok: true, auth };
}

async function requireSourceTraceFeatureAccess() {
  const auth = await getPluginAuthState({ refresh: true });
  if (!auth.token) throw new Error("请先登录 MMW工作台账号");
  if (!hasPluginFeatureAccess(auth, "source_trace")) {
    throw new Error(pluginFeatureDeniedReason(auth, "source_trace"));
  }
  return auth;
}

function hasPluginFeatureAccess(auth, featureKey) {
  return globalThis.MMWPluginFeatureAccess?.canUse?.(sanitizeAuthForUi(auth), featureKey) === true;
}

function pluginFeatureDeniedReason(auth, featureKey) {
  return globalThis.MMWPluginFeatureAccess?.deniedReason?.(sanitizeAuthForUi(auth), featureKey)
    || "当前账号未开通该插件功能。";
}

async function requestShopMoveBootstrap() {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const payload = await requestJson(auth.baseUrl, "GET", "/api/shop-move/bootstrap", null, auth.token, 15000);
  return unwrapPayload(payload);
}

async function requestShopMoveSaveConfig(config) {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const payload = await requestJson(auth.baseUrl, "POST", "/api/shop-move/config", config || {}, auth.token, 15000);
  return unwrapPayload(payload);
}

async function requestShopMoveCreateJob(message) {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const linksText = String(message.links || message.text || "").trim();
  const payload = await requestJson(auth.baseUrl, "POST", "/api/shop-move/jobs", {
    links: linksText,
    target_shop_id: String(message.targetShopId || message.target_shop_id || "").trim(),
    target_shop_name: String(message.targetShopName || message.target_shop_name || "").trim(),
    config: isObject(message.config) ? message.config : {},
    products: collectCachedShopMoveProductsForLinks(linksText)
  }, auth.token, 30000);
  return unwrapPayload(payload);
}

async function requestShopMoveParseProducts(message) {
  await requireEcommerceToolFeatureAccess();
  const linksText = String(message.links || message.text || "").trim();
  const links = parseShopMoveAiLinks(linksText);
  if (!links.length) throw new Error("请先粘贴要解析的商品链接");
  if (links.length > 20) throw new Error("每次最多解析 20 个商品链接");

  const config = isObject(message.config) ? message.config : {};
  const products = [];
  for (let index = 0; index < links.length; index += 1) {
    const link = links[index];
    const item = {
      id: `parse_${Date.now()}_${index + 1}`,
      index: index + 1,
      url: link.url,
      normalizedUrl: link.normalizedUrl,
      sourceItemId: link.sourceItemId
    };
    try {
      const product = await withShopMoveTimeout(
        resolveShopMoveAiProductPackage(item, config),
        SHOP_MOVE_PRODUCT_PARSE_TIMEOUT_MS,
        "商品信息包解析超时：商品页接口没有按时返回，请确认链接可打开后重试。"
      );
      const payload = buildShopMoveParseProductPayload(item, "ready", "商品信息包解析完成", product);
      recordAdminDebugEvent("shop-move-parse-result", {
        status: payload.status,
        message: payload.message,
        url: payload.url,
        normalized_url: payload.normalized_url,
        source_item_id: payload.source_item_id,
        product: summarizeShopMoveDebugProduct(payload.product)
      });
      cacheShopMoveParseProductPayload(payload);
      products.push(payload);
    } catch (error) {
      const payload = buildShopMoveParseProductPayload(item, "failed", buildShopMoveParseFailureMessage(error, item), error?.partialProduct || {});
      recordAdminDebugEvent("shop-move-parse-result", {
        status: payload.status,
        message: payload.message,
        url: payload.url,
        normalized_url: payload.normalized_url,
        source_item_id: payload.source_item_id,
        product: summarizeShopMoveDebugProduct(payload.product)
      }, "warn");
      cacheShopMoveParseProductPayload(payload);
      products.push(payload);
    }
  }

  const successCount = products.filter((item) => item.status === "ready").length;
  return {
    success_count: successCount,
    failed_count: products.length - successCount,
    products
  };
}

async function requestShopMoveRecoverParseResults(message) {
  await requireEcommerceToolFeatureAccess();
  const links = parseShopMoveRecoveryLinks(message);
  if (!links.length) return { products: [] };
  pruneShopMoveParseResultCache();
  const products = [];
  for (const link of links) {
    const cached = getCachedShopMoveParseProduct(link);
    if (cached) products.push(cached);
  }
  return { products };
}

async function requestShopMoveJobs(limit) {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const cleanLimit = Math.max(1, Math.min(100, Number(limit) || 20));
  const payload = await requestJson(auth.baseUrl, "GET", `/api/shop-move/jobs?limit=${encodeURIComponent(cleanLimit)}`, null, auth.token, 15000);
  return unwrapPayload(payload);
}

async function requestShopMoveRecords(limit) {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const cleanLimit = Math.max(1, Math.min(200, Number(limit) || 80));
  const payload = await requestJson(auth.baseUrl, "GET", `/api/shop-move/records?limit=${encodeURIComponent(cleanLimit)}`, null, auth.token, 15000);
  return unwrapPayload(payload);
}

async function openShopMoveSupportRecords() {
  const { auth } = await requireEcommerceToolFeatureAccess();
  const query = new URLSearchParams({ anchor: "shop-move-records" }).toString();
  const payload = await requestJson(auth.baseUrl, "GET", `/api/account/support-link?${query}`, null, auth.token, 15000);
  const data = unwrapPayload(payload);
  const rawUrl = String(data?.url || data?.support_url || "").trim();
  if (!rawUrl) throw new Error("账号服务未返回用户后台链接");
  const resolvedUrl = new URL(rawUrl, normalizeBaseUrl(auth.baseUrl)).toString();
  const tab = await chrome.tabs.create({ url: resolvedUrl, active: true });
  return { url: resolvedUrl, tabId: tab?.id || 0 };
}

async function requestShopMoveAiPublishStart(message) {
  await requireEcommerceToolFeatureAccess({ active: true });
  const links = parseShopMoveAiLinks(String(message.links || message.text || "").trim());
  if (!links.length) throw new Error("请先粘贴要搬家的商品链接。");
  if (links.length > 20) throw new Error("每次最多提交 20 个商品链接。");
  const config = isObject(message.config) ? message.config : {};
  const taskId = `ai_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  const task = {
    id: taskId,
    status: "queued",
    message: "AI浏览器上架任务已创建",
    config,
    items: links.map((link, index) => ({
      id: `${taskId}_${index + 1}`,
      index: index + 1,
      url: link.url,
      normalizedUrl: link.normalizedUrl,
      sourceItemId: link.sourceItemId,
      status: "queued",
      message: "等待上架",
      tabId: 0,
      product: buildShopMoveAiProduct(link, config, index)
    })),
    logs: [],
    activeTabId: 0,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    cancelled: false
  };
  shopMoveAiPublishTasks.set(taskId, task);
  appendShopMoveAiLog(task, "queued", task.message);
  runShopMoveAiPublishTask(task).catch((error) => {
    task.status = "failed";
    task.message = error.message || "AI浏览器上架任务失败";
    task.updatedAt = new Date().toISOString();
    appendShopMoveAiLog(task, "failed", task.message);
  });
  return { task: shopMoveAiTaskPayload(task) };
}

async function runShopMoveAiPublishTask(task) {
  task.status = "resolving";
  task.message = "正在解析商品信息包";
  task.updatedAt = new Date().toISOString();
  appendShopMoveAiLog(task, "resolving", task.message);

  for (const item of task.items) {
    if (task.cancelled) break;
    task.status = "resolving";
    item.status = "resolving";
    item.message = "正在解析商品信息包";
    task.message = `第 ${item.index} 个商品：${item.message}`;
    appendShopMoveAiLog(task, "resolving", task.message);

    try {
      item.product = await resolveShopMoveAiProductPackage(item, task.config);
      item.status = "ready";
      item.message = "商品信息包解析完成";
      task.message = `第 ${item.index} 个商品：${item.message}`;
      if (item.product?.parser_fallback_message) {
        appendShopMoveAiLog(task, "warning", `第 ${item.index} 个商品：解析服务不可用，已使用本地兜底解析。`);
      }
      appendShopMoveAiLog(task, "ready", `${task.message}：${item.product.title}`);
    } catch (error) {
      item.status = "failed";
      item.message = error.message || "商品信息包解析失败";
      task.message = `第 ${item.index} 个商品：${item.message}`;
      task.updatedAt = new Date().toISOString();
      appendShopMoveAiLog(task, "failed", task.message);
      continue;
    }

    if (task.cancelled) break;
    task.status = "running";
    item.status = "opening";
    item.message = "正在打开抖店发品页";
    appendShopMoveAiLog(task, "opening", `第 ${item.index} 个商品：${item.message}`);

    let tabId = 0;
    try {
      const tab = await createPreparedShopMovePublishTab(item, task.config, task);
      tabId = tab?.id || 0;
      if (!tabId) throw new Error("创建抖店发品页失败。");
      task.activeTabId = tabId;
      item.tabId = tabId;
      await waitForTabComplete(tabId, 60000);
      if (await isAgentBrowserControlEnabled().catch(() => false)) {
        await ensureAgentBrowserControlForTab(tabId).catch((error) => {
          appendShopMoveAiLog(task, "warning", `浏览器级接管通道初始化失败，回退页面脚本兜底：${error?.message || "未知错误"}`);
          return null;
        });
      }
      await chrome.scripting.executeScript({
        target: { tabId, allFrames: false },
        world: "MAIN",
        files: [SHOP_MOVE_AI_MODEL_HOOK_FILE]
      });
      await chrome.scripting.executeScript({
        target: { tabId, allFrames: false },
        files: [SHOP_MOVE_AI_PUBLISH_SKILL_FILE]
      });
      await chrome.scripting.executeScript({
        target: { tabId, allFrames: false },
        files: [SHOP_MOVE_AI_RUNNER_FILE]
      });
      item.status = "running";
      item.message = "正在填入商品信息";
      appendShopMoveAiLog(task, "running", `第 ${item.index} 个商品：${item.message}`);
      const results = await chrome.scripting.executeScript({
        target: { tabId, allFrames: false },
        func: (runnerTask) => {
          if (typeof window.__MMW_SHOP_MOVE_AI_PUBLISH_RUN_TASK__ !== "function") {
            throw new Error("AI上架runner未初始化。");
          }
          return window.__MMW_SHOP_MOVE_AI_PUBLISH_RUN_TASK__(runnerTask);
        },
        args: [{
          taskId: task.id,
          itemId: item.id,
          product: item.product,
          config: task.config
        }]
      });
      const response = Array.isArray(results) ? results[0]?.result : null;
      const action = String(response?.action || "");
      item.status = action === "manual_category_required" ? "manual_required" : "done";
      item.message = response?.message || (action === "manual_category_required" ? "系统未匹配到可用类目" : "AI上架步骤已完成");
      item.result = response || {};
      appendShopMoveAiLog(task, action === "manual_category_required" ? "warning" : "done", `第 ${item.index} 个商品：${item.message}`);
      const submitMode = String(task.config.ai_publish_submit_mode || "review");
      if (task.config.ai_publish_close_tab !== false && submitMode !== "review" && ["draft_clicked", "publish_clicked"].includes(action)) {
        await closeTabQuietly(tabId);
        item.tabId = 0;
      }
    } catch (error) {
      item.status = "failed";
      item.message = error.message || "AI上架失败";
      appendShopMoveAiLog(task, "failed", `第 ${item.index} 个商品：${item.message}`);
      if (tabId && task.config.ai_publish_close_failed_tab === true) {
        await closeTabQuietly(tabId);
      }
    } finally {
      if (task.activeTabId === tabId) task.activeTabId = 0;
      task.updatedAt = new Date().toISOString();
    }
  }

  if (task.cancelled) {
    task.status = "cancelled";
    task.message = "AI浏览器上架任务已取消";
  } else {
    const failedCount = task.items.filter((item) => item.status === "failed").length;
    const doneCount = task.items.filter((item) => item.status === "done").length;
    const manualCount = task.items.filter((item) => item.status === "manual_required").length;
    if (failedCount) {
      task.status = doneCount || manualCount ? "partial_done" : "failed";
      task.message = `完成 ${doneCount} 个，失败 ${failedCount} 个${manualCount ? `，待手动类目 ${manualCount} 个` : ""}。`;
    } else if (manualCount) {
      task.status = doneCount ? "partial_done" : "manual_required";
      task.message = doneCount
        ? `已完成 ${doneCount} 个商品，待手动类目 ${manualCount} 个。`
        : `有 ${manualCount} 个商品待手动选择类目。`;
    } else {
      task.status = "done";
      task.message = `已完成 ${doneCount} 个商品。`;
    }
  }
  task.updatedAt = new Date().toISOString();
  appendShopMoveAiLog(task, task.status, task.message);
}

async function createPreparedShopMovePublishTab(item, config, task) {
  const browserControlRequested = shouldUseShopMoveBrowserControl(config);
  if (browserControlRequested && !await isAgentBrowserControlEnabled().catch(() => false)) {
    await setAgentBrowserControlEnabled(true, null, null).catch((error) => {
      appendShopMoveAiLog(task, "warning", `浏览器级接管自动开启失败，将尝试页面脚本兜底：${error?.message || "未知错误"}`);
      return null;
    });
  }
  const preferVisible = browserControlRequested && await isAgentBrowserControlEnabled().catch(() => false);
  const packagePayload = buildShopMovePublishModelPackage(item?.product || {}, config || {}, item || {});
  packagePayload.publish_fill_mode = preferVisible ? "browser_control" : "dom_fallback";
  packagePayload.publishFillMode = packagePayload.publish_fill_mode;
  const prepareUrl = `https://fxg.jinritemai.com/ffa/mshop/homepage/index?mmw_shop_move_prepare=${Date.now()}`;
  const tab = await chrome.tabs.create({ url: prepareUrl, active: preferVisible });
  const tabId = tab?.id || 0;
  if (!tabId) throw new Error("创建抖店准备页失败。");
  if (preferVisible && tab?.windowId) {
    await chrome.windows.update(tab.windowId, { focused: true }).catch(() => null);
  }
  try {
    await waitForTabComplete(tabId, 60000);
    await chrome.scripting.executeScript({
      target: { tabId, allFrames: false },
      world: "MAIN",
      func: (payload) => {
        try {
          localStorage.setItem("mmw_shop_move_publish_package", JSON.stringify(payload || {}));
          const goodsDetail = payload?.goods_detail || payload?.goodsDetail || null;
          if (goodsDetail && typeof goodsDetail === "object") {
            localStorage.setItem("goodsDetail", JSON.stringify(goodsDetail));
          } else {
            localStorage.removeItem("goodsDetail");
          }
          localStorage.removeItem("custom_value");
          localStorage.removeItem("custom_value_list");
          localStorage.removeItem("custom_img");
          localStorage.removeItem("Json");
          localStorage.removeItem("Json2");
        } catch (error) {
          throw new Error(error?.message || "写入发品缓存失败");
        }
      },
      args: [packagePayload]
    });
  } catch (error) {
    appendShopMoveAiLog(task, "warning", `抖店发品预置缓存失败，转为页面内兜底同步：${error.message || "未知错误"}`);
  }
  await chrome.tabs.update(tabId, { url: SHOP_MOVE_AI_PUBLISH_URL, active: preferVisible });
  return tab;
}

function shouldUseShopMoveBrowserControl(config) {
  const value = config?.ai_publish_browser_control ?? config?.browser_control_publish ?? true;
  return value !== false && value !== "false" && value !== 0 && value !== "0";
}

function buildShopMovePublishModelPackage(product, config, item) {
  const raw = isObject(product) ? product : {};
  const nestedGoodsDetail = isObject(raw.goods_detail || raw.goodsDetail) ? (raw.goods_detail || raw.goodsDetail) : {};
  const referenceGoodsDetail = Object.keys(nestedGoodsDetail).length
    ? nestedGoodsDetail
    : buildShopMoveReferenceGoodsDetail(raw, config, item);
  const title = firstUsableShopMoveCapturedTitle([
    referenceGoodsDetail.title,
    raw.title,
    raw.source_title,
    raw.product_name,
    raw.productName,
    raw.name
  ]);
  const rawMainImages = uniqueShopMoveProductImages([
    ...listFromAny(raw.main_images),
    raw.main_image,
    ...listFromAny(referenceGoodsDetail.main_img),
    referenceGoodsDetail.main_img_0
  ]);
  const rawAllImages = uniqueShopMoveProductImages(listFromAny(raw.images));
  const mainImages = (rawMainImages.length ? rawMainImages : rawAllImages.slice(0, 1))
    .slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
  const rawDetailImages = uniqueShopMoveProductImages([
    ...listFromAny(raw.detail_images),
    ...listFromAny(referenceGoodsDetail.detail_img)
  ]);
  const detailImages = (rawDetailImages.length ? rawDetailImages : rawAllImages.slice(rawMainImages.length ? 0 : 1))
    .filter((url) => !mainImages.includes(url))
    .slice(0, 80);
  const attributes = Object.keys(getShopMoveProductAttributes(raw)).length
    ? getShopMoveProductAttributes(raw)
    : (isObject(referenceGoodsDetail.base_attr) ? referenceGoodsDetail.base_attr : {});
  const skuList = Array.isArray(raw.sku_list) && raw.sku_list.length
    ? raw.sku_list
    : buildShopMoveReferenceSkuRows(referenceGoodsDetail);
  return {
    ...raw,
    title,
    source_title: title || raw.source_title || raw.title || "",
    price: String(raw.price || raw.sale_price || ""),
    stock: String(raw.stock || raw.quantity || config?.stock_value || ""),
    main_images: mainImages,
    main_image: mainImages[0] || raw.main_image || "",
    detail_images: detailImages,
    images: uniqueShopMoveProductImages([
      ...mainImages,
      ...detailImages,
      ...listFromAny(raw.spec_images)
    ]).slice(0, 120),
    attributes,
    base_attr: attributes,
    specs: isObject(raw.specs) ? raw.specs : {},
    sku_list: skuList,
    spec_images: uniqueShopMoveProductImages([
      ...listFromAny(raw.spec_images),
      ...listFromAny(referenceGoodsDetail.sku_img_all),
      ...listFromAny(referenceGoodsDetail.sku_img)
    ]).filter(isLikelyShopMoveImageUrl),
    goods_detail: referenceGoodsDetail,
    goodsDetail: referenceGoodsDetail,
    main_img: referenceGoodsDetail.main_img || mainImages,
    main_img_0: referenceGoodsDetail.main_img_0 || mainImages[0] || "",
    detail_img: referenceGoodsDetail.detail_img || detailImages,
    sku_sort: referenceGoodsDetail.sku_sort || [],
    sku_key: referenceGoodsDetail.sku_key || {},
    sku_key_list: referenceGoodsDetail.sku_key_list || [],
    sku_price: referenceGoodsDetail.sku_price || {},
    sku_price_after_treatment: referenceGoodsDetail.sku_price_after_treatment || referenceGoodsDetail.sku_price || {},
    sku_img: referenceGoodsDetail.sku_img || {},
    sku_img_all: referenceGoodsDetail.sku_img_all || {},
    category: String(referenceGoodsDetail.category || raw.category || ""),
    categorySearchTextFromTitle: referenceGoodsDetail.categorySearchTextFromTitle === true,
    categoryMatchMode: String(referenceGoodsDetail.categoryMatchMode || "original"),
    useRecommendedCategory: referenceGoodsDetail.useRecommendedCategory === true,
    config: isObject(config) ? config : {},
    moving_config: isObject(config) ? config : {},
    source_url: item?.url || raw.source_url || "",
    source_item_id: item?.sourceItemId || raw.source_item_id || "",
    prepared_at: new Date().toISOString()
  };
}

function buildShopMoveReferenceGoodsDetail(product, config, item) {
  const raw = isObject(product) ? product : {};
  const title = firstUsableShopMoveCapturedTitle([
    raw.source_title,
    raw.title,
    raw.product_name,
    raw.productName,
    raw.name
  ]);
  const goodsId = cleanHtmlText(raw.goods_id || raw.product_id || raw.commodity_id || raw.promotion_id || item?.sourceItemId || raw.source_item_id || "");
  const mainImages = getShopMoveProductMainImages(raw).slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
  const detailImages = getShopMoveProductDetailImages(raw)
    .filter((url) => !mainImages.includes(url))
    .slice(0, Math.max(SHOP_MOVE_DETAIL_IMAGE_LIMIT, 80));
  const attributes = getShopMoveProductAttributes(raw);
  const movingConfig = isObject(config) ? config : {};
  const skuMaps = buildShopMoveReferenceSkuMapsFromProduct(raw);
  const price = String(raw.price || raw.sale_price || raw.min_price || "").trim();
  return {
    addtime: Number(raw.addtime || Date.now()) || Date.now(),
    base_attr: attributes,
    batchMovePageId: Number(raw.batchMovePageId || item?.batchMovePageId || 0) || 0,
    batchMoveTaskId: String(raw.batchMoveTaskId || item?.id || "").trim(),
    batchMoving: true,
    category: title || cleanHtmlText(raw.category || raw.category_name || raw.category_path || ""),
    categoryMatchMode: String(raw.categoryMatchMode || "original"),
    categorySearchTextFromTitle: true,
    detail_img: detailImages,
    fullDiscount: String(movingConfig.fullDiscount || movingConfig.full_discount || raw.fullDiscount || "").trim(),
    goods_id: goodsId,
    goods_url: String(item?.url || raw.source_url || "").trim(),
    id: String(raw.id || goodsId || item?.sourceItemId || raw.source_item_id || `mmw_${Date.now()}`).trim(),
    main_img: mainImages,
    main_img_0: mainImages[0] || "",
    main_img_mobile: uniqueShopMoveProductImages(listFromAny(raw.main_img_mobile || raw.mainImagesMobile)).slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT),
    movingConfig,
    platform: "douyin",
    platformEnum: "DOUYIN",
    price,
    sale_price: price,
    shopName: cleanHtmlText(raw.shopName || raw.shop_name || ""),
    source: "抖店",
    source_title: title || cleanHtmlText(raw.source_title || raw.title || ""),
    stock: String(raw.stock || raw.quantity || movingConfig.stock_value || "").trim(),
    outer_id: String(raw.outer_id || raw.sku_code || raw.source_item_id || "").trim(),
    target_platform: "DOUYIN",
    title,
    useRecommendedCategory: false,
    video_url: String(raw.video_url || raw.videoUrl || "").trim(),
    ...skuMaps
  };
}

function buildShopMoveReferenceSkuMapsFromProduct(product) {
  const raw = isObject(product) ? product : {};
  const rows = Array.isArray(raw.sku_list) && raw.sku_list.length
    ? raw.sku_list.filter(isObject).slice(0, 240)
    : buildShopMoveReferenceSkuRows(raw);
  const explicitSort = listFromAny(raw.sku_sort || raw.skuSort).map(stringifyShopMoveValue).filter(Boolean);
  const specGroupNames = explicitSort.length
    ? explicitSort
    : uniqueStrings(rows.flatMap((row) => Object.keys(isObject(row.spec) ? row.spec : {})).filter(Boolean));
  const sku_key = {};
  const sku_key_list = [];
  const sku_price = {};
  const sku_price_after_treatment = {};
  const sku_img = {};
  const sku_img_all = {};
  const fullAttrLists = [[], [], [], []];
  rows.forEach((row, index) => {
    const spec = isObject(row.spec) ? row.spec : {};
    const skuId = stringifyShopMoveValue(row.sku_id || row.skuId || row.id || row.name || `${Date.now()}_${index + 1}`);
    if (!skuId) return;
    const options = (specGroupNames.length ? specGroupNames : Object.keys(spec))
      .map((groupName) => stringifyShopMoveValue(spec[groupName]))
      .filter(Boolean);
    const fallbackName = stringifyShopMoveValue(row.name || row.sku_name || row.skuName);
    const optionList = options.length ? options : (fallbackName ? [fallbackName] : []);
    sku_key[skuId] = optionList;
    sku_key_list.push(skuId);
    optionList.slice(0, 4).forEach((value, optionIndex) => {
      if (value) fullAttrLists[optionIndex].push(value);
    });
    const image = firstShopMoveImageFromValue(row.image || row.images || row.skuPicture || row.sku_img || row.skuImg);
    if (image) {
      sku_img_all[skuId] = image;
      if (optionList[0] && !sku_img[optionList[0]]) sku_img[optionList[0]] = image;
    }
    const price = String(row.price || raw.price || raw.sale_price || "").trim();
    if (price) {
      sku_price[skuId] = { price };
      sku_price_after_treatment[skuId] = { price };
    }
  });
  return {
    sku_sort: specGroupNames,
    sku_key,
    sku_key_list,
    sku_price,
    sku_price_after_treatment,
    sku_img,
    sku_img_all,
    backSkuPrice: [sku_price, sku_price_after_treatment],
    __fullAttrListsForFill: {
      attr1: uniqueStrings(fullAttrLists[0]),
      attr2: uniqueStrings(fullAttrLists[1]),
      attr3: uniqueStrings(fullAttrLists[2]),
      attr4: uniqueStrings(fullAttrLists[3])
    }
  };
}

function getShopMoveAiPublishStatus(taskId) {
  const id = String(taskId || "").trim();
  const task = id ? shopMoveAiPublishTasks.get(id) : Array.from(shopMoveAiPublishTasks.values()).at(-1);
  return { task: task ? shopMoveAiTaskPayload(task) : null };
}

async function cancelShopMoveAiPublishTask(taskId) {
  const id = String(taskId || "").trim();
  const task = id ? shopMoveAiPublishTasks.get(id) : Array.from(shopMoveAiPublishTasks.values()).at(-1);
  if (!task) return { task: null };
  task.cancelled = true;
  task.status = "cancelled";
  task.message = "AI浏览器上架任务已取消";
  task.updatedAt = new Date().toISOString();
  appendShopMoveAiLog(task, "cancelled", task.message);
  if (task.activeTabId) await closeTabQuietly(task.activeTabId);
  return { task: shopMoveAiTaskPayload(task) };
}

function updateShopMoveAiPublishProgress(message) {
  const task = shopMoveAiPublishTasks.get(String(message.taskId || ""));
  if (!task) return;
  const item = task.items.find((entry) => entry.id === String(message.itemId || ""));
  if (item) {
    item.status = String(message.status || item.status || "running");
    item.message = String(message.message || item.message || "");
  }
  task.message = String(message.message || task.message || "");
  task.updatedAt = new Date().toISOString();
  appendShopMoveAiLog(task, String(message.status || "running"), String(message.message || ""));
}

function appendShopMoveAiLog(task, status, message) {
  task.logs.push({
    status: String(status || ""),
    message: String(message || ""),
    at: new Date().toISOString()
  });
  if (task.logs.length > 120) task.logs.splice(0, task.logs.length - 120);
  recordAdminDebugEvent("shop-move-ai-publish-log", {
    task_id: task.id,
    task_status: String(task.status || ""),
    step_status: String(status || ""),
    message: String(message || ""),
    active_tab_id: Number(task.activeTabId || 0) || 0,
    item_statuses: Array.isArray(task.items)
      ? task.items.map((item) => ({
        index: Number(item.index || 0) || 0,
        status: String(item.status || ""),
        message: String(item.message || ""),
        tab_id: Number(item.tabId || 0) || 0
      }))
      : []
  });
}

function storeShopMoveSourceCaptureRecord(sender, record) {
  const tabId = Number(sender?.tab?.id || 0);
  if (!tabId || !isObject(record)) return;
  const url = String(record.url || "").trim();
  const body = String(record.body || "").slice(0, SHOP_MOVE_SOURCE_CAPTURE_BODY_LIMIT);
  if (!url || !body) return;
  const normalized = {
    kind: String(record.kind || ""),
    method: String(record.method || "GET"),
    url,
    status: Number(record.status || 0),
    content_type: String(record.content_type || ""),
    body,
    page_url: String(record.page_url || sender.tab?.url || ""),
    at: Number(record.at || Date.now())
  };
  const list = shopMoveSourceCaptureRecords.get(tabId) || [];
  const fingerprint = `${normalized.kind}|${normalized.url}|${normalized.body.length}|${normalized.body.slice(0, 80)}`;
  if (list.some((item) => item.fingerprint === fingerprint)) return;
  list.push({ ...normalized, fingerprint });
  if (list.length > SHOP_MOVE_SOURCE_CAPTURE_MAX_RECORDS) {
    list.splice(0, list.length - SHOP_MOVE_SOURCE_CAPTURE_MAX_RECORDS);
  }
  shopMoveSourceCaptureRecords.set(tabId, list);
}

function getShopMoveSourceCaptureRecords(tabId) {
  return (shopMoveSourceCaptureRecords.get(Number(tabId) || 0) || []).map((record) => ({
    kind: record.kind,
    method: record.method,
    url: record.url,
    status: record.status,
    content_type: record.content_type,
    body: record.body,
    page_url: record.page_url,
    at: record.at
  }));
}

function attachShopMoveCapturedRecordsToSnapshot(snapshot, records, item) {
  const normalized = isObject(snapshot) ? { ...snapshot } : {};
  const captureRecords = uniqueShopMoveCaptureRecords([
    ...(Array.isArray(normalized.api_records) ? normalized.api_records : []),
    ...(Array.isArray(records) ? records : [])
  ]);
  const apiProduct = buildShopMoveProductFromCapturedRecords(captureRecords, item);
  normalized.api_records = captureRecords.map((record) => ({
    kind: record.kind,
    method: record.method,
    url: record.url,
    status: record.status,
    content_type: record.content_type,
    body: record.body,
    page_url: record.page_url
  }));
  normalized.product = mergeShopMoveProductPackages(normalized.product || {}, apiProduct);
  if (!normalized.url) normalized.url = item.url;
  if (!normalized.normalized_url) normalized.normalized_url = item.normalizedUrl;
  if (!normalized.source_item_id) normalized.source_item_id = item.sourceItemId;
  return normalized;
}

function uniqueShopMoveCaptureRecords(records) {
  const result = [];
  const seen = new Set();
  for (const record of records || []) {
    if (!record || !record.url || !record.body) continue;
    const normalized = {
      kind: String(record.kind || ""),
      method: String(record.method || "GET"),
      url: String(record.url || ""),
      status: Number(record.status || 0),
      content_type: String(record.content_type || ""),
      body: String(record.body || "").slice(0, SHOP_MOVE_SOURCE_CAPTURE_BODY_LIMIT),
      page_url: String(record.page_url || ""),
      at: Number(record.at || Date.now())
    };
    const fingerprint = `${normalized.kind}|${normalized.url}|${normalized.body.length}|${normalized.body.slice(0, 80)}`;
    if (seen.has(fingerprint)) continue;
    seen.add(fingerprint);
    result.push(normalized);
  }
  return result;
}

function buildShopMoveProductFromCapturedRecords(records, item) {
  const objects = [];
  for (const record of records || []) {
    const parsed = parseShopMoveCapturedBody(record?.body);
    if (parsed) {
      objects.push(parsed);
      collectShopMoveEmbeddedJsonObjects(parsed, objects);
    }
  }
  if (!objects.length) return {};

  const mainImages = [];
  const detailImages = [];
  const specImages = [];
  const allImages = [];
  const titleCandidates = [];
  const priceCandidates = [];
  const categoryCandidates = [];
  const descriptionCandidates = [];
  const productIdCandidates = [];
  const goodsIdCandidates = [];
  const commodityIdCandidates = [];
  const promotionIdCandidates = [];
  const attributes = {};
  const specs = {};
  const skuCandidates = [];

  for (const root of objects) {
    const goodsDetail = findShopMoveReferenceGoodsDetail(root);
    if (goodsDetail) {
      collectShopMoveReferenceGoodsDetail(goodsDetail, {
        titleCandidates,
        priceCandidates,
        categoryCandidates,
        descriptionCandidates,
        mainImages,
        detailImages,
        specImages,
        allImages,
        attributes,
        specs,
        skuCandidates,
        productIdCandidates,
        goodsIdCandidates,
        commodityIdCandidates,
        promotionIdCandidates
      });
    }

    const referenceSkuPackage = findShopMoveReferenceSkuPackage(root);
    if (referenceSkuPackage && referenceSkuPackage !== goodsDetail) {
      collectShopMoveReferenceSkuPackage(referenceSkuPackage, {
        priceCandidates,
        specImages,
        specs,
        skuCandidates
      });
    }

    const promotion = findFirstNestedObject(root, "promotion_h5") || findFirstNestedObject(root, "promotionH5");
    if (promotion) {
      pushText(titleCandidates, readNestedText(promotion, [
        ["basic_info_data", "title_info", "title"],
        ["basicInfoData", "titleInfo", "title"],
        ["title_info", "title"],
        ["titleInfo", "title"],
        ["title"],
        ["name"]
      ]));
      pushText(priceCandidates, readNestedText(promotion, [
        ["basic_info_data", "price_info", "price", "min_price"],
        ["basic_info_data", "price_info", "min_price"],
        ["basicInfoData", "priceInfo", "price", "minPrice"],
        ["price_info", "price", "min_price"],
        ["priceInfo", "minPrice"],
        ["price"]
      ]));
      collectShopMoveImages(promotion.head_figure_data || promotion.headFigureData || promotion.cover || promotion.cover_info, mainImages);
    }

    const detailInfo = findFirstNestedObject(root, "detail_info") || findFirstNestedObject(root, "detailInfo");
    if (detailInfo) {
      pushText(titleCandidates, readNestedText(detailInfo, [["title"], ["name"], ["product_name"], ["productName"]]));
      collectShopMoveImages(detailInfo.detail_imgs || detailInfo.detailImgs, detailImages);
      collectShopMoveImages(detailInfo.detail_imgs_new || detailInfo.detailImgsNew, detailImages);
      collectShopMoveImages(detailInfo.detail_images || detailInfo.detailImages, detailImages);
      collectShopMoveProductFormat(detailInfo.product_format || detailInfo.productFormat, attributes, specImages);
    }

    collectShopMoveImages(root, allImages);
    collectShopMoveValuesByKeys(root, [
      "title",
      "name",
      "product_name",
      "productName",
      "goods_name",
      "goodsName",
      "commodity_name",
      "commodityName",
      "promotion_title",
      "promotionTitle",
      "short_title",
      "shortTitle"
    ], titleCandidates);
    collectShopMoveValuesByKeys(root, [
      "category",
      "category_name",
      "categoryName",
      "category_path",
      "categoryPath",
      "cat_name",
      "catName"
    ], categoryCandidates);
    collectShopMoveValuesByKeys(root, [
      "product_id",
      "productId"
    ], productIdCandidates);
    collectShopMoveValuesByKeys(root, [
      "goods_id",
      "goodsId"
    ], goodsIdCandidates);
    collectShopMoveValuesByKeys(root, [
      "commodity_id",
      "commodityId"
    ], commodityIdCandidates);
    collectShopMoveValuesByKeys(root, [
      "promotion_id",
      "promotionId"
    ], promotionIdCandidates);
    collectShopMoveValuesByKeys(root, [
      "description",
      "desc",
      "detail",
      "sub_title",
      "subTitle",
      "selling_point",
      "sellingPoint"
    ], descriptionCandidates);
    collectShopMoveAttributePairs(root, attributes);
    collectShopMoveSkuCandidates(root, skuCandidates);
    collectShopMoveSpecGroups(root, specs, specImages);
    collectShopMoveDouyinSkuInfo(root, specs, specImages, skuCandidates);
    collectShopMoveValuesByKeys(root, ["price", "sale_price", "salePrice", "sell_price", "min_price", "minPrice"], priceCandidates);
  }

  const skuList = normalizeShopMoveCapturedSkuList(skuCandidates);
  for (const sku of skuList) {
    collectShopMoveImages(sku, specImages);
  }

  const uniqueMainImages = uniqueStrings(mainImages).slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
  const uniqueDetailImages = uniqueStrings(detailImages)
    .filter((url) => !uniqueMainImages.includes(url))
    .slice(0, SHOP_MOVE_DETAIL_IMAGE_LIMIT);
  const uniqueSpecImages = uniqueStrings(specImages).slice(0, 40);
  const images = uniqueShopMoveProductImages([
    ...uniqueMainImages,
    ...uniqueDetailImages,
    ...uniqueSpecImages,
    ...allImages
  ]).slice(0, 100);
  const title = firstUsableShopMoveCapturedTitle(titleCandidates);
  const price = normalizeShopMovePrice(priceCandidates.find(Boolean) || "");

  return {
    title,
    source_title: title,
    price,
    sale_price: price,
    category: cleanHtmlText(categoryCandidates.find(Boolean) || ""),
    attributes,
    specs,
    sku_list: skuList,
    description: cleanHtmlText(descriptionCandidates.find(Boolean) || ""),
    images,
    main_images: uniqueMainImages.length ? uniqueMainImages : images.slice(0, 1),
    main_image: (uniqueMainImages[0] || images[0] || ""),
    detail_images: uniqueDetailImages.length ? uniqueDetailImages : images.slice(1),
    spec_images: uniqueSpecImages,
    sku_debug: {
      candidate_count: skuCandidates.length,
      sku_count: skuList.length,
      spec_group_count: Object.keys(specs).length
    },
    product_id: cleanHtmlText(productIdCandidates.find(Boolean) || ""),
    goods_id: cleanHtmlText(goodsIdCandidates.find(Boolean) || ""),
    commodity_id: cleanHtmlText(commodityIdCandidates.find(Boolean) || ""),
    promotion_id: cleanHtmlText(promotionIdCandidates.find(Boolean) || item?.sourceItemId || ""),
    source_url: item?.url || "",
    normalized_url: item?.normalizedUrl || "",
    source_item_id: item?.sourceItemId || "",
    source: "rendered_tab_api"
  };
}

function collectShopMoveEmbeddedJsonObjects(root, output) {
  if (!output || output.length >= 160) return;
  walkShopMoveObject(root, (value) => {
    if (output.length >= 160 || typeof value !== "string") return;
    let text = value.trim();
    if (!text || text.length < 40 || text.length > SHOP_MOVE_SOURCE_CAPTURE_BODY_LIMIT) return;
    if (/^%5B|^%7B/i.test(text)) {
      try {
        text = decodeURIComponent(text);
      } catch {
        // Keep the original text when it was not URI encoded.
      }
    }
    if (!/^[\[{]/.test(text)) return;
    if (!/(promotion_h5|detail_info|goodsDetail|main_img|sku_price|skuPrice|sku_key|skuKey|sku_key_list|skuKeyList|sku_img|skuImg|sku_img_all|skuImgAll|sku_list|skuList|sku_infos|skuInfos|sku_info_list|skuInfoList|spec_list|specList|sale_props|saleProps|product_format|title|price)/i.test(text.slice(0, 6000))) return;
    const parsed = parseShopMoveCapturedBody(text);
    if (parsed && !output.includes(parsed)) output.push(parsed);
  });
}

function findShopMoveReferenceGoodsDetail(root) {
  if (isShopMoveReferenceGoodsDetail(root)) return root;
  const direct = findFirstNestedObject(root, "goodsDetail")
    || findFirstNestedObject(root, "goods_detail")
    || findFirstNestedObject(root, "goodsInfo")
    || findFirstNestedObject(root, "goods_info");
  if (isShopMoveReferenceGoodsDetail(direct)) return direct;
  return direct || null;
}

function isShopMoveReferenceGoodsDetail(value) {
  if (!isObject(value)) return false;
  const keys = Object.keys(value).join(",");
  return /(goods_url|goods_id|main_img|detail_img|sku_price|sku_key|sku_img|base_attr|shopName)/i.test(keys)
    && /(title|goods_id|goods_url|main_img)/i.test(keys);
}

function findShopMoveReferenceSkuPackage(root) {
  if (isShopMoveReferenceSkuPackage(root)) return root;
  let found = null;
  walkShopMoveObject(root, (value) => {
    if (!found && isShopMoveReferenceSkuPackage(value)) found = value;
  });
  return found;
}

function isShopMoveReferenceSkuPackage(value) {
  if (!isObject(value)) return false;
  const keys = Object.keys(value).join(",");
  return /(sku_key_list|skuKeyList|sku_key|skuKey|sku_price|skuPrice|sku_img|skuImg|sku_img_all|skuImgAll)/.test(keys)
    && /(sku_key|skuKey|sku_price|skuPrice)/.test(keys);
}

function collectShopMoveReferenceSkuPackage(goods, target) {
  const rows = buildShopMoveReferenceSkuRows(goods);
  for (const row of rows) {
    target.skuCandidates.push(row);
    if (row.price) target.priceCandidates.push(row.price);
    if (row.image) target.specImages.push(row.image);
    for (const [group, option] of Object.entries(row.spec || {})) {
      addShopMoveSpecValue(target.specs, group, option);
    }
  }
  if (!rows.length) {
    collectShopMoveReferenceSpecs(goods.sku_key || goods.skuKey || goods.sku_key_list || goods.skuKeyList, target.specs, target.skuCandidates);
    collectShopMoveReferenceSkuPrices(goods.sku_price || goods.skuPrice, target.skuCandidates, target.priceCandidates);
  }
}

function collectShopMoveReferenceGoodsDetail(goods, target) {
  if (!isObject(goods) || !target) return;
  pushText(target.titleCandidates, goods.title || goods.name || goods.product_name || goods.goods_name);
  pushText(target.priceCandidates, goods.price || goods.sale_price || goods.min_price || goods.coupon_price);
  pushText(target.categoryCandidates, goods.category || goods.category_name || goods.categoryName || goods.category_path);
  pushText(target.descriptionCandidates, goods.description || goods.desc || goods.detail_text || goods.detailText || goods.detail);
  pushText(target.productIdCandidates, goods.product_id || goods.productId);
  pushText(target.goodsIdCandidates, goods.goods_id || goods.goodsId);
  pushText(target.commodityIdCandidates, goods.commodity_id || goods.commodityId);
  pushText(target.promotionIdCandidates, goods.promotion_id || goods.promotionId || goods.id);

  collectShopMoveImages(goods.main_img || goods.mainImg || goods.main_images || goods.mainImages, target.mainImages);
  collectShopMoveImages(goods.detail_img || goods.detailImg || goods.detail_images || goods.detailImages, target.detailImages);
  collectShopMoveImages(goods.sku_img || goods.skuImg || goods.sku_img_all || goods.skuImgAll, target.specImages);
  collectShopMoveImages(goods.images || goods.image_list || goods.imgs || goods.pics, target.allImages);

  collectShopMoveReferenceAttributes(goods.base_attr || goods.baseAttr || goods.attrs || goods.attributes, target.attributes);
  const referenceSkuRows = buildShopMoveReferenceSkuRows(goods);
  if (referenceSkuRows.length) {
    for (const row of referenceSkuRows) {
      target.skuCandidates.push(row);
      if (row.price) target.priceCandidates.push(row.price);
      if (row.image) target.specImages.push(row.image);
      for (const [group, option] of Object.entries(row.spec || {})) {
        addShopMoveSpecValue(target.specs, group, option);
      }
    }
  } else {
    collectShopMoveReferenceSpecs(goods.sku_key || goods.skuKey || goods.sku_key_list || goods.skuKeyList, target.specs, target.skuCandidates);
    collectShopMoveReferenceSkuPrices(goods.sku_price || goods.skuPrice, target.skuCandidates, target.priceCandidates);
  }
}

function buildShopMoveReferenceSkuRows(goods) {
  if (!isObject(goods)) return [];
  const skuKey = goods.sku_key || goods.skuKey;
  const skuPrice = goods.sku_price || goods.skuPrice;
  const skuImg = goods.sku_img || goods.skuImg;
  const skuImgAll = goods.sku_img_all || goods.skuImgAll;
  const skuIds = uniqueStrings([
    ...listFromAny(goods.sku_key_list || goods.skuKeyList).map(stringifyShopMoveValue),
    ...Object.keys(isObject(skuKey) ? skuKey : {}),
    ...Object.keys(isObject(skuPrice) ? skuPrice : {})
  ]).filter(Boolean);
  if (!skuIds.length) return [];

  const groupNames = listFromAny(goods.sku_sort || goods.skuSort)
    .map(stringifyShopMoveValue)
    .filter(Boolean);
  const rows = [];
  const seen = new Set();

  for (const skuId of skuIds.slice(0, 240)) {
    const options = normalizeShopMoveReferenceSkuOptions(isObject(skuKey) ? skuKey[skuId] : null);
    const spec = {};
    for (let index = 0; index < options.length; index += 1) {
      const groupName = groupNames[index] || (groupNames.length === 1 ? groupNames[0] : `规格${index + 1}`);
      if (groupName && options[index]) spec[groupName.slice(0, 80)] = options[index].slice(0, 120);
    }

    const priceEntry = isObject(skuPrice) ? skuPrice[skuId] : null;
    const price = normalizeShopMovePrice(readShopMoveReferencePriceValue(priceEntry));
    const image = firstShopMoveImageFromValue(isObject(skuImgAll) ? skuImgAll[skuId] : null)
      || firstShopMoveImageFromValue(isObject(skuImg) ? skuImg[skuId] : null)
      || options.map((option) => firstShopMoveImageFromValue(isObject(skuImg) ? skuImg[option] : null)).find(Boolean)
      || firstShopMoveImageFromValue(priceEntry);
    const name = options.join(" ") || stringifyShopMoveValue(priceEntry?.name || priceEntry?.title || priceEntry?.sku_name || priceEntry?.skuName);
    const fingerprint = `${skuId}|${name}|${JSON.stringify(spec)}|${price}`;
    if (seen.has(fingerprint)) continue;
    seen.add(fingerprint);
    rows.push({
      sku_id: skuId,
      name,
      price,
      spec,
      image,
      images: image ? [image] : []
    });
  }

  return rows;
}

function normalizeShopMoveReferenceSkuOptions(value) {
  const entries = Array.isArray(value) ? value : (value == null ? [] : [value]);
  const options = [];
  for (const entry of entries.slice(0, 40)) {
    const text = isObject(entry)
      ? stringifyShopMoveValue(entry.name || entry.value || entry.text || entry.title || entry.option || entry.spec_value || entry.specValue)
      : stringifyShopMoveValue(entry);
    if (text) options.push(text);
  }
  return uniqueStrings(options);
}

function readShopMoveReferencePriceValue(entry) {
  if (!isObject(entry)) return entry;
  for (const key of ["price", "sale_price", "salePrice", "sell_price", "sellPrice", "min_price", "minPrice", "real_price", "realPrice", "discount_price", "discountPrice", "value"]) {
    if (entry[key] != null) return entry[key];
  }
  for (const key of ["price_info", "priceInfo", "sale_price_info", "salePriceInfo"]) {
    const nested = readShopMoveReferencePriceValue(entry[key]);
    if (nested) return nested;
  }
  return "";
}

function addShopMoveSpecValue(specs, group, option) {
  if (!specs) return;
  const groupName = stringifyShopMoveValue(group) || "规格";
  const optionName = stringifyShopMoveValue(option);
  if (!optionName) return;
  const existing = specs[groupName] ? String(specs[groupName]).split("、") : [];
  specs[groupName] = uniqueStrings([...existing, optionName]).join("、").slice(0, 240);
}

function collectShopMoveReferenceAttributes(value, attributes) {
  if (!attributes) return;
  if (Array.isArray(value)) {
    for (const entry of value.slice(0, 120)) {
      if (!isObject(entry)) continue;
      const key = stringifyShopMoveValue(entry.name || entry.key || entry.title || entry.attr_name || entry.attrName);
      const text = stringifyShopMoveValue(entry.value || entry.val || entry.text || entry.attr_value || entry.attrValue);
      if (key && text) attributes[key.slice(0, 80)] = text.slice(0, 240);
    }
    return;
  }
  if (isObject(value)) {
    for (const [key, entry] of Object.entries(value).slice(0, 120)) {
      const text = stringifyShopMoveValue(entry);
      if (key && text) attributes[String(key).slice(0, 80)] = text.slice(0, 240);
    }
  }
}

function collectShopMoveReferenceSpecs(value, specs, skuCandidates) {
  if (!value) return;
  const addSpecValue = (group, option, image = "") => {
    const groupName = stringifyShopMoveValue(group) || "规格";
    const optionName = stringifyShopMoveValue(option);
    if (!optionName) return;
    const existing = specs[groupName] ? String(specs[groupName]).split("、") : [];
    specs[groupName] = uniqueStrings([...existing, optionName]).join("、").slice(0, 240);
    skuCandidates.push({
      name: optionName,
      spec: { [groupName]: optionName },
      image,
      images: image ? [image] : []
    });
  };

  if (Array.isArray(value)) {
    for (const group of value.slice(0, 80)) {
      if (isObject(group)) {
        const groupName = group.name || group.title || group.key || group.property_name || group.propertyName || "规格";
        const options = Array.isArray(group.values) ? group.values
          : Array.isArray(group.options) ? group.options
            : Array.isArray(group.children) ? group.children
              : Array.isArray(group.list) ? group.list
                : [];
        if (options.length) {
          for (const option of options.slice(0, 120)) {
            const image = firstShopMoveImageFromValue(option);
            addSpecValue(groupName, isObject(option) ? (option.name || option.value || option.text || option.title) : option, image);
          }
        } else {
          addSpecValue("规格", group.name || group.value || group.text || group.title, firstShopMoveImageFromValue(group));
        }
      } else {
        addSpecValue("规格", group);
      }
    }
    return;
  }

  if (isObject(value)) {
    for (const [groupName, entry] of Object.entries(value).slice(0, 80)) {
      if (Array.isArray(entry)) {
        for (const option of entry.slice(0, 120)) {
          addSpecValue(groupName, isObject(option) ? (option.name || option.value || option.text || option.title) : option, firstShopMoveImageFromValue(option));
        }
      } else if (isObject(entry)) {
        for (const [optionName, optionValue] of Object.entries(entry).slice(0, 120)) {
          addSpecValue(groupName, stringifyShopMoveValue(optionValue) || optionName, firstShopMoveImageFromValue(optionValue));
        }
      } else {
        addSpecValue(groupName, entry);
      }
    }
  }
}

function collectShopMoveReferenceSkuPrices(value, skuCandidates, priceCandidates) {
  if (!value) return;
  if (Array.isArray(value)) {
    for (const entry of value.slice(0, 240)) {
      if (isObject(entry)) {
        const price = normalizeShopMovePrice(readShopMoveReferencePriceValue(entry));
        if (price) priceCandidates.push(price);
        skuCandidates.push({
          ...entry,
          price: price || entry.price || entry.sale_price || entry.salePrice
        });
      } else {
        const price = normalizeShopMovePrice(entry);
        if (price) priceCandidates.push(price);
      }
    }
    return;
  }
  if (isObject(value)) {
    for (const [key, entry] of Object.entries(value).slice(0, 240)) {
      const price = normalizeShopMovePrice(readShopMoveReferencePriceValue(entry));
      if (price) priceCandidates.push(price);
      skuCandidates.push({
        sku_id: key,
        name: key,
        price,
        spec: { 规格: key }
      });
    }
  } else {
    const price = normalizeShopMovePrice(value);
    if (price) priceCandidates.push(price);
  }
}

function mergeShopMoveProductPackages(baseProduct, extraProduct) {
  const base = isObject(baseProduct) ? baseProduct : {};
  const extra = isObject(extraProduct) ? extraProduct : {};
  const extraMainImages = getShopMoveProductMainImages(extra);
  const baseMainImages = getShopMoveProductMainImages(base);
  const mainImages = pickPreferredShopMoveImages(baseMainImages, extraMainImages, base, extra, "main")
    .slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
  const extraDetailImages = getShopMoveProductDetailImages(extra);
  const baseDetailImages = getShopMoveProductDetailImages(base);
  const detailImages = pickPreferredShopMoveImages(baseDetailImages, extraDetailImages, base, extra, "detail")
    .filter((url) => !mainImages.includes(url))
    .slice(0, SHOP_MOVE_DETAIL_IMAGE_LIMIT);
  const specImages = uniqueStrings([
    ...getShopMoveProductSpecImages(extra),
    ...getShopMoveProductSpecImages(base)
  ]).filter(isLikelyShopMoveImageUrl);
  const images = uniqueShopMoveProductImages([
    ...mainImages,
    ...listFromAny(extra.images),
    ...listFromAny(base.images),
    ...detailImages,
    ...specImages
  ]).slice(0, 100);
  const title = firstUsableShopMoveCapturedTitle([
    extra.title,
    extra.source_title,
    base.title,
    base.source_title
  ]);
  const sourceParts = uniqueStrings([base.source, extra.source]).filter(Boolean);
  const attributes = {
    ...getShopMoveProductAttributes(base),
    ...getShopMoveProductAttributes(extra)
  };
  const skuList = getShopMoveProductSkuList(extra);
  return {
    ...base,
    ...extra,
    title,
    source_title: extra.source_title || base.source_title || title,
    price: String(extra.price || base.price || ""),
    stock: String(base.stock || extra.stock || ""),
    product_id: String(extra.product_id || base.product_id || ""),
    goods_id: String(extra.goods_id || base.goods_id || ""),
    commodity_id: String(extra.commodity_id || base.commodity_id || ""),
    promotion_id: String(extra.promotion_id || base.promotion_id || ""),
    description: String(extra.description || base.description || ""),
    attributes,
    base_attr: attributes,
    specs: isObject(extra.specs) || isObject(base.specs)
      ? { ...(isObject(base.specs) ? base.specs : {}), ...(isObject(extra.specs) ? extra.specs : {}) }
      : (extra.specs || base.specs || {}),
    sku_list: skuList.length ? skuList : getShopMoveProductSkuList(base),
    images,
    main_images: mainImages.length ? mainImages : images.slice(0, 1),
    main_image: mainImages[0] || images[0] || "",
    detail_images: detailImages.length ? detailImages : images.slice(1),
    spec_images: specImages,
    source: sourceParts.join("+") || base.source || extra.source || ""
  };
}

async function enrichShopMoveProductWithDouyinSku(product, item) {
  const base = isObject(product) ? product : {};
  const currentSkuList = Array.isArray(base.sku_list)
    ? base.sku_list
    : (Array.isArray(base.skuList) ? base.skuList : []);
  if (currentSkuList.length) return base;
  const productId = String(
    base.product_id
    || base.goods_id
    || base.commodity_id
    || item?.sourceItemId
    || base.source_item_id
    || extractShopMoveAiSourceItemId(base.source_url || item?.url || "")
    || ""
  ).trim();
  const debugBase = isObject(base.sku_debug) ? base.sku_debug : {};
  if (!/^\d{8,30}$/.test(productId)) {
    return {
      ...base,
      sku_debug: {
        ...debugBase,
        supplement_source: "douyin_product_sku_list",
        supplement_ok: false,
        supplement_error: "缺少可用商品 ID"
      }
    };
  }
  try {
    const rawPackage = await fetchShopMoveDouyinSkuPackage(productId);
    const patch = buildShopMoveDouyinSkuProductPatch(rawPackage, productId);
    if (!patch.sku_list.length) {
      return {
        ...base,
        sku_debug: {
          ...debugBase,
          ...patch.sku_debug,
          supplement_ok: false,
          supplement_error: patch.sku_debug.response_message || "SKU 接口未返回可用规格"
        }
      };
    }
    return mergeShopMoveProductPackages(base, {
      sku_list: patch.sku_list,
      specs: patch.specs,
      spec_images: patch.spec_images,
      sku_debug: {
        ...debugBase,
        ...patch.sku_debug,
        supplement_ok: true
      },
      source: "douyin_product_sku_list"
    });
  } catch (error) {
    return {
      ...base,
      sku_debug: {
        ...debugBase,
        supplement_source: "douyin_product_sku_list",
        supplement_ok: false,
        supplement_error: error?.message || "SKU 接口读取失败"
      }
    };
  }
}

async function fetchShopMoveDouyinSkuPackage(productId) {
  const body = new URLSearchParams({ product_id: String(productId || "") }).toString();
  await setDownloadHeadersRule(SHOP_MOVE_DOUYIN_SKU_RULE_ID, SHOP_MOVE_DOUYIN_SKU_API_URL, {
    Origin: "https://live.douyin.com",
    Referer: "https://live.douyin.com/"
  }).catch(() => {});
  try {
    const response = await fetchWithTimeout(SHOP_MOVE_DOUYIN_SKU_API_URL, {
      method: "POST",
      cache: "no-store",
      credentials: "include",
      redirect: "follow",
      referrer: "https://live.douyin.com/",
      referrerPolicy: "strict-origin-when-cross-origin",
      headers: {
        Accept: "application/json, text/plain, */*",
        "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
      },
      body
    }, 20000, "SKU 接口读取超时");
    const text = await response.text();
    if (!response.ok) throw new Error(`SKU 接口请求失败：HTTP ${response.status}`);
    const parsed = parseShopMoveCapturedBody(text);
    if (!parsed) throw new Error("SKU 接口返回内容无法解析");
    return parsed;
  } finally {
    await clearRule(SHOP_MOVE_DOUYIN_SKU_RULE_ID);
  }
}

function buildShopMoveDouyinSkuProductPatch(rawPackage, productId) {
  const root = isObject(rawPackage) ? rawPackage : {};
  const candidates = [];
  const specs = {};
  const specImages = [];
  collectShopMoveDouyinSkuInfo(root, specs, specImages, candidates);
  collectShopMoveSkuCandidates(root, candidates);
  collectShopMoveImages(root?.data?.skus || root?.data?.sku_list || root?.data?.skuList, specImages);
  const skuList = normalizeShopMoveCapturedSkuList(candidates);
  return {
    sku_list: skuList,
    specs,
    spec_images: uniqueStrings(specImages).filter(isLikelyShopMoveImageUrl).slice(0, 40),
    sku_debug: {
      supplement_source: "douyin_product_sku_list",
      product_id: productId,
      response_status_code: Number(root.status_code ?? root.statusCode ?? root.code ?? 0) || 0,
      response_message: String(root.status_msg || root.statusMsg || root.msg || root.message || ""),
      data_skus_count: firstShopMoveArray(root?.data?.skus, root?.data?.sku_list, root?.data?.skuList).length,
      candidate_count: candidates.length,
      sku_count: skuList.length
    }
  };
}

function parseShopMoveCapturedBody(body) {
  let text = String(body || "").trim();
  if (!text) return null;
  if (/^%5B|^%7B/i.test(text)) {
    try {
      text = decodeURIComponent(text);
    } catch {
      // Keep raw text when it was not URI encoded.
    }
  }
  text = text.replace(/^\)\]\}',?\s*/, "").trim();
  if (!/^[\[{]/.test(text)) {
    const start = Math.min(...["{", "["].map((char) => {
      const index = text.indexOf(char);
      return index < 0 ? Number.POSITIVE_INFINITY : index;
    }));
    const endObject = text.lastIndexOf("}");
    const endArray = text.lastIndexOf("]");
    const end = Math.max(endObject, endArray);
    if (Number.isFinite(start) && end > start) text = text.slice(start, end + 1).trim();
  }
  if (!/^[\[{]/.test(text)) return null;
  try {
    return JSON.parse(text);
  } catch {
    return null;
  }
}

function findFirstNestedObject(root, keyName) {
  const target = String(keyName || "");
  let found = null;
  walkShopMoveObject(root, (value, key) => {
    if (!found && key === target && isObject(value)) found = value;
  });
  return found;
}

function readNestedText(root, paths) {
  for (const path of paths || []) {
    let current = root;
    for (const key of path) {
      if (!isObject(current) && !Array.isArray(current)) {
        current = null;
        break;
      }
      current = current?.[key];
    }
    const value = stringifyShopMoveValue(current);
    if (value) return value;
  }
  return "";
}

function collectShopMoveValuesByKeys(root, keys, output) {
  const names = new Set((keys || []).map((key) => String(key || "").toLowerCase()));
  walkShopMoveObject(root, (value, key) => {
    if (!names.has(String(key || "").toLowerCase())) return;
    const text = stringifyShopMoveValue(value);
    if (text) output.push(text);
  });
}

function collectShopMoveImages(value, output) {
  if (!output) return;
  if (typeof value === "string") {
    for (const url of findImageUrls(value)) {
      if (isLikelyShopMoveImageUrl(url)) output.push(url);
    }
    if (isLikelyShopMoveImageUrl(value)) output.push(value);
    return;
  }
  if (Array.isArray(value)) {
    for (const item of value.slice(0, 160)) collectShopMoveImages(item, output);
    return;
  }
  if (!isObject(value)) return;
  for (const [key, entry] of Object.entries(value)) {
    const lowerKey = key.toLowerCase();
    if (/(url|uri|image|img|pic|cover|thumb|media|content)/.test(lowerKey)) {
      collectShopMoveImages(entry, output);
    } else if (Array.isArray(entry) || isObject(entry)) {
      collectShopMoveImages(entry, output);
    }
  }
}

function collectShopMoveProductFormat(value, attributes, specImages) {
  const entries = Array.isArray(value) ? value : (isObject(value) ? Object.values(value) : []);
  for (const entry of entries.slice(0, 80)) {
    if (!isObject(entry)) continue;
    collectShopMoveImages(entry.pic_urls || entry.picUrls || entry.image || entry.images, specImages);
    const key = stringifyShopMoveValue(entry.name || entry.key || entry.title || entry.format_key || entry.formatKey);
    const directValue = stringifyShopMoveValue(entry.value || entry.text || entry.desc);
    const formatItems = Array.isArray(entry.format) ? entry.format : (Array.isArray(entry.values) ? entry.values : []);
    const values = [];
    for (const item of formatItems.slice(0, 40)) {
      collectShopMoveImages(item, specImages);
      const text = isObject(item)
        ? stringifyShopMoveValue(item.name || item.value || item.text || item.title)
        : stringifyShopMoveValue(item);
      if (text) values.push(text);
    }
    if (key && (directValue || values.length)) attributes[key.slice(0, 80)] = (directValue || values.join("、")).slice(0, 240);
  }
}

function collectShopMoveAttributePairs(root, attributes) {
  walkShopMoveObject(root, (value, key) => {
    const lowerKey = String(key || "").toLowerCase();
    if (!/(attribute|attr|base_attr|baseattr|prop|parameter|param|product_format|productformat)/.test(lowerKey)) return;
    if (Array.isArray(value)) {
      for (const item of value.slice(0, 80)) {
        if (!isObject(item)) continue;
        const name = stringifyShopMoveValue(item.name || item.key || item.title || item.property_name || item.propertyName);
        const text = stringifyShopMoveValue(item.value || item.text || item.property_value || item.propertyValue);
        if (name && text) attributes[name.slice(0, 80)] = text.slice(0, 240);
      }
    } else if (isObject(value)) {
      for (const [attrKey, attrValue] of Object.entries(value).slice(0, 80)) {
        const text = stringifyShopMoveValue(attrValue);
        if (attrKey && text) attributes[String(attrKey).slice(0, 80)] = text.slice(0, 240);
      }
    }
  });
}

function collectShopMoveSkuCandidates(root, output) {
  walkShopMoveObject(root, (value, key) => {
    const lowerKey = String(key || "").toLowerCase();
    if (Array.isArray(value) && /(sku|skus|sku_list|skulist|skuinfos|sku_infos|skuinfo|sku_info)/.test(lowerKey)) {
      output.push(...value.filter(isObject));
      return;
    }
    if (isObject(value) && /(sku_id|skuid|skuId|skuID|sku_code|skuCode)/.test(Object.keys(value).join(","))) {
      output.push(value);
    }
  });
}

function collectShopMoveSpecGroups(root, specs, specImages) {
  walkShopMoveObject(root, (value, key) => {
    const lowerKey = String(key || "").toLowerCase();
    if (!Array.isArray(value) || !/(spec|saleprop|sale_prop|property|properties)/.test(lowerKey)) return;
    for (const group of value.slice(0, 40)) {
      if (!isObject(group)) continue;
      const name = stringifyShopMoveValue(group.name || group.title || group.key || group.property_name || group.propertyName);
      const values = [];
      const options = Array.isArray(group.values) ? group.values
        : Array.isArray(group.options) ? group.options
          : Array.isArray(group.children) ? group.children
            : [];
      for (const option of options.slice(0, 80)) {
        collectShopMoveImages(option, specImages);
        const text = isObject(option)
          ? stringifyShopMoveValue(option.name || option.value || option.text || option.title)
          : stringifyShopMoveValue(option);
        if (text) values.push(text);
      }
      if (name && values.length) specs[name.slice(0, 80)] = uniqueStrings(values).join("、").slice(0, 240);
    }
  });
}

function collectShopMoveDouyinSkuInfo(root, specs, specImages, skuCandidates) {
  const info = findShopMoveDouyinSkuInfo(root);
  if (!info) return;
  const groups = firstShopMoveArray(info.specs, info.spec_list, info.specList, info.sale_props, info.saleProps, info.sale_properties, info.saleProperties);
  const picMap = isObject(info.big_pic) ? info.big_pic : (isObject(info.pic) ? info.pic : {});
  const itemNameById = new Map();
  const itemImageById = new Map();

  for (const group of groups.slice(0, 20)) {
    if (!isObject(group)) continue;
    const groupName = stringifyShopMoveValue(group.name || group.title || group.spec_name || group.specName) || "规格";
    const options = Array.isArray(group.spec_items) ? group.spec_items
      : Array.isArray(group.specItems) ? group.specItems
        : Array.isArray(group.values) ? group.values
          : Array.isArray(group.options) ? group.options
            : [];
    const values = [];
    for (const option of options.slice(0, 120)) {
      if (!isObject(option)) continue;
      const name = stringifyShopMoveValue(option.name || option.value || option.text || option.title);
      const ids = uniqueStrings([
        option.id,
        option.spec_item_id,
        option.specItemId,
        option.item_id,
        option.itemId,
        option.value_id,
        option.valueId
      ]);
      const image = firstShopMoveImageFromValue(
        firstShopMoveMapValue(picMap, ids) || option.big_pic || option.bigPic || option.pic || option.image || option.img || option.url
      );
      if (name) values.push(name);
      for (const id of ids) {
        if (name) itemNameById.set(id, { groupName, name });
        if (image) itemImageById.set(id, image);
      }
      if (image) specImages.push(image);
      if (name || image) {
        skuCandidates.push({
          sku_id: ids[0] || "",
          name,
          spec: name ? { [groupName]: name } : {},
          image,
          images: image ? [image] : []
        });
      }
    }
    if (groupName && values.length) {
      specs[groupName.slice(0, 80)] = uniqueStrings(values).join("、").slice(0, 240);
    }
  }

  const skuRows = firstShopMoveArray(info.skus, info.sku_list, info.skuList, info.sku_infos, info.skuInfos, info.sku_info_list, info.skuInfoList);
  for (const row of skuRows.slice(0, 240)) {
    if (!isObject(row)) continue;
    const ids = collectShopMoveSkuSpecIds(row);
    const spec = normalizeShopMoveCapturedSpec(row.spec || row.specs || row.properties || row.sale_properties || row.saleProperties || row.props);
    for (const id of ids) {
      const mapped = itemNameById.get(id);
      if (mapped?.groupName && mapped?.name && !spec[mapped.groupName]) spec[mapped.groupName] = mapped.name;
    }
    const image = firstShopMoveImageFromValue(row.image || row.img || row.pic || row.sku_pic || row.skuPic || firstShopMoveMapValue(picMap, ids))
      || ids.map((id) => itemImageById.get(id)).find(Boolean)
      || "";
    skuCandidates.push({
      ...row,
      spec,
      image: image || row.image || row.img || row.pic || "",
      images: image ? [image] : row.images
    });
    if (image) specImages.push(image);
  }
}

function findShopMoveDouyinSkuInfo(root) {
  if (isShopMoveDouyinSkuInfo(root)) return root;
  if (isShopMoveDouyinSkuInfo(root?.data)) return root.data;
  let found = null;
  walkShopMoveObject(root, (value) => {
    if (!found && isShopMoveDouyinSkuInfo(value)) found = value;
  });
  return found;
}

function isShopMoveDouyinSkuInfo(value) {
  if (!isObject(value)) return false;
  const hasSpecs = firstShopMoveArray(value.specs, value.spec_list, value.specList, value.sale_props, value.saleProps, value.sale_properties, value.saleProperties).length > 0;
  const hasSkuRows = firstShopMoveArray(value.skus, value.sku_list, value.skuList, value.sku_infos, value.skuInfos, value.sku_info_list, value.skuInfoList).length > 0;
  return hasSpecs && (hasSkuRows || isObject(value.big_pic) || isObject(value.pic));
}

function firstShopMoveArray(...values) {
  for (const value of values) {
    if (Array.isArray(value)) return value;
  }
  return [];
}

function collectShopMoveSkuSpecIds(row) {
  const ids = [];
  const sources = [
    row.spec_item_ids,
    row.specItemIds,
    row.spec_item_id_list,
    row.specItemIdList,
    row.spec_ids,
    row.specIds,
    row.spec_id,
    row.specId,
    row.spec_detail_ids,
    row.specDetailIds,
    row.spec_detail_id_list,
    row.specDetailIdList,
    row.sale_prop_ids,
    row.salePropIds,
    row.sale_property_ids,
    row.salePropertyIds
  ];
  for (const source of sources) {
    if (Array.isArray(source)) ids.push(...source.map(stringifyShopMoveValue));
    else if (source != null) ids.push(...String(source).split(/[,;|_\s]+/).map((part) => part.trim()));
  }
  return uniqueStrings(ids).filter(Boolean);
}

function firstShopMoveMapValue(map, keys) {
  if (!isObject(map)) return null;
  for (const key of keys || []) {
    if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
  }
  return null;
}

function firstShopMoveImageFromValue(value) {
  const images = [];
  collectShopMoveImages(value, images);
  return uniqueStrings(images)[0] || "";
}

function normalizeShopMoveCapturedSkuList(candidates) {
  const result = [];
  const seen = new Set();
  for (const item of candidates || []) {
    if (!isObject(item)) continue;
    const spec = normalizeShopMoveCapturedSpec(item.spec || item.specs || item.properties || item.sale_properties || item.saleProperties || item.props || item.sale_props || item.saleProps);
    const images = [];
    collectShopMoveImages(item, images);
    const image = firstShopMoveImageFromValue(item.image || item.img || item.pic || item.sku_pic || item.skuPic || item.sku_img || item.skuImg)
      || uniqueStrings(images)[0]
      || "";
    const sku = {
      sku_id: stringifyShopMoveValue(item.sku_id || item.skuId || item.id || item.skuID || item.sku_code || item.skuCode),
      name: stringifyShopMoveValue(item.name || item.title || item.sku_name || item.skuName) || Object.values(spec).join(" "),
      price: normalizeShopMovePrice(readShopMoveReferencePriceValue(item.price_info || item.priceInfo || item.sale_price_info || item.salePriceInfo || item.price || item.sale_price || item.salePrice || item.min_price || item.minPrice || "")),
      stock: stringifyShopMoveValue(item.stock || item.stock_num || item.stockNum || item.available_stock || item.availableStock || item.inventory || item.stock_info || item.stockInfo || ""),
      spec,
      image
    };
    const fingerprint = `${sku.sku_id}|${sku.name}|${JSON.stringify(sku.spec)}|${sku.price}`;
    if (!sku.sku_id && !sku.name && !Object.keys(spec).length) continue;
    if (seen.has(fingerprint)) continue;
    seen.add(fingerprint);
    result.push(sku);
    if (result.length >= 120) break;
  }
  return result;
}

function normalizeShopMoveCapturedSpec(value) {
  if (!value) return {};
  const result = {};
  if (Array.isArray(value)) {
    for (const entry of value.slice(0, 40)) {
      if (!isObject(entry)) continue;
      const key = stringifyShopMoveValue(entry.name || entry.key || entry.title || entry.property_name || entry.propertyName);
      const text = stringifyShopMoveValue(entry.value || entry.text || entry.option || entry.property_value || entry.propertyValue);
      if (key && text) result[key.slice(0, 80)] = text.slice(0, 120);
    }
    return result;
  }
  if (isObject(value)) {
    for (const [key, entry] of Object.entries(value).slice(0, 40)) {
      const text = stringifyShopMoveValue(entry);
      if (key && text) result[String(key).slice(0, 80)] = text.slice(0, 120);
    }
  }
  return result;
}

function walkShopMoveObject(value, visitor, depth = 0) {
  if (depth > 8 || value == null) return;
  if (Array.isArray(value)) {
    for (const item of value.slice(0, 160)) walkShopMoveObject(item, visitor, depth + 1);
    return;
  }
  if (!isObject(value)) return;
  for (const [key, entry] of Object.entries(value).slice(0, 240)) {
    visitor(entry, key, value);
    if (Array.isArray(entry) || isObject(entry)) walkShopMoveObject(entry, visitor, depth + 1);
  }
}

function stringifyShopMoveValue(value) {
  if (value == null) return "";
  if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
  if (typeof value === "string") return cleanHtmlText(value);
  if (Array.isArray(value)) return value.map(stringifyShopMoveValue).filter(Boolean).join("、");
  if (isObject(value)) {
    const preferred = value.name || value.title || value.value || value.text || value.desc || value.price;
    return preferred == null ? "" : stringifyShopMoveValue(preferred);
  }
  return "";
}

function firstUsableShopMoveCapturedTitle(values) {
  let bestTitle = "";
  let bestScore = Number.NEGATIVE_INFINITY;
  for (const value of uniqueStrings(values).map(cleanShopMoveTitle).filter(Boolean)) {
    const score = scoreShopMoveTitleCandidate(value);
    if (score > bestScore) {
      bestScore = score;
      bestTitle = value;
    }
  }
  return bestScore > Number.NEGATIVE_INFINITY ? bestTitle : "";
}

function pushText(output, value) {
  const text = stringifyShopMoveValue(value);
  if (text) output.push(text);
}

function isLikelyShopMoveImageUrl(value) {
  const text = String(value || "").trim();
  if (!/^https?:\/\//i.test(text)) return false;
  if (isBadShopMoveImageUrl(text)) return false;
  if (!/(ecombdimg|douyinpic|pstatp|byteimg|bytedance|snssdk|jinritemai|douyin)/i.test(text)) return false;
  return /\.(?:jpg|jpeg|png|webp|avif)(?:\?|$)/i.test(text) || /(image|img|tos-|obj\/|product|goods|webp|jpeg|png|avif)/i.test(text);
}

function isLikelyShopMoveProductImageUrl(value) {
  const text = String(value || "").trim();
  if (!isLikelyShopMoveImageUrl(text)) return false;
  return !isBadShopMoveImageUrl(text);
}

function uniqueShopMoveProductImages(values) {
  return uniqueStrings(values).filter(isLikelyShopMoveProductImageUrl);
}

function isBadShopMoveImageUrl(value) {
  return /(avatar|logo|icon|sprite|xianhuo|product_detail_head|cart|shopping|shop[_-]?cart|empty|placeholder|default|loading|head|profile|user|comment|rate|coupon|service|qrcode|qr_code)/i.test(String(value || ""));
}

async function fetchShopMoveImageDataUrl(rawUrl) {
  const url = String(rawUrl || "").trim();
  if (!isLikelyShopMoveImageUrl(url)) throw new Error("图片地址不在允许的商品图片域名内。");
  const attempts = [
    {
      cache: "no-store",
      credentials: "omit"
    },
    {
      cache: "reload",
      credentials: "include",
      referrer: "https://haohuo.jinritemai.com/",
      referrerPolicy: "strict-origin-when-cross-origin"
    },
    {
      cache: "force-cache",
      credentials: "include"
    }
  ];
  let lastError = null;
  for (const attempt of attempts) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 20000);
    try {
      const response = await fetch(url, {
      method: "GET",
      cache: attempt.cache,
      credentials: attempt.credentials,
      redirect: "follow",
      referrer: attempt.referrer,
      referrerPolicy: attempt.referrerPolicy,
      headers: {
        Accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
      },
      signal: controller.signal
      });
      if (!response.ok) throw new Error(`图片请求失败：HTTP ${response.status}`);
      const contentLength = Number(response.headers.get("content-length") || 0);
      if (contentLength > SHOP_MOVE_IMAGE_PROXY_MAX_BYTES) throw new Error("图片超过 8MB，已跳过。");
      const buffer = await response.arrayBuffer();
      if (buffer.byteLength < 32) throw new Error("图片内容为空，已跳过。");
      if (buffer.byteLength > SHOP_MOVE_IMAGE_PROXY_MAX_BYTES) throw new Error("图片超过 8MB，已跳过。");
      const mime = detectShopMoveImageMime(buffer, response.headers.get("content-type") || "", url);
      if (!mime) throw new Error("图片地址返回的不是图片内容。");
      return {
        url,
        mime,
        name: buildShopMoveImageFileName(url, mime),
        data_url: `data:${mime};base64,${arrayBufferToBase64(buffer)}`
      };
    } catch (error) {
      lastError = error?.name === "AbortError" ? new Error("图片读取超时，已跳过。") : error;
    } finally {
      clearTimeout(timer);
    }
  }
  throw lastError || new Error("图片读取失败。");
}

async function uploadShopMoveImagesInMainWorld(sender, rawUrls, maxCount = 5) {
  const tabId = Number(sender?.tab?.id || 0);
  if (!tabId) throw new Error("没有找到抖店发布页。");
  const imageLimit = Math.max(1, Math.min(50, Number(maxCount) || 5));
  const urls = uniqueStrings(Array.isArray(rawUrls) ? rawUrls : [rawUrls])
    .filter(isLikelyShopMoveImageUrl)
    .slice(0, imageLimit);
  if (!urls.length) throw new Error("没有可上传的商品图片。");
  const images = [];
  for (let index = 0; index < urls.length; index += 1) {
    try {
      const payload = await fetchShopMoveImageDataUrl(urls[index]);
      images.push({
        url: payload.url,
        dataUrl: payload.data_url,
        name: payload.name || `shop-move-${index + 1}.jpg`,
        mime: payload.mime || "image/jpeg"
      });
    } catch (error) {
      images.push({
        url: urls[index],
        error: error.message || "图片读取失败"
      });
    }
  }
  const usableImages = images.filter((item) => item.dataUrl);
  if (!usableImages.length) {
    throw new Error(images.find((item) => item.error)?.error || "图片读取失败。");
  }
  const results = await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    world: "MAIN",
    func: uploadShopMoveImagesInPageMainWorld,
    args: [usableImages, imageLimit]
  });
  const result = Array.isArray(results) ? results[0]?.result : null;
  if (!result?.ok) throw new Error(result?.error || "图片上传失败。");
  return {
    requested_count: urls.length,
    prepared_count: usableImages.length,
    uploaded_count: Number(result.uploaded_count || 0),
    preview_count: Number(result.preview_count || 0),
    failed_count: images.filter((item) => item.error).length,
    errors: images.filter((item) => item.error).map((item) => item.error).slice(0, 5)
  };
}

async function uploadShopMoveImagesInPageMainWorld(images, maxCount = 5) {
  const imageLimit = Math.max(1, Math.min(50, Number(maxCount) || 5));
  const cleanImages = Array.isArray(images) ? images.filter((item) => item?.dataUrl).slice(0, imageLimit) : [];
  if (!cleanImages.length) return { ok: false, error: "没有可上传的商品图片。" };
  let uploadedCount = 0;
  const errors = [];
  const firstInput = findNextImageInput();
  if (imageLimit > 5 && firstInput?.multiple) {
    try {
      const beforePreviewCount = countUploadPreviews();
      const dataTransfer = new DataTransfer();
      for (let index = 0; index < cleanImages.length; index += 1) {
        dataTransfer.items.add(await buildUploadJpegFile(cleanImages[index], index));
      }
      firstInput.files = dataTransfer.files;
      firstInput.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
      firstInput.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
      uploadedCount = dataTransfer.files.length;
      await waitForPreviewIncrease(beforePreviewCount, 20000);
      const previewCount = countUploadPreviews();
      return {
        ok: uploadedCount > 0 || previewCount > 0,
        uploaded_count: uploadedCount,
        preview_count: previewCount,
        errors,
        error: ""
      };
    } catch (error) {
      errors.push(error?.message || "批量图片上传失败");
    }
  }
  for (let index = 0; index < cleanImages.length; index += 1) {
    try {
      const input = findNextImageInput();
      if (!input) throw new Error("未找到主图上传控件。");
      const beforePreviewCount = countUploadPreviews();
      const file = await buildUploadJpegFile(cleanImages[index], index);
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);
      input.files = dataTransfer.files;
      input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
      input.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
      uploadedCount += 1;
      await waitForPreviewIncrease(beforePreviewCount, 12000);
    } catch (error) {
      errors.push(error?.message || "图片上传失败");
    }
  }
  const previewCount = countUploadPreviews();
  return {
    ok: uploadedCount > 0 || previewCount > 0,
    uploaded_count: uploadedCount,
    preview_count: previewCount,
    errors,
    error: errors[0] || ""
  };

  function findNextImageInput() {
    const inputs = Array.from(document.querySelectorAll('input[type="file"]')).filter(isImageInput);
    if (!inputs.length) return null;
    return inputs.find(isEmptyUploadInput) || inputs[0];
  }

  function isImageInput(input) {
    const accept = String(input.getAttribute("accept") || "").toLowerCase();
    if (accept && !accept.includes("image") && !/\.(jpe?g|png|webp|gif)/.test(accept)) return false;
    const slot = findUploadSlot(input);
    if (slot && !isVisibleMainWorld(slot)) return false;
    const slotText = normalizeMainWorldText(slot?.textContent || "");
    return Boolean(
      input.closest(".material-upload-button") ||
      input.closest("[class*='upload' i]") ||
      /上传主图|上传辅助图|商品正面图|上传图片|商详图片|商品详情/.test(slotText)
    );
  }

  function isEmptyUploadInput(input) {
    const slot = findUploadSlot(input);
    if (!slot) return true;
    const slotText = normalizeMainWorldText(slot.textContent || "");
    return !slot.querySelector("img") && (/上传主图|上传辅助图|商品正面图|上传图片|商详图片|商品详情|\+/.test(slotText) || !slotText);
  }

  function findUploadSlot(input) {
    const directSlot = input.closest(".material-upload-button");
    if (directSlot) return directSlot;
    let node = input.parentElement;
    while (node && node !== document.body) {
      const textValue = normalizeMainWorldText(node.textContent || "");
      const rect = node.getBoundingClientRect();
      const looksLikeSingleSlot = rect.width > 20 && rect.height > 20 && rect.width <= 260 && rect.height <= 260;
      if (looksLikeSingleSlot && (/上传主图|上传辅助图|商品正面图|上传图片|商详图片|商品详情|\+/.test(textValue) || node.querySelector("img"))) {
        return node;
      }
      node = node.parentElement;
    }
    return input.closest("[class*='upload' i],[class*='material' i]") || input.parentElement;
  }

  function countUploadPreviews() {
    const images = new Set();
    const roots = Array.from(document.querySelectorAll(".material-upload-button,[class*='upload' i],[class*='material' i]"));
    for (const root of roots) {
      for (const image of root.querySelectorAll("img")) {
        const rect = image.getBoundingClientRect();
        if ((rect.width > 20 && rect.height > 20) || image.currentSrc || image.src) images.add(image);
      }
    }
    return images.size;
  }

  async function waitForPreviewIncrease(beforeCount, timeoutMs) {
    const startedAt = Date.now();
    while (Date.now() - startedAt < timeoutMs) {
      const afterCount = countUploadPreviews();
      if (afterCount > beforeCount) return true;
      await waitMainWorld(350);
    }
    return false;
  }

  function normalizeMainWorldText(value) {
    return String(value || "").replace(/\s+/g, "");
  }

  function isVisibleMainWorld(node) {
    if (!node || typeof node.getBoundingClientRect !== "function") return false;
    const rect = node.getBoundingClientRect();
    const style = getComputedStyle(node);
    return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
  }

  async function buildUploadJpegFile(imagePayload, index) {
    const image = await loadImage(imagePayload.dataUrl);
    const width = Math.max(1, image.naturalWidth || image.width || 0);
    const height = Math.max(1, image.naturalHeight || image.height || 0);
    const scale = Math.max(1, 600 / width, 600 / height);
    const canvas = document.createElement("canvas");
    canvas.width = Math.round(width * scale);
    canvas.height = Math.round(height * scale);
    const context = canvas.getContext("2d");
    if (!context) throw new Error("图片转换失败");
    context.fillStyle = "#ffffff";
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    const blob = await toBlob(canvas, "image/jpeg", 0.92);
    return new File([blob], buildFileName(imagePayload.name, index), {
      type: "image/jpeg",
      lastModified: Date.now()
    });
  }

  function loadImage(dataUrl) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.onload = () => resolve(image);
      image.onerror = () => reject(new Error("图片解码失败"));
      image.src = dataUrl;
    });
  }

  function toBlob(canvas, mime, quality) {
    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) resolve(blob);
        else reject(new Error("图片转换失败"));
      }, mime, quality);
    });
  }

  function buildFileName(name, index) {
    const raw = String(name || "").replace(/\.[a-z0-9]{2,5}$/i, "").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 48);
    return `${raw || `shop-move-${index + 1}`}.jpg`;
  }

  function waitMainWorld(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

function normalizeShopMoveImageMime(contentType, url) {
  const lower = String(contentType || "").toLowerCase();
  if (/image\/(jpeg|jpg|png|webp|gif|avif)/.test(lower)) {
    return lower.includes("jpg") ? "image/jpeg" : lower.match(/image\/(jpeg|png|webp|gif|avif)/)?.[0] || "image/jpeg";
  }
  const ext = String(url || "").split("?")[0].match(/\.([a-z0-9]{3,5})$/i)?.[1]?.toLowerCase() || "";
  if (ext === "png") return "image/png";
  if (ext === "webp") return "image/webp";
  if (ext === "gif") return "image/gif";
  if (ext === "avif") return "image/avif";
  return "image/jpeg";
}

function detectShopMoveImageMime(buffer, contentType, url) {
  const bytes = new Uint8Array(buffer || new ArrayBuffer(0));
  if (bytes.length >= 12) {
    if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return "image/jpeg";
    if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) return "image/png";
    if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) return "image/gif";
    const ascii = String.fromCharCode(...bytes.slice(0, 16));
    if (ascii.startsWith("RIFF") && ascii.slice(8, 12) === "WEBP") return "image/webp";
    if (ascii.slice(4, 12) === "ftypavif" || ascii.slice(4, 12) === "ftypavis") return "image/avif";
  }
  return String(contentType || "").toLowerCase().includes("image/")
    ? normalizeShopMoveImageMime(contentType, url)
    : "";
}

function buildShopMoveImageFileName(url, mime) {
  const ext = mime === "image/png" ? "png" : mime === "image/webp" ? "webp" : mime === "image/gif" ? "gif" : mime === "image/avif" ? "avif" : "jpg";
  const rawName = String(url || "").split("?")[0].split("/").filter(Boolean).pop() || `shop-move-${Date.now()}`;
  const safeName = rawName.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/\.(jpeg|jpg|png|webp|gif|avif)$/i, "").slice(0, 48) || "shop-move-image";
  return `${safeName}.${ext}`;
}

function arrayBufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  const chunkSize = 0x8000;
  let binary = "";
  for (let offset = 0; offset < bytes.length; offset += chunkSize) {
    binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
  }
  return btoa(binary);
}

function getLatestShopMoveAiPublishTask(taskId = "") {
  const cleanTaskId = String(taskId || "").trim();
  if (cleanTaskId) return shopMoveAiPublishTasks.get(cleanTaskId) || null;
  return Array.from(shopMoveAiPublishTasks.values()).at(-1) || null;
}

function shopMoveAiTaskPayload(task) {
  return {
    id: task.id,
    status: task.status,
    message: task.message,
    activeTabId: task.activeTabId || 0,
    createdAt: task.createdAt,
    updatedAt: task.updatedAt,
    items: task.items.map((item) => ({
      id: item.id,
      index: item.index,
      url: item.url,
      sourceItemId: item.sourceItemId,
      status: item.status,
      message: item.message,
      tabId: item.tabId || 0,
      product: {
        title: item.product?.title || "",
        price: item.product?.price || "",
        stock: item.product?.stock || "",
        outer_id: item.product?.outer_id || "",
        image_count: Array.isArray(item.product?.images) ? item.product.images.length : 0,
        main_image_count: Array.isArray(item.product?.main_images) ? item.product.main_images.length : 0,
        detail_image_count: Array.isArray(item.product?.detail_images) ? item.product.detail_images.length : 0,
        sku_count: Array.isArray(item.product?.sku_list) ? item.product.sku_list.length : 0,
        source: item.product?.source || ""
      },
      result: item.result || null
    })),
    logs: task.logs.slice(-40)
  };
}

function withShopMoveTimeout(promise, ms, message) {
  let timer = 0;
  const timeout = new Promise((_, reject) => {
    timer = setTimeout(() => reject(new Error(message || "请求超时")), Math.max(1000, Number(ms) || 30000));
  });
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}

async function resolveShopMoveAiProductPackage(item, config) {
  const strictUrl = isShopMoveStrictRenderedCaptureUrl(item.url);
  const renderedSnapshot = await fetchShopMoveRenderedProductSnapshot(item).catch((error) => {
    item.renderParserMessage = error.message || "商品页渲染解析不可用";
    return null;
  });
  if (renderedSnapshot && !hasShopMoveCompleteCapturedPackage(renderedSnapshot)) {
    item.renderParserMessage = buildShopMoveIncompletePackageMessage(getShopMoveCapturedPackageState(renderedSnapshot));
  }
  if (renderedSnapshot && !hasUsableShopMoveProductPackage(renderedSnapshot.product)) {
    const recordCount = Array.isArray(renderedSnapshot.api_records) ? renderedSnapshot.api_records.length : 0;
    if (recordCount) item.renderParserMessage = `商品页已捕获 ${recordCount} 条接口记录，正在交给后端解析`;
  }
  const serverProduct = await resolveShopMoveAiProductPackageFromServer(item, config, renderedSnapshot).catch((error) => {
    item.parserFallbackMessage = error.message || "解析服务不可用";
    return null;
  });
  let bestProduct = null;
  if (serverProduct && hasUsableShopMoveProductPackage(serverProduct)) {
    bestProduct = renderedSnapshot?.product ? mergeShopMoveProductPackages(renderedSnapshot.product, serverProduct) : serverProduct;
  }
  if (!bestProduct && renderedSnapshot?.product) {
    const renderedProduct = normalizeShopMoveResolvedProduct(renderedSnapshot.product, item, config);
    renderedProduct.parser_fallback_message = item.parserFallbackMessage || "";
    if (hasUsableShopMoveProductPackage(renderedProduct)) bestProduct = renderedProduct;
  }
  if (bestProduct) {
    bestProduct = await enrichShopMoveProductWithDouyinSku(bestProduct, item);
    const finalProduct = finalizeShopMoveParsedProduct(bestProduct, item, config, renderedSnapshot);
    const packageState = getShopMoveCapturedPackageState({
      ...(isObject(renderedSnapshot) ? renderedSnapshot : {}),
      product: finalProduct
    });
    if (strictUrl && !packageState.complete) {
      throw buildShopMovePartialParseError(buildShopMoveIncompletePackageMessage(packageState), finalProduct);
    }
    if (hasUsableShopMoveProductPackage(finalProduct)) return finalProduct;
  }

  if (strictUrl) {
    let partialProduct = renderedSnapshot?.product
      ? finalizeShopMoveParsedProduct(normalizeShopMoveResolvedProduct(renderedSnapshot.product, item, config), item, config, renderedSnapshot)
      : {};
    if (hasUsableShopMoveProductPackage(partialProduct)) {
      partialProduct = finalizeShopMoveParsedProduct(
        await enrichShopMoveProductWithDouyinSku(partialProduct, item),
        item,
        config,
        renderedSnapshot
      );
    }
    const packageState = getShopMoveCapturedPackageState({
      ...(isObject(renderedSnapshot) ? renderedSnapshot : {}),
      product: partialProduct
    });
    throw buildShopMovePartialParseError(
      item.parserFallbackMessage || item.renderParserMessage || buildShopMoveIncompletePackageMessage(packageState),
      partialProduct
    );
  }

  const base = buildShopMoveAiProduct(item, config, (Number(item.index) || 1) - 1);
  const page = await fetchShopMoveProductPage(item.normalizedUrl || item.url);
  const parsed = parseShopMoveProductPage(page.html, page.url);
  const title = applyShopMoveTitleRule(parsed.title, config);
  const rawProduct = await enrichShopMoveProductWithDouyinSku({
    ...base,
    ...parsed,
    title,
    price: parsed.price || base.price,
    stock: base.stock || parsed.stock || "",
    outer_id: base.outer_id,
    source_url: item.url,
    source_item_id: item.sourceItemId,
    resolved_url: page.url,
    source: parsed.source || "page",
    parser_fallback_message: item.parserFallbackMessage || ""
  }, item);
  const product = finalizeShopMoveParsedProduct(rawProduct, item, config, null);
  if (!hasUsableShopMoveProductPackage(product)) {
    throw new Error("商品信息包解析失败：未识别到商品标题，已停止打开抖店发品页。");
  }
  return product;
}

async function resolveShopMoveAiProductPackageFromServer(item, config, renderedSnapshot = null) {
  const auth = await getPluginAuthState({ refresh: false });
  if (!auth.token) throw new Error("请先登录 MMW工作台账号");
  let snapshots = [];
  if (renderedSnapshot) {
    snapshots = [renderedSnapshot];
  } else {
    const page = await fetchShopMoveProductPage(item.normalizedUrl || item.url);
    snapshots = [{
      url: item.url,
      normalized_url: item.normalizedUrl,
      source_item_id: item.sourceItemId,
      resolved_url: page.url,
      html: String(page.html || "").slice(0, SHOP_MOVE_PARSER_HTML_LIMIT)
    }];
  }
  const payload = await requestJson(auth.baseUrl, "POST", "/api/shop-move/parse-products", {
    links: item.url,
    config: config || {},
    snapshots
  }, auth.token, 60000);
  const data = unwrapPayload(payload);
  const parsed = Array.isArray(data.products) ? data.products[0] : null;
  if (!parsed || parsed.status !== "ready") {
    throw new Error(parsed?.message || "解析服务未返回商品信息包");
  }
  const product = normalizeShopMoveResolvedProduct(parsed.product || {}, item, config);
  if (!hasUsableShopMoveProductPackage(product)) {
    throw new Error("解析服务未识别到商品标题");
  }
  return product;
}

function buildShopMoveParseProductPayload(item, status, message, product) {
  const parsedProduct = isObject(product) ? product : {};
  return {
    status,
    message,
    url: item.url,
    normalized_url: item.normalizedUrl,
    source_item_id: item.sourceItemId,
    product: {
      ...parsedProduct,
      source_url: parsedProduct.source_url || item.url,
      normalized_url: parsedProduct.normalized_url || item.normalizedUrl,
      source_item_id: parsedProduct.source_item_id || item.sourceItemId
    }
  };
}

function cacheShopMoveParseProductPayload(payload) {
  if (!isObject(payload)) return;
  pruneShopMoveParseResultCache();
  const now = Date.now();
  const keys = [
    payload.url,
    payload.normalized_url,
    payload.source_item_id,
    payload.product?.source_url,
    payload.product?.normalized_url,
    payload.product?.source_item_id
  ]
    .map((value) => String(value || "").trim())
    .filter(Boolean);
  for (const key of keys) {
    shopMoveParseResultCache.set(key, {
      updatedAt: now,
      payload
    });
  }
}

function summarizeShopMoveParseResultCache(limit = 10) {
  pruneShopMoveParseResultCache();
  const grouped = new Map();
  for (const entry of shopMoveParseResultCache.values()) {
    if (!entry?.payload) continue;
    const payload = entry.payload;
    const key = [
      payload.normalized_url,
      payload.url,
      payload.source_item_id
    ]
      .map((value) => String(value || "").trim())
      .filter(Boolean)
      .join("|");
    if (!key) continue;
    const current = grouped.get(key);
    if (!current || Number(entry.updatedAt || 0) >= Number(current.updatedAt || 0)) {
      grouped.set(key, {
        updatedAt: Number(entry.updatedAt || 0) || 0,
        payload
      });
    }
  }
  return {
    count: grouped.size,
    items: Array.from(grouped.values())
      .sort((left, right) => Number(right.updatedAt || 0) - Number(left.updatedAt || 0))
      .slice(0, Math.max(1, Math.min(50, Number(limit) || 10)))
      .map((entry) => ({
        updatedAt: entry.updatedAt,
        url: entry.payload?.url || "",
        normalized_url: entry.payload?.normalized_url || "",
        source_item_id: entry.payload?.source_item_id || "",
        status: entry.payload?.status || "",
        message: entry.payload?.message || "",
        product: summarizeShopMoveDebugProduct(entry.payload?.product)
      }))
  };
}

function pruneShopMoveParseResultCache() {
  const now = Date.now();
  for (const [key, entry] of shopMoveParseResultCache.entries()) {
    if (!entry || now - Number(entry.updatedAt || 0) > SHOP_MOVE_PARSE_RESULT_CACHE_TTL_MS) {
      shopMoveParseResultCache.delete(key);
    }
  }
}

function getCachedShopMoveParseProduct(link) {
  const keys = [
    link?.url,
    link?.normalizedUrl,
    link?.sourceItemId
  ]
    .map((value) => String(value || "").trim())
    .filter(Boolean);
  for (const key of keys) {
    const entry = shopMoveParseResultCache.get(key);
    if (entry?.payload) return entry.payload;
  }
  return null;
}

function collectCachedShopMoveProductsForLinks(linksText) {
  const links = parseShopMoveAiLinks(String(linksText || "").trim());
  const products = [];
  const seen = new Set();
  for (const link of links) {
    const cached = getCachedShopMoveParseProduct(link);
    if (!cached) continue;
    const key = [
      cached.normalized_url,
      cached.url,
      cached.source_item_id
    ].filter(Boolean).join("|") || JSON.stringify(cached).slice(0, 160);
    if (seen.has(key)) continue;
    seen.add(key);
    products.push(cached);
  }
  return products;
}

function parseShopMoveRecoveryLinks(message) {
  const items = Array.isArray(message?.items) ? message.items : [];
  if (items.length) {
    return items
      .map((item, index) => ({
        url: String(item?.url || "").trim(),
        normalizedUrl: String(item?.normalizedUrl || item?.normalized_url || "").trim(),
        sourceItemId: String(item?.sourceItemId || item?.source_item_id || "").trim(),
        index: index + 1
      }))
      .filter((item) => item.url || item.normalizedUrl || item.sourceItemId);
  }
  return parseShopMoveAiLinks(String(message?.links || message?.text || "").trim());
}

function buildShopMovePartialParseError(message, product) {
  const error = new Error(message || "商品信息包解析失败");
  if (isObject(product)) error.partialProduct = product;
  return error;
}

function finalizeShopMoveParsedProduct(product, item, config, renderedSnapshot = null) {
  const parsedProduct = isObject(product) ? product : {};
  const renderedProduct = isObject(renderedSnapshot?.product) ? renderedSnapshot.product : {};
  const rawTitle = firstUsableShopMoveCapturedTitle([
    parsedProduct.source_title,
    parsedProduct.title,
    renderedProduct.source_title,
    renderedProduct.title,
    parsedProduct.product_name,
    parsedProduct.productName,
    parsedProduct.name
  ]);
  const parsedMainImages = getShopMoveProductMainImages(parsedProduct);
  const renderedMainImages = getShopMoveProductMainImages(renderedProduct);
  let mainImages = pickPreferredShopMoveImages(renderedMainImages, parsedMainImages, renderedProduct, parsedProduct, "main")
    .slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
  const parsedDetailImages = getShopMoveProductDetailImages(parsedProduct);
  const renderedDetailImages = getShopMoveProductDetailImages(renderedProduct);
  let detailImages = pickPreferredShopMoveImages(renderedDetailImages, parsedDetailImages, renderedProduct, parsedProduct, "detail")
    .filter((url) => !mainImages.includes(url))
    .slice(0, SHOP_MOVE_DETAIL_IMAGE_LIMIT);
  const specImages = uniqueStrings([
    ...getShopMoveProductSpecImages(parsedProduct),
    ...getShopMoveProductSpecImages(renderedProduct)
  ]).filter(isLikelyShopMoveImageUrl);
  const images = uniqueShopMoveProductImages([
    ...mainImages,
    ...listFromAny(parsedProduct.images),
    ...listFromAny(renderedProduct.images),
    ...detailImages,
    ...specImages
  ]).slice(0, 100);
  const parsedAttributes = getShopMoveProductAttributes(parsedProduct);
  const renderedAttributes = getShopMoveProductAttributes(renderedProduct);
  let attributes = Object.keys(parsedAttributes).length
    ? { ...renderedAttributes, ...parsedAttributes }
    : renderedAttributes;
  const parsedSkuList = getShopMoveProductSkuList(parsedProduct);
  const renderedSkuList = getShopMoveProductSkuList(renderedProduct);
  const skuList = parsedSkuList.length ? parsedSkuList : renderedSkuList;
  const renderedPackageState = renderedSnapshot
    ? getShopMoveCapturedPackageState({
      ...(isObject(renderedSnapshot) ? renderedSnapshot : {}),
      product: renderedProduct
    })
    : null;
  if (renderedPackageState?.complete) {
    const renderedMainOnly = renderedMainImages.slice(0, SHOP_MOVE_MAIN_IMAGE_LIMIT);
    const preferRenderedMain = renderedMainOnly.length
      && (mainImages.length !== renderedMainOnly.length || renderedMainOnly.some((url) => !mainImages.includes(url)));
    if (preferRenderedMain) {
      mainImages = renderedMainOnly;
    }
    const renderedDetailOnly = renderedDetailImages
      .filter((url) => !mainImages.includes(url))
      .slice(0, SHOP_MOVE_DETAIL_IMAGE_LIMIT);
    const preferRenderedDetail = renderedDetailOnly.length
      && (detailImages.length !== renderedDetailOnly.length || renderedDetailOnly.some((url) => !detailImages.includes(url)));
    if (preferRenderedDetail) {
      detailImages = renderedDetailOnly;
    }
    const renderedAttributeKeys = Object.keys(renderedAttributes);
    const missingRenderedAttribute = renderedAttributeKeys.some((key) => !String(attributes?.[key] || "").trim());
    if (renderedAttributeKeys.length && (renderedAttributeKeys.length > Object.keys(attributes).length || missingRenderedAttribute)) {
      attributes = { ...renderedAttributes, ...attributes };
    }
  }
  const normalizedProduct = {
    ...parsedProduct,
    title: applyShopMoveTitleRule(rawTitle || parsedProduct.title || "", config),
    source_title: parsedProduct.source_title || rawTitle || parsedProduct.title || "",
    images,
    main_images: mainImages.length ? mainImages : images.slice(0, 1),
    main_image: mainImages[0] || images[0] || "",
    detail_images: detailImages.length ? detailImages : images.slice(mainImages.length || 1),
    spec_images: specImages,
    attributes,
    base_attr: attributes,
    sku_list: skuList
  };
  const referenceGoodsDetail = buildShopMoveReferenceGoodsDetail(normalizedProduct, config, item);
  const snapshot = {
    ...(isObject(renderedSnapshot) ? renderedSnapshot : {}),
    product: normalizedProduct
  };
  const packageState = getShopMoveCapturedPackageState(snapshot);
  const missingFields = packageState.missingFields || [];
  const records = Array.isArray(snapshot.api_records) ? snapshot.api_records : [];
  const capturedUrls = uniqueStrings(records.map((record) => String(record?.url || "").split("?")[0])).slice(0, 12);
  const parseDebug = {
    ...(isObject(parsedProduct.parse_debug) ? parsedProduct.parse_debug : {}),
    package_state: packageState,
    record_count: records.length,
    captured_urls: capturedUrls,
    capture_elapsed_ms: Number(snapshot.capture_elapsed_ms || 0) || 0,
    image_counts: {
      parsed_main: parsedMainImages.length,
      parsed_detail: parsedDetailImages.length,
      rendered_main: renderedMainImages.length,
      rendered_detail: renderedDetailImages.length,
      final_main: mainImages.length,
      final_detail: detailImages.length
    },
    sku_debug: isObject(parsedProduct.sku_debug) ? parsedProduct.sku_debug : null,
    price_missing: !String(normalizedProduct.price || normalizedProduct.sale_price || "").trim()
  };
  return {
    ...normalizedProduct,
    goods_detail: referenceGoodsDetail,
    goodsDetail: referenceGoodsDetail,
    main_img: referenceGoodsDetail.main_img,
    main_img_0: referenceGoodsDetail.main_img_0,
    detail_img: referenceGoodsDetail.detail_img,
    sku_sort: referenceGoodsDetail.sku_sort,
    sku_key: referenceGoodsDetail.sku_key,
    sku_key_list: referenceGoodsDetail.sku_key_list,
    sku_price: referenceGoodsDetail.sku_price,
    sku_price_after_treatment: referenceGoodsDetail.sku_price_after_treatment,
    sku_img: referenceGoodsDetail.sku_img,
    sku_img_all: referenceGoodsDetail.sku_img_all,
    categorySearchTextFromTitle: referenceGoodsDetail.categorySearchTextFromTitle,
    categoryMatchMode: referenceGoodsDetail.categoryMatchMode,
    useRecommendedCategory: referenceGoodsDetail.useRecommendedCategory,
    mainImages: normalizedProduct.main_images,
    detailImages: normalizedProduct.detail_images,
    specImages: normalizedProduct.spec_images,
    skuList,
    missing_fields: missingFields,
    missingFields,
    price_missing: parseDebug.price_missing,
    parse_debug: parseDebug,
    parseDebug
  };
}

function buildShopMoveIncompletePackageMessage(packageState) {
  const labels = shopMoveMissingFieldLabels(packageState?.missingFields || []);
  if ((packageState?.missingFields || []).includes("attributes")) {
    return `商品信息包解析失败：参数待补/移动端未捕获${labels.length ? `（缺少：${labels.join("、")}）` : ""}。已抓到的标题、主图或详情图会保留在采集行中。`;
  }
  return labels.length
    ? `商品信息包解析失败：缺少 ${labels.join("、")}。`
    : "商品信息包解析失败：未抓到完整商品接口包。";
}

function shopMoveMissingFieldLabels(fields) {
  const labels = {
    title: "标题",
    main_images: "主图",
    detail_images: "详情图",
    attributes: "详细参数",
    api_package: "商品接口包"
  };
  return uniqueStrings((fields || []).map((field) => labels[field] || field).filter(Boolean));
}

function buildShopMoveParseFailureMessage(error, item) {
  const primary = String(error?.message || "").trim();
  const details = uniqueStrings([
    primary,
    item?.renderParserMessage,
    item?.parserFallbackMessage
  ]).filter(Boolean);
  const joinedDetails = details
    .filter((text) => !/^商品信息包解析失败[:：]?/.test(text))
    .slice(0, 2)
    .join("；");
  const genericFailure = details.some((text) => /商品信息包解析失败|未识别到商品标题|未返回商品信息包|渲染解析未识别到商品信息|页面内容为空|解析超时|解析页已关闭|脚本暂时不可用/i.test(text));
  if (genericFailure) {
    return `解析失败：当前浏览器未获取到完整标题/主图/详情图/详细参数，请先在 Chrome 登录抖店/抖音并确认链接可正常打开后重试${joinedDetails ? `（${joinedDetails}）` : ""}`;
  }
  return primary ? `解析失败：${primary.replace(/^解析失败[:：]\s*/, "")}` : "解析失败：未获取到商品数据，请确认商品链接可正常打开后重试";
}

function normalizeShopMoveResolvedProduct(rawProduct, item, config) {
  const product = isObject(rawProduct) ? rawProduct : {};
  const base = buildShopMoveAiProduct(item, config, (Number(item.index) || 1) - 1);
  const resolvedTitle = firstUsableShopMoveCapturedTitle([
    product.title,
    product.source_title,
    product.product_name,
    product.productName,
    product.name,
    base.title,
    base.source_title
  ]);
  const mainImages = getShopMoveProductMainImages(product);
  const detailImages = getShopMoveProductDetailImages(product);
  const specImages = getShopMoveProductSpecImages(product);
  const images = uniqueShopMoveProductImages([
    ...mainImages,
    ...listFromAny(product.images),
    ...detailImages,
    ...specImages
  ]);
  const attributes = getShopMoveProductAttributes(product);
  const skuList = getShopMoveProductSkuList(product);
  return {
    ...base,
    ...product,
    title: applyShopMoveTitleRule(resolvedTitle, config),
    price: String(product.price || product.sale_price || product.min_price || base.price || ""),
    stock: String(base.stock || product.stock || product.quantity || ""),
    outer_id: String(base.outer_id || product.outer_id || product.sku_code || ""),
    product_id: String(product.product_id || product.goods_id || ""),
    goods_id: String(product.goods_id || ""),
    commodity_id: String(product.commodity_id || ""),
    promotion_id: String(product.promotion_id || item.sourceItemId || ""),
    description: String(product.description || product.detail || product.desc || ""),
    images,
    main_images: mainImages.length ? mainImages : images.slice(0, 1),
    main_image: mainImages[0] || images[0] || "",
    detail_images: detailImages.length ? detailImages : images.slice(1),
    spec_images: specImages,
    attributes,
    base_attr: attributes,
    specs: isObject(product.specs) ? product.specs : (product.specs || {}),
    sku_list: skuList,
    source_url: item.url,
    normalized_url: item.normalizedUrl,
    source_item_id: item.sourceItemId,
    resolved_url: String(product.resolved_url || ""),
    source: String(product.source || "server_parser"),
    parser_version: String(product.parser_version || "")
  };
}

async function fetchShopMoveRenderedProductSnapshot(item) {
  const url = item.normalizedUrl || item.url;
  if (!url) throw new Error("商品链接为空，无法渲染解析。");
  const captureScriptIds = await registerShopMoveSourceCaptureScripts().catch(() => []);
  const [previousActiveTab] = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
  const existingTab = await findShopMoveRenderedProductTab(item).catch(() => null);
  const createdForParse = !existingTab?.id;
  const tab = existingTab || await chrome.tabs.create({ url: "about:blank", active: false });
  const tabId = tab?.id || 0;
  if (!tabId) throw new Error("创建商品解析页失败。");
  const parseTabState = {
    closed: false,
    closedAt: 0,
    reusedTab: !createdForParse,
    url,
    sourceItemId: item.sourceItemId,
    startedAt: Date.now(),
    stage: createdForParse ? "created" : "reusing",
    lastTabUrl: tab.url || "",
    lastTabStatus: tab.status || "",
    lastTabTitle: tab.title || "",
    lastTabChangeAt: Date.now(),
    lastSnapshotAt: 0,
    lastPackageState: null,
    lastScriptError: "",
    lastMessage: createdForParse ? "已创建商品解析页" : "复用已有商品解析页"
  };
  shopMoveRenderedParseTabs.set(tabId, parseTabState);
  let lastSnapshot = null;
  let lastScriptError = "";
  try {
    shopMoveSourceCaptureRecords.delete(tabId);
    recordAdminDebugEvent("shop-move-render-start", {
      url: item.url,
      normalized_url: item.normalizedUrl,
      source_item_id: item.sourceItemId,
      tab_id: tabId,
      reused_tab: !createdForParse,
      tab_url: tab.url || ""
    });
    if (createdForParse) {
      parseTabState.stage = "opening";
      parseTabState.lastMessage = "正在打开商品详情页";
      await assertShopMoveParseTabOpen(tabId, parseTabState);
      await injectShopMoveSourceCaptureScripts(tabId).catch(() => {});
      await chrome.tabs.update(tabId, { url }).catch((error) => {
        lastScriptError = error?.message || "商品页打开失败";
        parseTabState.lastScriptError = lastScriptError;
        parseTabState.lastMessage = lastScriptError;
        throw error;
      });
    } else {
      parseTabState.stage = "reloading";
      parseTabState.lastMessage = "正在刷新已打开的商品页";
      await assertShopMoveParseTabOpen(tabId, parseTabState);
      await chrome.tabs.reload(tabId).catch((error) => {
        lastScriptError = error?.message || "商品页刷新失败";
        parseTabState.lastScriptError = lastScriptError;
        parseTabState.lastMessage = lastScriptError;
      });
    }
    parseTabState.stage = "waiting_load";
    parseTabState.lastMessage = "等待商品页加载完成";
    const loadPromise = waitForTabComplete(tabId, 45000).catch((error) => {
      lastScriptError = error?.message || "商品页加载等待超时";
      parseTabState.lastScriptError = lastScriptError;
      parseTabState.lastMessage = lastScriptError;
    });
    await delay(500);
    await assertShopMoveParseTabOpen(tabId, parseTabState);
    await injectShopMoveSourceCaptureScripts(tabId).catch((error) => {
      lastScriptError = error?.message || "商品页接口捕获脚本注入失败";
      parseTabState.lastScriptError = lastScriptError;
      parseTabState.lastMessage = lastScriptError;
    });
    await Promise.race([loadPromise, delay(6000)]);
    await assertShopMoveParseTabOpen(tabId, parseTabState);
    parseTabState.stage = "overlay";
    parseTabState.lastMessage = "已显示抓取遮罩，准备轮询商品接口";
    await showShopMoveParsingOverlay(tabId, item, "正在抓取商品信息，请不要关闭此页面").catch(() => {});
    await delay(1000);
    const startedAt = Date.now();
    parseTabState.stage = "capturing";
    parseTabState.lastMessage = "正在轮询商品接口与页面数据";
    while (Date.now() - startedAt < SHOP_MOVE_RENDER_PARSE_TIMEOUT_MS) {
      await assertShopMoveParseTabOpen(tabId, parseTabState);
      try {
        const results = await chrome.scripting.executeScript({
          target: { tabId, allFrames: false },
          func: extractShopMoveRenderedProductSnapshot,
          args: [{
            url: item.url,
            normalizedUrl: item.normalizedUrl,
            sourceItemId: item.sourceItemId
          }]
        });
        lastSnapshot = Array.isArray(results) ? results[0]?.result : null;
        lastScriptError = "";
        parseTabState.lastScriptError = "";
      } catch (error) {
        lastScriptError = error?.message || "商品页脚本暂时不可用";
        parseTabState.lastScriptError = lastScriptError;
        parseTabState.lastMessage = lastScriptError;
        await assertShopMoveParseTabOpen(tabId, parseTabState);
        await delay(1000);
        continue;
      }
      lastSnapshot = attachShopMoveCapturedRecordsToSnapshot(lastSnapshot, getShopMoveSourceCaptureRecords(tabId), item);
      const packageState = getShopMoveCapturedPackageState(lastSnapshot);
      const recordCount = Array.isArray(lastSnapshot?.api_records) ? lastSnapshot.api_records.length : 0;
      parseTabState.lastSnapshotAt = Date.now();
      parseTabState.lastPackageState = packageState;
      parseTabState.lastMessage = `轮询中：接口 ${recordCount}，主图 ${packageState.mainImageCount}，详情图 ${packageState.detailImageCount}，参数 ${packageState.attributeCount}，SKU ${packageState.skuCount}`;
      await showShopMoveParsingOverlay(
        tabId,
        item,
        `正在读取商品接口、图片、SKU 和属性（接口 ${recordCount} 条，主图 ${packageState.mainImageCount}，详情图 ${packageState.detailImageCount}，参数 ${packageState.attributeCount}）`
      ).catch(() => {});
      if (Date.now() - startedAt > 1500) {
        triggerShopMoveProductPageActivity(tabId).catch(() => {});
      }
      const minWaitMs = packageState.hasGoodsDetail || packageState.hasDomCompletePackage ? 3500 : SHOP_MOVE_RENDER_PARSE_MIN_WAIT_MS;
      const waitedForCapture = Date.now() - startedAt > minWaitMs;
      if (packageState.complete && waitedForCapture) {
        lastSnapshot.capture_elapsed_ms = Date.now() - startedAt;
        parseTabState.stage = "completed";
        parseTabState.lastMessage = "已捕获完整商品信息包";
        recordAdminDebugEvent("shop-move-render-snapshot", summarizeShopMoveDebugSnapshot(lastSnapshot));
        return lastSnapshot;
      }
      await delay(1000);
    }
    if (hasShopMoveCompleteCapturedPackage(lastSnapshot)) {
      lastSnapshot.capture_elapsed_ms = Date.now() - startedAt;
      parseTabState.stage = "completed";
      parseTabState.lastMessage = "已捕获完整商品信息包";
      return lastSnapshot;
    }
    const recordCount = Array.isArray(lastSnapshot?.api_records) ? lastSnapshot.api_records.length : 0;
    if (lastSnapshot) lastSnapshot.capture_elapsed_ms = Date.now() - startedAt;
    parseTabState.stage = "failed";
    parseTabState.lastMessage = lastSnapshot?.message || lastScriptError || "商品页渲染解析未识别到商品信息";
    parseTabState.lastScriptError = lastScriptError;
    recordAdminDebugEvent("shop-move-render-snapshot-failed", summarizeShopMoveDebugSnapshot(lastSnapshot || {}), "warn");
    if (recordCount) {
      return {
        ...(isObject(lastSnapshot) ? lastSnapshot : {}),
        message: lastSnapshot?.message || `商品页已捕获 ${recordCount} 条接口记录，本地暂未识别到商品标题。`
      };
    }
    throw new Error(lastSnapshot?.message || lastScriptError || `商品页渲染解析未识别到商品信息${recordCount ? `（已捕获 ${recordCount} 条接口记录）` : ""}。`);
  } finally {
    shopMoveRenderedParseTabs.delete(tabId);
    shopMoveSourceCaptureRecords.delete(tabId);
    await unregisterShopMoveSourceCaptureScripts(captureScriptIds);
    if (createdForParse) {
      await closeTabQuietly(tabId);
    } else {
      await hideShopMoveParsingOverlay(tabId).catch(() => {});
    }
    if (!createdForParse && previousActiveTab?.id && previousActiveTab.id !== tabId) {
      await chrome.tabs.update(previousActiveTab.id, { active: true }).catch(() => {});
    }
  }
}

async function findShopMoveRenderedProductTab(item) {
  const sourceItemId = String(item?.sourceItemId || "").trim();
  const normalizedUrl = normalizeShopMoveAiUrl(item?.normalizedUrl || item?.url || "");
  const tabs = await chrome.tabs.query({}).catch(() => []);
  const candidates = tabs
    .filter((tab) => Number.isInteger(tab?.id) && /^https?:\/\//i.test(tab.url || ""))
    .filter((tab) => isShopMoveRenderedProductTabMatch(tab.url, normalizedUrl, sourceItemId));
  candidates.sort((left, right) => {
    const leftActive = left.active ? 1 : 0;
    const rightActive = right.active ? 1 : 0;
    return rightActive - leftActive;
  });
  return candidates[0] || null;
}

function isShopMoveRenderedProductTabMatch(tabUrl, normalizedUrl, sourceItemId) {
  let parsed = null;
  try {
    parsed = new URL(String(tabUrl || ""));
  } catch {
    return false;
  }
  const host = parsed.hostname.toLowerCase();
  if (!(host === "haohuo.jinritemai.com" || host.endsWith(".jinritemai.com") || host.endsWith(".douyin.com"))) {
    return false;
  }
  const currentNormalized = normalizeShopMoveAiUrl(tabUrl);
  if (normalizedUrl && currentNormalized === normalizedUrl) return true;
  if (!sourceItemId) return false;
  return extractShopMoveAiSourceItemId(tabUrl) === sourceItemId;
}

async function showShopMoveParsingOverlay(tabId, item, message) {
  await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    func: (payload) => {
      const id = "mmw-shop-move-parsing-mask";
      let node = document.getElementById(id);
      if (!node) {
        node = document.createElement("div");
        node.id = id;
        node.style.cssText = [
          "position:fixed",
          "inset:0",
          "z-index:1190",
          "display:flex",
          "align-items:center",
          "justify-content:center",
          "background:rgba(9,18,35,.38)",
          "backdrop-filter:blur(2px)",
          "font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
          "pointer-events:none"
        ].join(";");
        const card = document.createElement("div");
        card.style.cssText = [
          "min-width:280px",
          "max-width:520px",
          "padding:22px 26px",
          "border-radius:12px",
          "background:rgba(15,23,42,.94)",
          "box-shadow:0 22px 70px rgba(37,99,235,.45),0 0 0 1px rgba(96,165,250,.55)",
          "color:#fff",
          "text-align:center"
        ].join(";");
        card.innerHTML = '<div style="font-size:18px;font-weight:700;margin-bottom:8px;">抓取商品信息中</div><div data-mmw-shop-move-mask-message style="font-size:14px;line-height:1.7;color:#dbeafe;"></div><div style="height:4px;background:rgba(255,255,255,.16);border-radius:999px;overflow:hidden;margin-top:16px;"><div style="height:100%;width:45%;background:linear-gradient(90deg,#60a5fa,#22d3ee);border-radius:999px;animation:mmwShopMoveMaskBar 1.2s ease-in-out infinite alternate;"></div></div>';
        const style = document.createElement("style");
        style.textContent = "@keyframes mmwShopMoveMaskBar{from{transform:translateX(-80%)}to{transform:translateX(180%)}}";
        node.append(style, card);
        document.documentElement.appendChild(node);
      }
      const msg = node.querySelector("[data-mmw-shop-move-mask-message]");
      if (msg) {
        const title = String(payload.title || payload.sourceItemId || "").trim();
        msg.textContent = `${payload.message || "正在抓取商品信息"}${title ? `：${title}` : ""}`;
      }
    },
    args: [{
      message,
      title: item?.sourceItemId || item?.url || ""
    }]
  });
}

async function hideShopMoveParsingOverlay(tabId) {
  await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    func: () => {
      document.getElementById("mmw-shop-move-parsing-mask")?.remove();
    }
  });
}

function hasShopMoveUsefulCapturedRecords(snapshot) {
  return getShopMoveCapturedPackageState(snapshot).hasUsefulCapture;
}

function hasShopMoveCompleteCapturedPackage(snapshot) {
  return getShopMoveCapturedPackageState(snapshot).complete;
}

function getShopMoveCapturedPackageState(snapshot) {
  const product = isObject(snapshot?.product) ? snapshot.product : {};
  const records = Array.isArray(snapshot?.api_records) ? snapshot.api_records : [];
  const flags = {
    hasGoodsDetail: false,
    hasPromotion: false,
    hasPromotionImages: false,
    hasDetailInfo: false,
    hasDetailImages: false,
    hasSku: false,
    hasAttributes: false
  };
  for (const record of records) {
    const sample = `${record?.url || ""}\n${String(record?.body || "").slice(0, 20000)}`;
    if (/goodsDetail|main_img|detail_img|sku_price|sku_key/i.test(sample)) flags.hasGoodsDetail = true;
    if (/promotion_h5|promotionH5/i.test(sample)) flags.hasPromotion = true;
    if (/head_figure_data|headFigureData|media_list|main_img|mainImg/i.test(sample)) flags.hasPromotionImages = true;
    if (/detail_info|detailInfo/i.test(sample)) flags.hasDetailInfo = true;
    if (/detail_img|detail_imgs|detailImgs|detail_images|detailImages/i.test(sample)) flags.hasDetailImages = true;
    if (/sku_price|skuPrice|sku_key|skuKey|sku_key_list|skuKeyList|sku_img|skuImg|sku_img_all|skuImgAll|sku_list|skuList|sku_infos|skuInfos|sku_info_list|skuInfoList|spec_list|specList|sale_props|saleProps|product_format|productFormat/i.test(sample)) flags.hasSku = true;
    if (/base_attr|baseAttr|product_format|productFormat|attributes?|attrs?|parameter|params?|产品参数|商品参数|详细参数/i.test(sample)) flags.hasAttributes = true;
  }
  const hasTitle = hasUsableShopMoveProductPackage(product);
  const mainImageCount = uniqueStrings([
    ...listFromAny(product.main_images),
    product.main_image
  ]).filter(isLikelyShopMoveImageUrl).length;
  const detailImageCount = uniqueStrings(listFromAny(product.detail_images)).filter(isLikelyShopMoveImageUrl).length;
  const attributeCount = Object.entries(isObject(product.attributes) ? product.attributes : {})
    .filter(([key, value]) => String(key || "").trim() && String(value || "").trim())
    .length;
  const skuCount = Array.isArray(product.sku_list) ? product.sku_list.length : 0;
  const hasUsefulCapture = flags.hasGoodsDetail || (flags.hasPromotion && flags.hasPromotionImages) || (flags.hasDetailInfo && flags.hasDetailImages) || flags.hasSku;
  const hasDomCompletePackage = hasTitle && mainImageCount >= 1 && detailImageCount >= 2 && attributeCount > 0;
  const hasRequiredApiPackage = flags.hasGoodsDetail
    || ((flags.hasPromotion && flags.hasPromotionImages) && (flags.hasDetailInfo && flags.hasDetailImages))
    || (hasUsefulCapture && mainImageCount >= 2 && detailImageCount >= 1 && attributeCount > 0)
    || hasDomCompletePackage;
  const missingFields = [];
  if (!hasTitle) missingFields.push("title");
  if (mainImageCount < 1) missingFields.push("main_images");
  if (detailImageCount < 1) missingFields.push("detail_images");
  if (attributeCount < 1) missingFields.push("attributes");
  if (!hasRequiredApiPackage) missingFields.push("api_package");
  const complete = hasTitle
    && mainImageCount > 0
    && detailImageCount > 0
    && attributeCount > 0
    && hasRequiredApiPackage;
  return {
    ...flags,
    hasUsefulCapture,
    hasDomCompletePackage,
    hasRequiredApiPackage,
    complete,
    missingFields,
    mainImageCount,
    detailImageCount,
    attributeCount,
    skuCount
  };
}

function isShopMoveStrictRenderedCaptureUrl(rawUrl) {
  let host = "";
  try {
    host = new URL(String(rawUrl || "")).hostname.toLowerCase();
  } catch {
    return false;
  }
  return host === "haohuo.jinritemai.com" || host.endsWith(".jinritemai.com") || host.endsWith(".douyin.com");
}

async function extractShopMoveRenderedProductSnapshot(input) {
  const args = input || {};
  const sourceUrl = String(args.url || location.href || "");
  const normalizedUrl = String(args.normalizedUrl || sourceUrl);
  const sourceItemId = String(args.sourceItemId || "");
  const lines = String(document.body?.innerText || "")
    .split(/\n+/)
    .map((line) => line.trim())
    .filter(Boolean);
  const badLine = /^(打开抖音APP|购物实惠又有趣|立即打开|保障|物流|店铺|客服|购物车|加入购物车|去抢购|进店逛逛|产品参数|商品参数|参数信息|价格说明|销量说明|协议|暂无商品评价|限时价|起|￥|\?|0|1\/\d+)$/;
  const title = lines
    .filter((line) => /[\u4e00-\u9fa5A-Za-z]/.test(line) && line.length >= 8 && line.length <= 120 && !badLine.test(line) && !isBadTitleLine(line))
    .sort((a, b) => scoreTitleLine(b) - scoreTitleLine(a))[0] || "";
  const priceMatch = String(document.body?.innerText || "").match(/[￥¥]\s*(\d+(?:\.\d{1,2})?)/);
  const images = Array.from(document.images || [])
    .map((img) => String(img.currentSrc || img.src || "").trim())
    .filter((src) => /^https?:\/\//i.test(src))
    .filter((src) => /(ecombdimg|douyinpic|pstatp|byteimg|bytedance)/i.test(src))
    .filter((src) => !/(xianhuo|product_detail_head|shop_logo|avatar|icon|cart|shopping|placeholder|default|comment|profile|coupon)/i.test(src));
  const attributes = {};
  const paramsIndex = lines.findIndex((line) => /^(产品参数|商品参数|参数信息|规格参数|更多详细参数|详细参数)$/.test(line.replace(/\s+/g, "")));
  if (paramsIndex >= 0) {
    const endIndex = lines.findIndex((line, index) => index > paramsIndex && /^(价格说明|销量说明|协议|推荐|评价|店铺|客服)$/.test(line.replace(/\s+/g, "")));
    const end = endIndex > paramsIndex ? endIndex : Math.min(lines.length, paramsIndex + 20);
    const knownKeys = ["品牌", "形状", "材质", "商品类型", "生产企业名称", "生产企业", "货号", "产品名称", "型号", "规格", "净含量", "产地", "保质期"];
    for (let index = paramsIndex + 1; index < end; index += 1) {
      const line = lines[index];
      if (!line || badLine.test(line)) continue;
      const compact = line.replace(/\s+/g, "");
      const pair = line.match(/^([^:：]{1,24})[:：]\s*(.{1,240})$/);
      if (pair && !badLine.test(pair[1]) && !badLine.test(pair[2])) {
        attributes[pair[1].trim().slice(0, 80)] = pair[2].trim().slice(0, 240);
        continue;
      }
      const key = knownKeys.find((name) => compact.startsWith(name) && compact.length > name.length);
      if (key) {
        attributes[key.slice(0, 80)] = compact.slice(key.length).slice(0, 240);
        continue;
      }
      const next = lines[index + 1];
      if (next && knownKeys.includes(compact) && !badLine.test(next)) {
        attributes[compact.slice(0, 80)] = next.slice(0, 240);
        index += 1;
        continue;
      }
      if (next && !badLine.test(next) && line.length <= 24 && next.length <= 240) {
        attributes[line.slice(0, 80)] = next.slice(0, 240);
        index += 1;
      }
    }
  }
  const shopEntryIndex = lines.indexOf("进店逛逛");
  const shopName = shopEntryIndex >= 2 ? lines[shopEntryIndex - 2] : "";
  const uniqueImages = Array.from(new Set(images)).slice(0, 40);
  const mainImages = uniqueImages.slice(0, 5);
  const detailImages = uniqueImages.slice(mainImages.length);
  const directCaptureRecords = await fetchShopMoveDirectCaptureBundle(sourceUrl).catch(() => []);
  const hookRecords = [
    ...readShopMoveHookRecords(),
    ...readShopMoveStorageRecords(),
    ...(Array.isArray(directCaptureRecords) ? directCaptureRecords : [])
  ];
  return {
    url: sourceUrl,
    normalized_url: normalizedUrl,
    source_item_id: sourceItemId,
    resolved_url: location.href,
    api_records: hookRecords,
    product: {
      title,
      source_title: title,
      price: priceMatch ? priceMatch[1] : "",
      stock: "",
      description: "",
      shop_name: shopName,
      attributes,
      images: uniqueImages,
      main_image: uniqueImages[0] || "",
      main_images: mainImages,
      detail_images: detailImages,
      spec_images: [],
      specs: {},
      sku_list: [],
      source_url: sourceUrl,
      normalized_url: normalizedUrl,
      source_item_id: sourceItemId,
      resolved_url: location.href,
      source: "rendered_tab_dom"
    }
  };

  function isBadTitleLine(value) {
    const text = String(value || "").replace(/\s+/g, "");
    if (/^(商品参数|产品参数|参数信息|规格参数|类目属性)/.test(text)) return true;
    if (/收到|物流|包装|客服|好评|推荐|试用|安装|非常nice|不错|喜欢|复购|值得|挺好|质量/.test(text) && /[，。,.!！]/.test(text)) return true;
    const labels = text.match(/(产地|品牌|材质|货号|型号|规格|颜色|尺码|适用|保修期|生产企业|生产企业名称|组合件数|功效|成分|净含量|重量|包装)[:：]/g) || [];
    return labels.length >= 2 || /(产地|品牌|材质)[:：].+(产地|品牌|材质)[:：]/.test(text);
  }

  function readShopMoveHookRecords() {
    const nodes = Array.from(document.querySelectorAll(".mmw_shop_move_hook,.hook_data.hook,.hook_pc.hook,.hook_detail.hook,.hook_skulist.hook"));
    return nodes.slice(-40).map((node) => {
      let body = String(node.textContent || "");
      try {
        body = decodeURIComponent(body);
      } catch {
        // Keep the raw hook text when it was not URI encoded.
      }
      return {
        kind: "dom_hook",
        method: "GET",
        url: String(node.getAttribute("data-url") || ""),
        status: 200,
        content_type: "application/json",
        body: body.slice(0, 900 * 1024),
        page_url: location.href,
        at: Date.now()
      };
    }).filter((record) => record.url && record.body);
  }

  function readShopMoveStorageRecords() {
    const records = [];
    const stores = [
      ["localStorage", window.localStorage],
      ["sessionStorage", window.sessionStorage]
    ];
    for (const [storeName, store] of stores) {
      try {
        if (!store) continue;
        for (let index = 0; index < Math.min(store.length, 240); index += 1) {
          const key = String(store.key(index) || "");
          const body = String(store.getItem(key) || "");
          if (!body || body.length < 40 || body.length > 900 * 1024) continue;
          const sample = `${key}\n${body.slice(0, 20000)}`;
          if (!/(goodsDetail|goods_detail|promotion_h5|detail_info|product_format|base_attr|sku_price|skuPrice|sku_key|skuKey|sku_key_list|skuKeyList|sku_img|skuImg|sku_img_all|skuImgAll|sku_list|skuList|sku_infos|skuInfos|sku_info_list|skuInfoList|spec_list|specList|sale_props|saleProps|main_img|detail_img)/i.test(sample)) continue;
          records.push({
            kind: storeName,
            method: "GET",
            url: `${location.href}#${storeName}:${key}`,
            status: 200,
            content_type: "application/json",
            body: body.slice(0, 900 * 1024),
            page_url: location.href,
            at: Date.now()
          });
          if (records.length >= 40) return records;
        }
      } catch {
        // Storage access can be blocked by the page; captured API records are still usable.
      }
    }
    return records;
  }

  async function fetchShopMoveDirectCaptureBundle(pageUrl) {
    let parsed = null;
    try {
      parsed = new URL(pageUrl || location.href);
    } catch {
      return [];
    }
    const promotionId = parsed.searchParams.get("id") || parsed.searchParams.get("promotion_id") || sourceItemId;
    if (!promotionId) return [];
    const originType = parsed.searchParams.get("h5_origin_type") || parsed.searchParams.get("origin_type") || "604";
    const cacheKey = `${promotionId}|${originType}`;
    const cache = window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_CACHE__;
    if (cache && cache.key === cacheKey && Array.isArray(cache.records) && cache.records.length) {
      return cache.records;
    }
    const pending = window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_PROMISE__;
    if (pending && pending.key === cacheKey && pending.promise) {
      await pending.promise.catch(() => {});
      const resolved = window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_CACHE__;
      return resolved && resolved.key === cacheKey && Array.isArray(resolved.records) ? resolved.records : [];
    }
    const promise = (async () => {
      const body = buildShopMoveDirectCaptureBody(parsed, promotionId, originType);
      const specs = [
        {
          kind: "direct_promotion_pack_h5",
          path: "/aweme/v2/shop/promotion/pack/h5/",
          query: `is_h5=1&is_native_h5=1${originType ? `&origin_type=${encodeURIComponent(originType)}` : ""}`,
          discover: /promotion\/pack\/h5/i
        },
        {
          kind: "direct_promotion_pack_detail",
          path: "/aweme/v2/shop/promotion/pack/detail/",
          query: `is_h5=1${originType ? `&origin_type=${encodeURIComponent(originType)}` : ""}`,
          discover: /promotion\/pack\/detail/i
        },
        {
          kind: "direct_promotion_pack",
          path: "/aweme/v2/shop/promotion/pack/",
          query: `is_h5=1&is_native_h5=1${originType ? `&origin_type=${encodeURIComponent(originType)}` : ""}`,
          discover: /promotion\/pack\/(?!detail|h5)/i
        }
      ];
      const records = [];
      for (const spec of specs) {
        const record = await fetchShopMoveDirectCaptureRecord(spec, body).catch(() => null);
        if (record) records.push(record);
      }
      window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_CACHE__ = {
        key: cacheKey,
        records
      };
      return records;
    })();
    window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_PROMISE__ = { key: cacheKey, promise };
    try {
      return await promise;
    } finally {
      if (window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_PROMISE__?.key === cacheKey) {
        delete window.__MMW_SHOP_MOVE_DIRECT_CAPTURE_PROMISE__;
      }
    }
  }

  function buildShopMoveDirectCaptureBody(parsed, promotionId, originType) {
    const params = new URLSearchParams();
    params.set("promotion_id", promotionId);
    if (originType) params.set("origin_type", originType);
    params.set("page_url", location.href);
    params.set("device_platform", "web");
    params.set("appName", "aweme");
    for (const [key, value] of parsed.searchParams.entries()) {
      if (!key || params.has(key)) continue;
      if (String(value || "").length > 4000) continue;
      params.set(key, value);
    }
    try {
      const trackParams = JSON.parse(parsed.searchParams.get("track_params") || "{}");
      if (trackParams && typeof trackParams === "object") {
        Object.entries(trackParams).forEach(([key, value]) => {
          if (!key || params.has(key) || value == null) return;
          const text = typeof value === "string" ? value : JSON.stringify(value);
          if (text.length <= 4000) params.set(key, text);
        });
      }
    } catch {
      // Optional tracking params are not required for every copied link.
    }
    params.set("ui_params", JSON.stringify({
      full_mode: true,
      from_live: false,
      from_video: null,
      source_page: parsed.searchParams.get("source_page") || "",
      enter_method: parsed.searchParams.get("enter_method") || "",
      carrier_type: parsed.searchParams.get("carrier_type") || "",
      request_additions: parsed.searchParams.get("request_additions") || ""
    }));
    return params.toString();
  }

  async function fetchShopMoveDirectCaptureRecord(spec, body) {
    const endpoint = findShopMoveDirectCaptureUrl(spec?.discover)
      || `https://haohuo.jinritemai.com${spec?.path || ""}?${spec?.query || ""}`;
    if (!endpoint || !body) return null;
    const response = await fetch(endpoint, {
      method: "POST",
      credentials: "include",
      cache: "no-store",
      headers: {
        "content-type": "application/x-www-form-urlencoded"
      },
      body
    });
    const text = await response.text();
    if (!text || text.trim().length < 3) return null;
    return {
      kind: String(spec?.kind || "direct_promotion_pack"),
      method: "POST",
      url: endpoint,
      status: response.status,
      content_type: response.headers.get("content-type") || "application/json",
      body: text.slice(0, 900 * 1024),
      page_url: location.href,
      at: Date.now()
    };
  }

  function findShopMoveDirectCaptureUrl(matcher) {
    if (!(matcher instanceof RegExp)) return "";
    const candidates = [];
    try {
      const nodes = Array.from(document.querySelectorAll(".mmw_shop_move_hook[data-url], .hook[data-url]"));
      for (const node of nodes) {
        const url = String(node.getAttribute("data-url") || "").trim();
        if (url) candidates.push(url);
      }
    } catch {
      // Hidden hook nodes are optional.
    }
    try {
      const resources = performance.getEntriesByType("resource") || [];
      for (const entry of resources) {
        const url = String(entry?.name || "").trim();
        if (url) candidates.push(url);
      }
    } catch {
      // Performance entries may be unavailable on some pages.
    }
    for (let index = candidates.length - 1; index >= 0; index -= 1) {
      const url = candidates[index];
      if (/^https:\/\/haohuo\.jinritemai\.com\//i.test(url) && matcher.test(url)) {
        return url;
      }
    }
    return "";
  }

  function scoreTitleLine(value) {
    const text = String(value || "");
    let score = Math.min(text.length, 60);
    if (text.length > 60) score -= (text.length - 60) * 2;
    if (/[￥¥]|\d+(?:\.\d+)?元|销量|库存|客服|店铺/.test(text)) score -= 30;
    if (/耳朵|耳夹|发夹|按摩|护肤|洗护|用品|工具|创意|儿童|时尚|简约/.test(text)) score += 12;
    return score;
  }
}

async function fetchShopMoveProductPage(url) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 15000);
  try {
    const response = await fetch(url, {
      method: "GET",
      redirect: "follow",
      credentials: "include",
      cache: "no-store",
      headers: {
        Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
      },
      signal: controller.signal
    });
    const html = await response.text();
    if (!response.ok) throw new Error(`商品页面请求失败：HTTP ${response.status}`);
    if (!html || html.length < 200) throw new Error("商品页面内容为空，无法解析商品信息包。");
    return { html, url: response.url || url };
  } catch (error) {
    if (error.name === "AbortError") throw new Error("商品信息包解析超时，请确认商品链接能正常打开。");
    throw new Error(error.message || "商品信息包解析失败");
  } finally {
    clearTimeout(timer);
  }
}

function parseShopMoveProductPage(html, finalUrl) {
  const source = String(html || "");
  const meta = collectHtmlMeta(source);
  const pageTitle = cleanShopMoveTitle(firstNonEmpty(
    meta["og:title"],
    meta["twitter:title"],
    readHtmlTitle(source)
  ));
  const jsonTitle = cleanShopMoveTitle(findJsonStringValue(source, ["product_name", "productName", "goods_name", "goodsName", "title", "name"]));
  const title = isGenericShopMoveTitle(pageTitle) ? jsonTitle : (pageTitle || jsonTitle);
  const description = cleanHtmlText(firstNonEmpty(
    meta["og:description"],
    meta.description,
    findJsonStringValue(source, ["description", "desc", "detail", "sub_title", "subtitle"])
  ));
  const imageList = uniqueStrings([
    meta["og:image"],
    meta["twitter:image"],
    ...findImageUrls(source)
  ]).slice(0, 20);
  return {
    title,
    description,
    price: normalizeShopMovePrice(findJsonStringValue(source, ["sale_price", "sell_price", "price", "min_price", "real_price"])),
    stock: "",
    images: imageList,
    resolved_url: finalUrl,
    source: "html"
  };
}

function collectHtmlMeta(html) {
  const result = {};
  const tags = String(html || "").match(/<meta\b[^>]*>/gi) || [];
  for (const tag of tags) {
    const key = String(getHtmlAttribute(tag, "property") || getHtmlAttribute(tag, "name") || "").trim().toLowerCase();
    const value = cleanHtmlText(getHtmlAttribute(tag, "content") || "");
    if (key && value && !result[key]) result[key] = value;
  }
  return result;
}

function getHtmlAttribute(tag, name) {
  const pattern = new RegExp(`${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i");
  const match = String(tag || "").match(pattern);
  return decodeHtmlEntities(match?.[2] || match?.[3] || match?.[4] || "");
}

function readHtmlTitle(html) {
  const match = String(html || "").match(/<title[^>]*>([\s\S]*?)<\/title>/i);
  return cleanHtmlText(match?.[1] || "");
}

function findJsonStringValue(source, keys) {
  const keyGroup = keys.map(escapeRegExp).join("|");
  const pattern = new RegExp(`["'](?:${keyGroup})["']\\s*:\\s*(?:"((?:\\\\.|[^"\\\\]){1,260})"|'((?:\\\\.|[^'\\\\]){1,260})'|([0-9]+(?:\\.[0-9]+)?))`, "gi");
  const candidates = [];
  let match;
  while ((match = pattern.exec(source))) {
    const value = cleanHtmlText(decodeJsonString(match[1] || match[2] || match[3] || ""));
    if (value) candidates.push(value);
    if (candidates.length > 30) break;
  }
  return candidates.find(isLikelyShopMoveTitle) || candidates[0] || "";
}

function findImageUrls(source) {
  const normalized = String(source || "")
    .replace(/\\u002F/g, "/")
    .replace(/\\\//g, "/");
  const matches = normalized.match(/https?:\/\/[^"'<>\\\s]+?\.(?:jpg|jpeg|png|webp)(?:\?[^"'<>\\\s]*)?/gi) || [];
  return matches.map((url) => cleanHtmlText(url));
}

function applyShopMoveTitleRule(title, config) {
  const cleanTitle = cleanShopMoveTitle(title);
  if (!cleanTitle) return "";
  const prefix = String(config.title_prefix || "").trim();
  const suffix = String(config.title_suffix || "").trim();
  return `${prefix}${prefix ? " " : ""}${cleanTitle}${suffix ? ` ${suffix}` : ""}`.trim().slice(0, 60);
}

function cleanShopMoveTitle(value) {
  return cleanHtmlText(value)
    .replace(/\s*[-_｜|]\s*(抖音商城|抖音|抖店|今日头条|巨量百应|小店).*$/i, "")
    .replace(/^(商品详情|商品|详情)\s*[-_｜|]\s*/i, "")
    .slice(0, 120)
    .trim();
}

function cleanHtmlText(value) {
  return decodeHtmlEntities(String(value || ""))
    .replace(/<[^>]+>/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function decodeHtmlEntities(value) {
  return String(value || "")
    .replace(/&quot;/g, "\"")
    .replace(/&#34;/g, "\"")
    .replace(/&apos;/g, "'")
    .replace(/&#39;/g, "'")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code) || 0))
    .replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(parseInt(code, 16) || 0));
}

function decodeJsonString(value) {
  const text = String(value || "");
  try {
    return JSON.parse(`"${text.replace(/"/g, '\\"')}"`);
  } catch {
    return text.replace(/\\u([0-9a-f]{4})/gi, (_, code) => String.fromCharCode(parseInt(code, 16) || 0));
  }
}

function normalizeShopMovePrice(value) {
  const text = cleanHtmlText(value);
  if (!text) return "";
  const match = text.match(/(?:¥|￥)?\s*(\d+(?:\.\d{1,2})?)/);
  if (!match) return "";
  return match[1];
}

function hasUsableShopMoveProductPackage(product) {
  const title = String(product?.title || "").trim();
  return title.length >= 4 && !/^待解析商品/.test(title) && !/^解析中/.test(title) && isLikelyShopMoveTitle(title);
}

function isLikelyShopMoveTitle(value) {
  const text = cleanShopMoveTitle(value);
  if (text.length < 4 || text.length > 120) return false;
  if (/^(true|false|null|undefined|\d+)$/.test(text)) return false;
  if (isGenericShopMoveTitle(text)) return false;
  if (isShopMoveAttributeLikeText(text)) return false;
  if (isShopMoveCategoryLikeText(text)) return false;
  if (/登录|验证码|隐私政策|用户协议|服务市场/.test(text) && text.length < 18) return false;
  return /[\u4e00-\u9fa5a-zA-Z]/.test(text);
}

function scoreShopMoveTitleCandidate(value) {
  const text = cleanShopMoveTitle(value);
  if (!isLikelyShopMoveTitle(text)) return Number.NEGATIVE_INFINITY;
  let score = Math.min(text.length, 80);
  if (/[\u4e00-\u9fa5]/.test(text)) score += 18;
  if (/[a-zA-Z]/.test(text)) score += 6;
  if (/\d/.test(text)) score += 4;
  if (/[，,、]/.test(text)) score += 8;
  if (text.length >= 10) score += 12;
  if (text.length >= 16) score += 10;
  if (text.length <= 8) score -= 12;
  return score;
}

function isShopMoveAttributeLikeText(value) {
  const text = String(value || "").replace(/\s+/g, "");
  if (/^(商品参数|产品参数|参数信息|规格参数|类目属性)/.test(text)) return true;
  if (/收到|物流|包装|客服|好评|推荐|试用|安装|非常nice|不错|喜欢|复购|值得|挺好|质量/.test(text) && /[，。,.!！]/.test(text)) return true;
  const labels = text.match(/(产地|品牌|材质|货号|型号|规格|颜色|尺码|适用|保修期|生产企业|生产企业名称|组合件数|功效|成分|净含量|重量|包装)[:：]/g) || [];
  if (labels.length >= 2) return true;
  return /(产地|品牌|材质)[:：].+(产地|品牌|材质)[:：]/.test(text);
}

function isShopMoveCategoryLikeText(value) {
  const text = String(value || "").replace(/\s+/g, "");
  if (!text || text.length > 16) return false;
  return /(种类|类目|分类|品类|类别|属性)$/.test(text);
}

function isGenericShopMoveTitle(value) {
  return /^(商品详情|商品|详情|抖音商城|抖音|抖店|今日头条|小店|prefetch)$/i.test(String(value || "").trim());
}

function uniqueStrings(values) {
  const seen = new Set();
  const result = [];
  for (const value of values) {
    const text = String(value || "").trim();
    if (!text || seen.has(text)) continue;
    seen.add(text);
    result.push(text);
  }
  return result;
}

function listFromAny(value) {
  if (!value) return [];
  if (Array.isArray(value)) {
    return value.flatMap((item) => listFromAny(item));
  }
  if (isObject(value)) {
    return Object.values(value).flatMap((item) => listFromAny(item));
  }
  const text = String(value || "").trim();
  return text ? [text] : [];
}

function firstNonEmpty(...values) {
  return values.find((value) => String(value || "").trim()) || "";
}

function escapeRegExp(value) {
  return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function parseShopMoveAiLinks(rawText) {
  const matches = String(rawText || "").match(/https?:\/\/[^\s<>'"]+/gi) || [];
  const results = [];
  const seen = new Set();
  for (const raw of matches) {
    const url = cleanShopMoveAiUrl(raw);
    const normalizedUrl = normalizeShopMoveAiUrl(url);
    if (!normalizedUrl || seen.has(normalizedUrl)) continue;
    seen.add(normalizedUrl);
    results.push({ url, normalizedUrl, sourceItemId: extractShopMoveAiSourceItemId(normalizedUrl) });
  }
  return results;
}

function buildShopMoveAiProduct(link, config, index) {
  return {
    title: "",
    price: "",
    stock: String(config.stock_mode || "") === "fixed" ? String(config.stock_value || "") : "",
    outer_id: buildShopMoveAiOuterId(link, config, index),
    source_url: link.url,
    source_item_id: link.sourceItemId,
    images: [],
    main_images: [],
    detail_images: [],
    spec_images: [],
    attributes: {},
    specs: {},
    sku_list: [],
    description: ""
  };
}

function buildShopMoveAiOuterId(link, config, index) {
  const sourceId = link.sourceItemId || `${Date.now()}${index + 1}`;
  const prefix = String(config.sku_code_prefix || "").trim();
  const suffix = String(config.sku_code_suffix || "").trim();
  if (String(config.sku_code_mode || "") === "auto") return `MMW${sourceId}`;
  return `${prefix}${sourceId}${suffix}`.slice(0, 80);
}

function cleanShopMoveAiUrl(rawUrl) {
  return decodeHtmlEntities(String(rawUrl || "").trim()).replace(/[.,;:!?，。；：！？、)）\]】]+$/g, "");
}

function normalizeShopMoveAiUrl(rawUrl) {
  const text = cleanShopMoveAiUrl(rawUrl);
  if (!text) return "";
  try {
    const parsed = new URL(text);
    if (!/^https?:$/i.test(parsed.protocol)) return "";
    const params = new URLSearchParams(parsed.search);
    for (const key of Array.from(params.keys())) {
      const lower = key.toLowerCase();
      if (lower.startsWith("utm_") || lower.startsWith("share_") || ["timestamp", "from", "source", "enter_from"].includes(lower)) {
        params.delete(key);
      }
    }
    parsed.search = Array.from(params.keys()).sort().map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params.get(key) || "")}`).join("&");
    parsed.hash = "";
    return parsed.toString();
  } catch {
    return "";
  }
}

function extractShopMoveAiSourceItemId(url) {
  try {
    const parsed = new URL(url);
    const keys = ["product_id", "promotion_id", "commodity_id", "item_id", "goods_id", "gid", "id"];
    for (const key of keys) {
      const value = parsed.searchParams.get(key);
      if (value) return String(value).trim().slice(0, 80);
    }
    const numbers = parsed.pathname.match(/(?<!\d)(\d{8,24})(?!\d)/g) || [];
    return numbers.length ? numbers[numbers.length - 1].slice(0, 80) : "";
  } catch {
    return "";
  }
}

function waitForTabComplete(tabId, timeoutMs) {
  return new Promise((resolve, reject) => {
    let done = false;
    const timer = setTimeout(() => finish(new Error("抖店发品页加载超时。")), timeoutMs || 60000);
    const finish = (error) => {
      if (done) return;
      done = true;
      clearTimeout(timer);
      chrome.tabs.onUpdated.removeListener(listener);
      if (error) reject(error);
      else resolve();
    };
    const listener = (updatedTabId, info) => {
      if (updatedTabId === tabId && info.status === "complete") finish();
    };
    chrome.tabs.onUpdated.addListener(listener);
    chrome.tabs.get(tabId).then((tab) => {
      if (tab?.status === "complete") finish();
    }).catch((error) => finish(error));
  });
}

function sendTabMessage(tabId, message) {
  return new Promise((resolve, reject) => {
    chrome.tabs.sendMessage(tabId, message, (response) => {
      const error = chrome.runtime.lastError;
      if (error) {
        reject(new Error(error.message || "受控发品页通信失败"));
        return;
      }
      resolve(response);
    });
  });
}

async function closeTabQuietly(tabId) {
  try {
    await chrome.tabs.remove(tabId);
  } catch {
    // Ignore close failures: the user may have closed the tab manually.
  }
}

async function assertShopMoveParseTabOpen(tabId, parseTabState) {
  if (parseTabState?.closed) {
    await delay(300);
    const reopened = await chrome.tabs.get(tabId).catch(() => null);
    if (reopened) {
      parseTabState.closed = false;
      parseTabState.closedAt = 0;
      return reopened;
    }
    recordAdminDebugEvent("shop-move-render-tab-closed", {
      ...summarizeShopMoveParseTabRuntime(parseTabState, tabId),
      reason: "removed_flag"
    }, "warn");
    throw new Error("商品解析页已关闭，解析已中断，请重新点击解析并等待抓取完成。");
  }
  let tab = await chrome.tabs.get(tabId).catch(() => null);
  if (!tab) {
    await delay(300);
    tab = await chrome.tabs.get(tabId).catch(() => null);
  }
  if (!tab) {
    if (parseTabState) parseTabState.closed = true;
    recordAdminDebugEvent("shop-move-render-tab-missing", {
      ...summarizeShopMoveParseTabRuntime(parseTabState, tabId),
      reason: "tabs_get_empty"
    }, "warn");
    throw new Error("商品解析页已关闭，解析已中断，请重新点击解析并等待抓取完成。");
  }
  return tab;
}

async function registerShopMoveSourceCaptureScripts() {
  if (!chrome.scripting?.registerContentScripts) return [];
  const token = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  const ids = [`mmw_shop_move_capture_main_${token}`, `mmw_shop_move_capture_bridge_${token}`];
  const scripts = [
    {
      id: ids[0],
      matches: SHOP_MOVE_SOURCE_CAPTURE_MATCHES,
      js: [SHOP_MOVE_SOURCE_CAPTURE_MAIN_FILE],
      runAt: "document_start",
      allFrames: false,
      world: "MAIN",
      persistAcrossSessions: false
    },
    {
      id: ids[1],
      matches: SHOP_MOVE_SOURCE_CAPTURE_MATCHES,
      js: [SHOP_MOVE_SOURCE_CAPTURE_BRIDGE_FILE],
      runAt: "document_start",
      allFrames: false,
      world: "ISOLATED",
      persistAcrossSessions: false
    }
  ];
  await chrome.scripting.registerContentScripts(scripts);
  return ids;
}

async function unregisterShopMoveSourceCaptureScripts(ids) {
  if (!chrome.scripting?.unregisterContentScripts || !Array.isArray(ids) || !ids.length) return;
  await chrome.scripting.unregisterContentScripts({ ids }).catch(() => {});
}

async function injectShopMoveSourceCaptureScripts(tabId) {
  await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    files: [SHOP_MOVE_SOURCE_CAPTURE_MAIN_FILE],
    world: "MAIN"
  });
  await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    files: [SHOP_MOVE_SOURCE_CAPTURE_BRIDGE_FILE]
  });
}

async function triggerShopMoveProductPageActivity(tabId) {
  await chrome.scripting.executeScript({
    target: { tabId, allFrames: false },
    func: () => {
      try {
        const maxY = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
        window.__MMW_SHOP_MOVE_SCROLL_STEP__ = Number(window.__MMW_SHOP_MOVE_SCROLL_STEP__ || 0) + 1;
        const positions = [0.18, 0.42, 0.68, 0.9, 1, 0.55];
        const next = Math.round(maxY * positions[window.__MMW_SHOP_MOVE_SCROLL_STEP__ % positions.length]);
        window.scrollTo({ top: next, behavior: "auto" });
        window.dispatchEvent(new Event("scroll"));
        window.dispatchEvent(new Event("resize"));
        const candidates = Array.from(document.querySelectorAll("button,a,[role='button'],div,span"))
          .filter((node) => {
            const text = String(node.textContent || "").replace(/\s+/g, "");
            if (!/(更多详细参数|产品参数|商品参数|详细参数|规格参数|展开|查看更多)/.test(text)) return false;
            const rect = node.getBoundingClientRect();
            const style = window.getComputedStyle(node);
            return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
          })
          .slice(0, 4);
        for (const node of candidates) {
          try {
            node.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
          } catch {
            // Some mobile detail nodes reject synthetic clicks.
          }
        }
      } catch {
        // Only used to trigger lazy product API requests.
      }
    }
  });
}

async function requestShopMoveCaptureCurrentShop(sender) {
  await requireEcommerceToolFeatureAccess();
  const tab = await resolveCurrentPageTab(sender);
  if (!tab?.id) throw new Error("没有找到当前活动页面，请先打开抖店店铺后台页面。");
  if (isRestrictedPageUrl(tab.url || "")) {
    throw new Error("当前页面不支持读取店铺信息，请切换到抖店店铺后台页面后再获取。");
  }
  const results = await chrome.scripting.executeScript({
    target: { tabId: tab.id, allFrames: false },
    func: extractCurrentShopInfoFromPage
  });
  const result = Array.isArray(results) ? results[0]?.result : null;
  const shop = normalizeCapturedShopInfo(result?.shop || result || {}, tab);
  if (!shop.shop_name) {
    throw new Error("当前页面没有识别到店铺名称，请先打开抖店/抖音店铺后台页面后再获取。");
  }
  return { shop };
}

async function requestShopMoveCaptureShopTemplates(sender) {
  await requireEcommerceToolFeatureAccess();
  const tab = await resolveCurrentPageTab(sender);
  if (!tab?.id) throw new Error("没有找到当前活动页面，请先打开抖店店铺后台页面。");
  if (isRestrictedPageUrl(tab.url || "")) {
    throw new Error("当前页面不支持读取店铺模板，请切换到抖店店铺后台页面后再刷新。");
  }
  const results = await chrome.scripting.executeScript({
    target: { tabId: tab.id, allFrames: false },
    func: extractShopMoveTemplatesFromPage
  });
  const result = Array.isArray(results) ? results[0]?.result : null;
  const catalog = normalizeShopTemplateCatalog(result?.catalog || result || {});
  let shop = normalizeCapturedShopInfo(result?.shop || {}, tab);
  if (!shop.shop_name) {
    try {
      const shopResults = await chrome.scripting.executeScript({
        target: { tabId: tab.id, allFrames: false },
        func: extractCurrentShopInfoFromPage
      });
      const shopResult = Array.isArray(shopResults) ? shopResults[0]?.result : null;
      const currentShop = normalizeCapturedShopInfo(shopResult?.shop || shopResult || {}, tab);
      if (currentShop.shop_name) shop = currentShop;
    } catch {
      // Template refresh can continue even when the shop-name helper is blocked by the current page.
    }
  }
  if (!catalog.freight_templates.length && !catalog.service_templates.length) {
    return { catalog, shop, source: result?.source || "empty" };
  }
  return { catalog, shop, source: catalog.source || result?.source || "current_shop_page" };
}

async function resolveCurrentPageTab(sender) {
  if (sender?.tab?.id) return sender.tab;
  const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
  return Array.isArray(tabs) ? tabs.find((tab) => tab?.id) : null;
}

function isRestrictedPageUrl(url) {
  const text = String(url || "");
  return /^(chrome|edge|about|devtools|chrome-extension):/i.test(text);
}

function normalizeCapturedShopInfo(raw, tab) {
  const source = isObject(raw) ? raw : {};
  const shopName = cleanCapturedShopName(firstShopText(
    source.shop_name,
    source.shopName,
    source.name,
    source.title
  ));
  const shopId = firstShopText(
    source.shop_id,
    source.shopId,
    source.id,
    source.shop_id_str,
    source.shopIdStr
  );
  const shopCode = firstShopText(source.shop_code, source.shopCode, source.code, source.merchant_id, source.merchantId);
  return {
    shop_id: String(shopId || "").trim().slice(0, 120),
    shop_name: isInvalidCapturedShopName(shopName) ? "" : shopName,
    shop_code: String(shopCode || "").trim().slice(0, 120),
    platform: String(source.platform || detectShopPlatform(tab?.url || source.url || "") || "douyin").trim().slice(0, 40),
    url: String(source.url || tab?.url || "").trim().slice(0, 500),
    source: String(source.source || "").trim().slice(0, 80),
    page_title: String(source.page_title || tab?.title || "").trim().slice(0, 160)
  };
}

function firstShopText(...values) {
  for (const value of values) {
    const text = String(value ?? "").trim();
    if (text) return text;
  }
  return "";
}

function cleanCapturedShopName(value) {
  return String(value || "")
    .replace(/[《》<>]/g, "")
    .replace(/\s+/g, " ")
    .replace(/^(当前店铺|店铺名称|店铺名|店铺|商家名称|商户名称|商家|商户)[:：\s]*/i, "")
    .trim()
    .slice(0, 120);
}

function isInvalidCapturedShopName(value) {
  const text = String(value || "").trim();
  if (!text) return true;
  if (/^(首页|工作台|抖店|店铺后台|商家后台|用户后台|下载页|抖音主页|商品|订单|订单管理|售后|物流|运费模板|服务模板)$/i.test(text)) return true;
  if (text.length < 2 || text.length > 80) return true;
  return false;
}

function detectShopPlatform(url) {
  const host = safeHostname(url);
  if (/jinritemai\.com$/i.test(host) || /douyin\.com$/i.test(host)) return "douyin";
  return "";
}

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

function normalizeShopTemplateCatalog(raw) {
  const source = isObject(raw) ? raw : {};
  return {
    freight_templates: normalizeShopTemplateList(source.freight_templates || source.freightTemplates || source.freight || []),
    service_templates: normalizeShopTemplateList(source.service_templates || source.serviceTemplates || source.service || []),
    source: String(source.source || "").trim().slice(0, 80),
    fetched_at: String(source.fetched_at || source.fetchedAt || "").trim().slice(0, 60)
  };
}

function normalizeShopTemplateList(list) {
  const results = [];
  const seen = new Set();
  for (const raw of Array.isArray(list) ? list : []) {
    const item = isObject(raw) ? raw : {};
    const id = firstShopText(item.id, item.template_id, item.templateId, item.value, item.code);
    const name = firstShopText(item.name, item.template_name, item.templateName, item.label, item.title, id);
    if (!name) continue;
    const key = `${id || name}::${name}`;
    if (seen.has(key)) continue;
    seen.add(key);
    results.push({
      id: String(id || name).trim().slice(0, 120),
      name: String(name).trim().slice(0, 160),
      source: String(item.source || "").trim().slice(0, 80)
    });
    if (results.length >= 80) break;
  }
  return results;
}

async function extractShopMoveTemplatesFromPage() {
  const MAX_STORAGE_KEYS = 260;
  const MAX_VALUE_LENGTH = 180000;
  const FREIGHT_RE = /freight|shipping|delivery|logistics|express|运费|物流|快递|发货|配送/i;
  const SERVICE_RE = /service|after|install|promise|qualification|服务|售后|安装|承诺|资质/i;
  const TEMPLATE_RE = /template|tpl|模板/i;
  const catalog = {
    freight_templates: [],
    service_templates: [],
    source: "",
    fetched_at: new Date().toISOString()
  };
  const seen = {
    freight_templates: new Set(),
    service_templates: new Set()
  };
  const pageUrl = String(location.href || "");
  const pageTitle = String(document.title || "").trim();
  let hasAuthoritativeFreightTemplates = false;

  await collectFromDouyinFreightApi();
  await collectFromObservedNetwork();
  collectFromStorage(localStorage, "storage");
  collectFromStorage(sessionStorage, "storage");
  collectFromDom();

  return {
    catalog,
    shop: collectShopInfo(),
    source: catalog.source || "empty"
  };

  async function collectFromDouyinFreightApi() {
    if (safeHostname(pageUrl) !== "fxg.jinritemai.com") return;
    const before = catalog.freight_templates.length;
    const token = await readFxgUserToken().catch(() => "");
    const fp = getCookieValue("s_v_web_id");
    const params = new URLSearchParams({
      page: "1",
      pageSize: "100",
      appid: "1",
      _bid: "ffa_order",
      aid: "4272",
      _lid: buildDouyinLid(),
      verifyFp: fp,
      fp,
      msToken: getCookieValue("msToken"),
      a_bogus: ""
    });
    if (token) params.set("__token", token);
    try {
      const apiUrl = `/freight/template/getShopFreightTemplate?${params.toString()}`;
      const response = await fetch(apiUrl, {
        credentials: "include",
        cache: "no-store",
        referrer: "https://fxg.jinritemai.com/ffa/morder/logistics/freight-list",
        headers: {
          Accept: "application/json,text/plain,*/*",
          "x-tt-from-appid": "ffa-order",
          "x-tt-from-end": "PC"
        }
      });
      const payload = tryParseJson(await response.text());
      if (payload) walkValue(payload, apiUrl, "douyinFreightApi", 0);
    } catch {
      // The generic collectors below still provide a fallback when the signed Douyin API is blocked.
    }
    hasAuthoritativeFreightTemplates = catalog.freight_templates.length > before;
  }

  async function collectFromObservedNetwork() {
    const urls = observedTemplateUrls();
    for (const url of urls.slice(0, 8)) {
      try {
        const response = await fetch(url, {
          credentials: "include",
          cache: "no-store",
          headers: { Accept: "application/json,text/plain,*/*" }
        });
        const text = await response.text();
        const payload = tryParseJson(text);
        if (payload) {
          walkValue(payload, url, "network", 0);
        }
      } catch {
        // Ignore individual template refresh failures.
      }
    }
  }

  function observedTemplateUrls() {
    let entries = [];
    try {
      entries = performance.getEntriesByType("resource") || [];
    } catch {
      return [];
    }
    const urls = [];
    for (const entry of entries) {
      const url = String(entry.name || "");
      if (!url || !sameOrigin(url)) continue;
      if (!/(freight|shipping|delivery|service|template|tpl|运费|物流|发货|服务|模板)/i.test(url)) continue;
      if (/\.(js|css|png|jpg|jpeg|gif|webp|svg|woff2?)(\?|$)/i.test(url)) continue;
      urls.push(url);
    }
    return Array.from(new Set(urls));
  }

  function sameOrigin(url) {
    try {
      return new URL(url, location.href).origin === location.origin;
    } catch {
      return false;
    }
  }

  function collectFromStorage(storage, source) {
    if (!storage) return;
    const total = Math.min(storage.length || 0, MAX_STORAGE_KEYS);
    for (let index = 0; index < total; index += 1) {
      const key = storage.key(index);
      if (!key) continue;
      let value = "";
      try {
        value = String(storage.getItem(key) || "");
      } catch {
        continue;
      }
      if (value.length > MAX_VALUE_LENGTH) continue;
      const context = `${key} ${value.slice(0, 8000)}`;
      if (!TEMPLATE_RE.test(context) && !FREIGHT_RE.test(context) && !SERVICE_RE.test(context)) continue;
      const parsed = tryParseJson(value);
      if (parsed) {
        walkValue(parsed, key, source, 0);
      } else {
        collectFromLooseText(value, key, source);
      }
    }
  }

  function collectFromDom() {
    const controls = Array.from(document.querySelectorAll("select, [role='listbox'], [class*='select'], [class*='dropdown']"))
      .slice(0, 160);
    for (const control of controls) {
      const context = visibleText(control.closest("form, section, article, div") || control).slice(0, 1200);
      const type = templateTypeFromContext(context);
      if (!type) continue;
      for (const option of Array.from(control.querySelectorAll("option, [role='option'], li, div")).slice(0, 120)) {
        const name = cleanTemplateName(visibleText(option));
        if (name && name.length <= 120) addTemplate(type, { name, id: option.getAttribute("value") || name }, "dom");
      }
    }
  }

  function walkValue(value, path, source, depth) {
    if (depth > 7 || value == null) return;
    if (Array.isArray(value)) {
      for (const item of value.slice(0, 80)) walkValue(item, path, source, depth + 1);
      return;
    }
    if (typeof value !== "object") {
      if (typeof value === "string" && value.length < 2000) collectFromLooseText(value, path, source);
      return;
    }
    const record = value;
    const keys = Object.keys(record);
    const context = `${path} ${keys.join(" ")}`;
    const type = templateTypeFromContext(context);
    const id = pickValue(record, [
      "freight_template_id", "shipping_template_id", "service_template_id", "template_id", "templateId",
      "freightTemplateId", "serviceTemplateId", "id", "value", "code"
    ]);
    const name = cleanTemplateName(pickValue(record, [
      "freight_template_name", "shipping_template_name", "service_template_name", "template_name", "templateName",
      "freightTemplateName", "serviceTemplateName", "name", "label", "title"
    ]));
    if (type && (name || id) && (TEMPLATE_RE.test(context) || FREIGHT_RE.test(context) || SERVICE_RE.test(context))) {
      addTemplate(type, { id, name: name || id }, source);
    }
    for (const key of keys.slice(0, 120)) {
      const next = record[key];
      if (next && typeof next === "object") {
        walkValue(next, `${path}.${key}`, source, depth + 1);
      } else if (typeof next === "string" && next.length < 2000 && (TEMPLATE_RE.test(key) || FREIGHT_RE.test(key) || SERVICE_RE.test(key))) {
        collectFromLooseText(next, `${path}.${key}`, source);
      }
    }
  }

  function collectFromLooseText(text, path, source) {
    const contextType = templateTypeFromContext(path);
    if (!contextType) return;
    const pattern = /(?:模板名称|模板名|templateName|name)\s*[:：=]\s*([^,，;；|｜\n]{2,80})/gi;
    let match;
    while ((match = pattern.exec(String(text || ""))) != null) {
      addTemplate(contextType, { name: cleanTemplateName(match[1]) }, source);
    }
  }

  function templateTypeFromContext(context) {
    const text = String(context || "");
    if (FREIGHT_RE.test(text)) return "freight_templates";
    if (SERVICE_RE.test(text)) return "service_templates";
    return "";
  }

  function addTemplate(type, raw, source) {
    if (!type || !catalog[type]) return;
    if (type === "freight_templates" && hasAuthoritativeFreightTemplates && source !== "douyinFreightApi") return;
    const id = cleanTemplateName(raw.id || raw.template_id || raw.templateId || raw.value || raw.code || "");
    const name = cleanTemplateName(raw.name || raw.template_name || raw.templateName || raw.label || raw.title || id);
    if (!name) return;
    const key = `${id || name}::${name}`;
    if (seen[type].has(key)) return;
    seen[type].add(key);
    catalog[type].push({
      id: (id || name).slice(0, 120),
      name: name.slice(0, 160),
      source
    });
    if (!catalog.source) catalog.source = source;
  }

  function collectShopInfo() {
    const titleName = pageTitle.replace(/[-_｜|].*$/, "").trim();
    return {
      shop_id: queryValue(["shop_id", "shopId", "merchant_id", "merchantId", "seller_id", "sellerId"]),
      shop_name: titleName,
      shop_code: "",
      platform: "douyin",
      url: pageUrl,
      source: "templatePage",
      page_title: pageTitle
    };
  }

  async function readFxgUserToken() {
    const userData = await readFxgSsrUserData();
    return String(userData?.user?.token || userData?.token || "").trim();
  }

  async function readFxgSsrUserData() {
    const current = extractUserDataFromSsrValue(window["__SSR_CONFIG_ECOM_FXG_ADMIN"]);
    if (current) return current;
    try {
      const response = await fetch("/ffa/mshop/", {
        credentials: "include",
        cache: "no-store",
        headers: { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
      });
      return extractUserDataFromSsrHtml(await response.text()) || null;
    } catch {
      return null;
    }
  }

  function extractUserDataFromSsrHtml(html) {
    const source = String(html || "");
    const marker = "\"userData\"";
    const markerIndex = source.indexOf(marker);
    if (markerIndex < 0) return null;
    const colonIndex = source.indexOf(":", markerIndex + marker.length);
    const braceIndex = source.indexOf("{", colonIndex);
    if (colonIndex < 0 || braceIndex < 0) return null;
    const jsonText = readBalancedJsonObject(source, braceIndex);
    if (!jsonText) return null;
    try {
      return JSON.parse(jsonText);
    } catch {
      return null;
    }
  }

  function extractUserDataFromSsrValue(value) {
    if (!value || typeof value !== "object") return null;
    if (value.userData && typeof value.userData === "object") return value.userData;
    return null;
  }

  function readBalancedJsonObject(source, startIndex) {
    let depth = 0;
    let quote = "";
    let escaped = false;
    for (let index = startIndex; index < source.length; index += 1) {
      const char = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (char === "\\") escaped = true;
        else if (char === quote) quote = "";
        continue;
      }
      if (char === "\"" || char === "'") {
        quote = char;
      } else if (char === "{") {
        depth += 1;
      } else if (char === "}") {
        depth -= 1;
        if (depth === 0) return source.slice(startIndex, index + 1);
      }
    }
    return "";
  }

  function getCookieValue(name) {
    const key = `${name}=`;
    return String(document.cookie || "")
      .split(";")
      .map((item) => item.trim())
      .find((item) => item.startsWith(key))
      ?.slice(key.length) || "";
  }

  function buildDouyinLid() {
    return String(Date.now()).slice(5) + String(Math.random()).slice(2, 6);
  }

  function safeHostname(url) {
    try {
      return new URL(String(url || ""), location.href).hostname.toLowerCase();
    } catch {
      return "";
    }
  }

  function queryValue(keys) {
    try {
      const parsed = new URL(pageUrl);
      for (const key of keys) {
        const value = parsed.searchParams.get(key);
        if (value) return value;
      }
    } catch {
      return "";
    }
    return "";
  }

  function pickValue(record, keys) {
    for (const key of keys) {
      if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
      const value = record[key];
      if (value == null) continue;
      if (typeof value === "string" || typeof value === "number") {
        const text = String(value).trim();
        if (text) return text;
      }
    }
    return "";
  }

  function visibleText(node) {
    return String(node?.innerText || node?.textContent || "").replace(/\s+/g, " ").trim();
  }

  function cleanTemplateName(value) {
    return String(value || "")
      .replace(/[《》<>]/g, "")
      .replace(/\s+/g, " ")
      .replace(/^(模板名称|模板名|运费模板|服务模板)[:：\s]*/i, "")
      .trim();
  }

  function tryParseJson(value) {
    try {
      return JSON.parse(String(value || ""));
    } catch {
      return null;
    }
  }
}

async function extractCurrentShopInfoFromPage() {
  const MAX_STORAGE_KEYS = 260;
  const MAX_TEXT_NODES = 220;
  const MAX_VALUE_LENGTH = 160000;
  const SHOP_CONTEXT_RE = /shop|store|seller|merchant|mshop|抖店|店铺|小店|商家|商户/i;
  const PRODUCT_CONTEXT_RE = /product|goods|item|commodity|商品/i;

  const pageUrl = String(location.href || "");
  const pageTitle = String(document.title || "").trim();
  const candidates = [];

  await collectFromPlatformApi();
  collectFromUrl();
  collectFromStorage(localStorage, "localStorage");
  collectFromStorage(sessionStorage, "sessionStorage");
  collectFromDom();
  collectFromTitle();

  candidates.sort((a, b) => scoreCandidate(b) - scoreCandidate(a));
  const best = candidates.find((item) => item && item.name && !isBadShopName(item.name)) || {};
  return {
    shop: {
      shop_id: best.id || "",
      shop_name: cleanShopName(best.name || ""),
      shop_code: best.code || "",
      platform: "douyin",
      url: pageUrl,
      source: best.source || "",
      page_title: pageTitle
    },
    candidates: candidates.slice(0, 8)
  };

  async function collectFromPlatformApi() {
    let parsed;
    try {
      parsed = new URL(pageUrl);
    } catch {
      return;
    }
    const host = String(parsed.hostname || "").toLowerCase();
    const urls = [];
    if (host === "fxg.jinritemai.com") {
      collectFromFxgSsrValue(window["__SSR_CONFIG_ECOM_FXG_ADMIN"], "windowSsr");
      await collectFromFxgSsrPage("/ffa/mshop/");
      urls.push(`/byteshop/index/getshoplist?appid=1&_lid=${Date.now()}`);
    }
    for (const url of urls) {
      try {
        const response = await fetch(url, {
          credentials: "include",
          cache: "no-store",
          headers: { Accept: "application/json,text/plain,*/*" }
        });
        const text = await response.text();
        const payload = tryParseJson(text);
        if (payload) {
          walkValue(payload, url, "platformApi", 0);
        }
      } catch {
        // Ignore platform API failures and continue with storage/DOM detection.
      }
    }
  }

  async function collectFromFxgSsrPage(url) {
    try {
      const response = await fetch(url, {
        credentials: "include",
        cache: "no-store",
        headers: { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
      });
      const userData = extractUserDataFromSsrHtml(await response.text());
      collectFxgUserData(userData, `fxgSsr:${url}`);
      if (userData) walkValue({ userData }, url, "fxgSsr", 0);
    } catch {
      // Continue with storage/DOM fallback when the SSR page is blocked.
    }
  }

  function collectFromFxgSsrValue(value, source) {
    const userData = value && typeof value === "object" ? value.userData : null;
    collectFxgUserData(userData, source);
    if (userData) walkValue({ userData }, source, "fxgSsr", 0);
  }

  function collectFxgUserData(userData, source) {
    if (!userData || typeof userData !== "object") return;
    const shop = userData.shop && typeof userData.shop === "object" ? userData.shop : {};
    const user = userData.user && typeof userData.user === "object" ? userData.user : {};
    const shopName = cleanShopName(shop.shop_name || shop.name || user.shop_name || user.name || "");
    const shopId = normalizeScalar(shop.shop_id || shop.shopId || shop.id || user.shop_id || user.shopId || "");
    const shopCode = normalizeScalar(shop.shop_code || shop.shopCode || user.shop_code || user.shopCode || "");
    if (shopName || shopId || shopCode) candidates.push({ name: shopName, id: shopId, code: shopCode, source });
  }

  function extractUserDataFromSsrHtml(html) {
    const source = String(html || "");
    const marker = "\"userData\"";
    const markerIndex = source.indexOf(marker);
    if (markerIndex < 0) return null;
    const colonIndex = source.indexOf(":", markerIndex + marker.length);
    const braceIndex = source.indexOf("{", colonIndex);
    if (colonIndex < 0 || braceIndex < 0) return null;
    const jsonText = readBalancedJsonObject(source, braceIndex);
    if (!jsonText) return null;
    try {
      return JSON.parse(jsonText);
    } catch {
      return null;
    }
  }

  function readBalancedJsonObject(source, startIndex) {
    let depth = 0;
    let quote = "";
    let escaped = false;
    for (let index = startIndex; index < source.length; index += 1) {
      const char = source[index];
      if (quote) {
        if (escaped) escaped = false;
        else if (char === "\\") escaped = true;
        else if (char === quote) quote = "";
        continue;
      }
      if (char === "\"" || char === "'") {
        quote = char;
      } else if (char === "{") {
        depth += 1;
      } else if (char === "}") {
        depth -= 1;
        if (depth === 0) return source.slice(startIndex, index + 1);
      }
    }
    return "";
  }

  function collectFromUrl() {
    let parsed;
    try {
      parsed = new URL(pageUrl);
    } catch {
      return;
    }
    const keys = ["shop_id", "shopId", "shop_id_str", "mshop_id", "mshopId", "merchant_id", "merchantId", "seller_id", "sellerId"];
    const id = first(keys.map((key) => parsed.searchParams.get(key)).filter(Boolean));
    if (id) candidates.push({ id, name: "", source: "url" });
  }

  function collectFromStorage(storage, source) {
    if (!storage) return;
    const total = Math.min(storage.length || 0, MAX_STORAGE_KEYS);
    for (let index = 0; index < total; index += 1) {
      const key = storage.key(index);
      if (!key) continue;
      let value = "";
      try {
        value = String(storage.getItem(key) || "");
      } catch {
        continue;
      }
      const searchText = `${key} ${value.slice(0, 6000)}`;
      if (!SHOP_CONTEXT_RE.test(searchText)) continue;
      if (value.length > MAX_VALUE_LENGTH) continue;
      collectFromValue(key, value, source);
    }
  }

  function collectFromValue(key, value, source) {
    const direct = extractFromLooseText(value);
    if (direct.name || direct.id) {
      candidates.push({ ...direct, source: `${source}:${key}` });
    }
    const parsed = tryParseJson(value);
    if (parsed) walkValue(parsed, key, source, 0);
  }

  function walkValue(value, path, source, depth) {
    if (depth > 6 || value == null) return;
    if (Array.isArray(value)) {
      for (const item of value.slice(0, 40)) walkValue(item, path, source, depth + 1);
      return;
    }
    if (typeof value !== "object") return;
    const record = value;
    const keys = Object.keys(record);
    const context = `${path} ${keys.join(" ")}`;
    const name = pickValue(record, [
      "shopName", "shop_name", "shop_name_str", "shopTitle", "storeName", "store_name",
      "merchantName", "merchant_name", "sellerName", "seller_name", "name", "title"
    ], context);
    const id = pickValue(record, [
      "shopId", "shop_id", "shop_id_str", "shopIdStr", "mshopId", "mshop_id",
      "merchantId", "merchant_id", "sellerId", "seller_id", "id"
    ], context);
    const code = pickValue(record, ["shopCode", "shop_code", "code", "merchantCode", "merchant_code"], context);
    if (name || id || code) {
      candidates.push({ name, id, code, source: `${source}:${path}` });
    }
    for (const key of keys.slice(0, 80)) {
      const nextPath = `${path}.${key}`;
      if (SHOP_CONTEXT_RE.test(nextPath) || typeof record[key] === "object") {
        walkValue(record[key], nextPath, source, depth + 1);
      }
    }
  }

  function collectFromDom() {
    const texts = Array.from(document.querySelectorAll("body *"))
      .slice(0, 1800)
      .map((node) => String(node.textContent || "").replace(/\s+/g, " ").trim())
      .filter((text) => text && text.length >= 2 && text.length <= 180)
      .slice(0, MAX_TEXT_NODES);
    for (const text of texts) {
      const result = extractFromLooseText(text);
      if (result.name || result.id) candidates.push({ ...result, source: "dom" });
    }
  }

  function collectFromTitle() {
    const title = cleanShopName(pageTitle.replace(/[-_｜|].*$/, ""));
    if (title && !isBadShopName(title) && SHOP_CONTEXT_RE.test(`${pageTitle} ${pageUrl}`)) {
      candidates.push({ name: title, id: "", code: "", source: "title" });
    }
  }

  function extractFromLooseText(text) {
    const source = String(text || "").replace(/\s+/g, " ").trim();
    if (!source) return { name: "", id: "", code: "" };
    const nameMatch = source.match(/(?:店铺名称|店铺名|店铺|小店|商家名称|商户名称|商家|商户|shopName|shop_name|storeName|merchantName)\s*[:：=]\s*([^,，;；|｜]{2,80})/i);
    const idMatch = source.match(/(?:店铺ID|店铺编号|商家ID|商户ID|shopId|shop_id|merchantId|merchant_id|sellerId)\s*[:：=]\s*([A-Za-z0-9_-]{4,80})/i);
    return {
      name: cleanShopName(nameMatch ? nameMatch[1] : ""),
      id: idMatch ? String(idMatch[1] || "").trim() : "",
      code: ""
    };
  }

  function pickValue(record, keys, context) {
    for (const key of keys) {
      if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
      if ((key === "name" || key === "title" || key === "id") && (!SHOP_CONTEXT_RE.test(context) || PRODUCT_CONTEXT_RE.test(context))) continue;
      const value = normalizeScalar(record[key]);
      if (value) return key === "name" || key === "title" ? cleanShopName(value) : value;
    }
    return "";
  }

  function normalizeScalar(value) {
    if (value == null) return "";
    if (typeof value === "string" || typeof value === "number") return String(value).trim();
    return "";
  }

  function scoreCandidate(item) {
    let score = 0;
    if (item.name) score += 50;
    if (item.id) score += 12;
    if (item.code) score += 6;
    if (/localStorage|sessionStorage/.test(item.source || "")) score += 18;
    if (/fxgSsr|windowSsr/.test(item.source || "")) score += 45;
    if (/dom/.test(item.source || "")) score += 8;
    if (/title/.test(item.source || "")) score -= 8;
    if (item.name && isBadShopName(item.name)) score -= 100;
    return score;
  }

  function cleanShopName(value) {
    return String(value || "")
      .replace(/[《》<>]/g, "")
      .replace(/\s+/g, " ")
      .replace(/^(当前店铺|店铺名称|店铺名|店铺|商家名称|商户名称|商家|商户)[:：\s]*/i, "")
      .trim()
      .slice(0, 120);
  }

  function isBadShopName(value) {
    const text = cleanShopName(value);
    if (!text) return true;
    if (/^(首页|工作台|抖店|店铺后台|商家后台|用户后台|下载页|抖音主页|商品|订单|订单管理|售后|物流|运费模板|服务模板)$/i.test(text)) return true;
    return text.length < 2 || text.length > 80;
  }

  function tryParseJson(value) {
    try {
      return JSON.parse(value);
    } catch {
      return null;
    }
  }

  function first(values) {
    for (const value of values) {
      const text = String(value || "").trim();
      if (text) return text;
    }
    return "";
  }
}

function pluginBalanceObjects(payload) {
  if (!isObject(payload)) return [];
  const objects = [];
  const pending = [payload];
  const seen = new Set();
  while (pending.length && objects.length < 16) {
    const current = pending.shift();
    if (!isObject(current) || seen.has(current)) continue;
    seen.add(current);
    objects.push(current);
    for (const key of ["data", "account", "user", "billing", "result"]) {
      if (isObject(current[key])) pending.push(current[key]);
    }
  }
  return objects;
}

function firstPluginBalanceValue(objects, keys) {
  for (const key of keys) {
    for (const object of objects) {
      const value = object[key];
      if (value !== undefined && value !== null && value !== "") return value;
    }
  }
  return undefined;
}

function pluginBalanceNumber(value) {
  if (value === undefined) return undefined;
  const number = Number(value);
  return Number.isFinite(number) ? Math.max(0, Math.floor(number)) : undefined;
}

async function syncPluginAccountBalances(payload, expectedToken = "") {
  const objects = pluginBalanceObjects(payload);
  const pointsBalance = pluginBalanceNumber(firstPluginBalanceValue(objects, [
    "current_points_balance", "currentPointsBalance",
    "points_balance_after", "pointsBalanceAfter",
    "points_balance", "pointsBalance"
  ]));
  const aiWalletBalance = pluginBalanceNumber(firstPluginBalanceValue(objects, [
    "current_ai_wallet_balance_cents", "currentAiWalletBalanceCents",
    "ai_wallet_balance_after_cents", "aiWalletBalanceAfterCents",
    "wallet_balance_after_cents", "walletBalanceAfterCents",
    "ai_wallet_balance_cents", "aiWalletBalanceCents",
    "wallet_balance_cents", "walletBalanceCents"
  ]));
  const freeRemaining = pluginBalanceNumber(firstPluginBalanceValue(objects, [
    "free_quota_remaining", "source_trace_free_remaining", "freeQuotaRemaining"
  ]));
  if (pointsBalance === undefined && aiWalletBalance === undefined && freeRemaining === undefined) return;

  const auth = await getPluginAuthState({ refresh: false });
  if (!auth.token || (expectedToken && auth.token !== expectedToken)) return;
  const account = isObject(auth.account) ? { ...auth.account } : {};
  if (pointsBalance !== undefined) {
    account.points_balance = pointsBalance;
    account.pointsBalance = pointsBalance;
  }
  if (aiWalletBalance !== undefined) {
    account.ai_wallet_balance_cents = aiWalletBalance;
    account.aiWalletBalanceCents = aiWalletBalance;
  }
  if (freeRemaining !== undefined) account.source_trace_free_remaining = freeRemaining;
  await setPluginAuthState({ ...auth, account }).catch(() => {});
}

async function publishPluginRemoteLogin(payload = {}) {
  const remoteLogin = payload?.remote_login && typeof payload.remote_login === "object"
    ? payload.remote_login
    : {};
  const state = {
    active: true,
    changedAt: Date.now(),
    message: String(payload?.message || REMOTE_LOGIN_MESSAGE).split(/\r?\n/, 1)[0],
    remoteLogin: {
      loginIp: String(remoteLogin.login_ip || remoteLogin.loginIp || "").trim(),
      loginAt: String(remoteLogin.login_at || remoteLogin.loginAt || "").trim(),
      deviceName: String(remoteLogin.device_name || remoteLogin.deviceName || "").trim()
    }
  };
  await setPluginAuthState(normalizeAuthState(null)).catch(() => {});
  await chrome.storage.local.set({ [REMOTE_LOGIN_STORAGE_KEY]: state }).catch(() => {});
  const message = { type: REMOTE_LOGIN_EVENT, ...state };
  chrome.runtime.sendMessage(message).catch(() => {});
  chrome.tabs.query({}).then((tabs) => Promise.all((tabs || []).map((tab) => (
    tab?.id ? chrome.tabs.sendMessage(tab.id, message).catch(() => {}) : null
  )))).catch(() => {});
}

async function requestJson(baseUrl, method, path, body, token = "", timeoutMs = 12000, options = {}) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), Math.max(3000, Number(timeoutMs) || 12000));
  const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
  let responseReceived = false;
  recordAdminDebugEvent("backend-request", {
    method,
    path,
    baseUrl: normalizedBaseUrl,
    body: summarizeBackendDebugBody(path, body)
  });
  try {
    const headers = { Accept: "application/json" };
    if (body) headers["Content-Type"] = "application/json";
    if (token) headers.Authorization = `Bearer ${token}`;
    const response = await fetch(`${normalizedBaseUrl}${path}`, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
      credentials: "omit",
      cache: "no-store",
      signal: controller.signal
    });
    responseReceived = true;
    void globalThis.MMWBackendConnectivity?.publish(false);
    const text = await response.text();
    let payload = {};
    try {
      payload = text ? JSON.parse(text) : {};
    } catch {
      payload = { message: text };
    }
    await syncPluginAccountBalances(payload, token);
    recordAdminDebugEvent("backend-response", {
      method,
      path,
      baseUrl: normalizedBaseUrl,
      status: response.status,
      ok: response.ok,
      payload: summarizeBackendDebugPayload(path, payload)
    }, response.ok ? "info" : "warn");
    if (!response.ok || payload?.success === false) {
      const error = new Error(messageFromPayload(payload) || `请求失败：HTTP ${response.status}`);
      error.status = response.status;
      error.code = String(payload?.code || payload?.error?.code || "").trim();
      error.payload = payload;
      if (error.code === REMOTE_LOGIN_CODE) {
        await publishPluginRemoteLogin(payload);
      }
      throw error;
    }
    return payload;
  } catch (error) {
    recordAdminDebugEvent("backend-error", {
      method,
      path,
      baseUrl: normalizedBaseUrl,
      error: error.message || String(error)
    }, "error");
    if (!responseReceived && globalThis.MMWBackendConnectivity?.isNoResponseError(error)) {
      if (options.notifyMaintenance !== false) {
        await globalThis.MMWBackendConnectivity.publish(true);
      }
      throw globalThis.MMWBackendConnectivity.createUnavailableError();
    }
    throw error;
  } finally {
    clearTimeout(timer);
  }
}

function summarizeBackendDebugBody(path, body) {
  if (!isObject(body)) return body;
  if (!String(path || "").includes("/api/shop-move/parse-products")) return body;
  return {
    ...body,
    snapshots: Array.isArray(body.snapshots)
      ? body.snapshots.map((snapshot) => summarizeShopMoveDebugSnapshot(snapshot))
      : body.snapshots
  };
}

function summarizeBackendDebugPayload(path, payload) {
  if (!String(path || "").includes("/api/shop-move/parse-products")) return payload;
  const data = isObject(payload?.data) ? payload.data : payload;
  const products = Array.isArray(data?.products) ? data.products : [];
  return {
    success: payload?.success,
    message: payload?.message,
    success_count: data?.success_count,
    failed_count: data?.failed_count,
    products: products.map((item) => ({
      status: item?.status,
      message: item?.message,
      url: item?.url,
      normalized_url: item?.normalized_url,
      source_item_id: item?.source_item_id,
      product: summarizeShopMoveDebugProduct(item?.product)
    }))
  };
}

function summarizeShopMoveDebugSnapshot(snapshot) {
  const product = isObject(snapshot?.product) ? snapshot.product : {};
  const records = Array.isArray(snapshot?.api_records) ? snapshot.api_records : [];
  const packageState = getShopMoveCapturedPackageState(snapshot);
  return {
    url: snapshot?.url,
    normalized_url: snapshot?.normalized_url,
    source_item_id: snapshot?.source_item_id,
    resolved_url: snapshot?.resolved_url,
    html_length: String(snapshot?.html || "").length,
    record_count: records.length,
    package_state: packageState,
    records: records.slice(0, 20).map((record) => ({
      kind: record?.kind,
      method: record?.method,
      url: record?.url,
      status: record?.status,
      content_type: record?.content_type,
      body_length: String(record?.body || "").length,
      page_url: record?.page_url
    })),
    product: summarizeShopMoveDebugProduct(product)
  };
}

function getShopMoveProductAttributes(product) {
  const raw = isObject(product) ? product : {};
  return {
    ...(isObject(raw.base_attr) ? raw.base_attr : {}),
    ...(isObject(raw.baseAttr) ? raw.baseAttr : {}),
    ...(isObject(raw.attributes) ? raw.attributes : {})
  };
}

function getShopMoveProductMainImages(product) {
  const raw = isObject(product) ? product : {};
  return uniqueStrings([
    ...listFromAny(raw.main_images),
    ...listFromAny(raw.mainImages),
    raw.main_image,
    raw.mainImage
  ]).filter(isLikelyShopMoveImageUrl);
}

function getShopMoveProductDetailImages(product) {
  const raw = isObject(product) ? product : {};
  return uniqueStrings([
    ...listFromAny(raw.detail_images),
    ...listFromAny(raw.detailImages)
  ]).filter(isLikelyShopMoveImageUrl);
}

function getShopMoveProductSpecImages(product) {
  const raw = isObject(product) ? product : {};
  return uniqueStrings([
    ...listFromAny(raw.spec_images),
    ...listFromAny(raw.specImages)
  ]).filter(isLikelyShopMoveImageUrl);
}

function getShopMoveProductSkuList(product) {
  const raw = isObject(product) ? product : {};
  return Array.isArray(raw.sku_list)
    ? raw.sku_list
    : (Array.isArray(raw.skuList) ? raw.skuList : []);
}

function pickPreferredShopMoveImages(baseImages, extraImages, baseProduct, extraProduct, kind) {
  const base = Array.isArray(baseImages) ? baseImages : [];
  const extra = Array.isArray(extraImages) ? extraImages : [];
  if (!base.length) return extra;
  if (!extra.length) return base;
  const baseScore = scoreShopMoveImageSet(base, baseProduct, kind);
  const extraScore = scoreShopMoveImageSet(extra, extraProduct, kind);
  if (extraScore === baseScore) return extra.length >= base.length ? extra : base;
  return extraScore > baseScore ? extra : base;
}

function scoreShopMoveImageSet(images, product, kind) {
  const count = Array.isArray(images) ? images.length : 0;
  if (!count) return 0;
  let score = count;
  if (kind === "main") score += count > 1 ? 20 : 0;
  if (kind === "detail") score += count > 3 ? 8 : 3;
  if (isShopMoveApiBackedProduct(product)) score += 12;
  if (isShopMoveDomOnlyProduct(product)) score -= 2;
  return score;
}

function isShopMoveApiBackedProduct(product) {
  const source = String(product?.source || "");
  return /(rendered_tab_api|plugin_api_records|douyin_product_sku_list|direct_promotion_pack|maibaole_promotion_pack)/i.test(source);
}

function isShopMoveDomOnlyProduct(product) {
  const source = String(product?.source || "");
  return /(rendered_tab_dom|plugin_snapshot|plugin_html_snapshot|server_html|html)/i.test(source);
}

function summarizeShopMoveDebugProduct(product) {
  if (!isObject(product)) return {};
  return {
    title: product.title || product.source_title || "",
    price: product.price || product.sale_price || "",
    source: product.source || "",
    image_count: listFromAny(product.images).length,
    main_image_count: listFromAny(product.main_images).length || (product.main_image ? 1 : 0),
    detail_image_count: listFromAny(product.detail_images).length,
    attribute_count: Object.keys(isObject(product.attributes) ? product.attributes : {}).length,
    sku_count: Array.isArray(product.sku_list) ? product.sku_list.length : 0,
    missing_fields: Array.isArray(product.missing_fields) ? product.missing_fields : []
  };
}

function unwrapPayload(payload) {
  if (payload?.success === false) throw new Error(messageFromPayload(payload) || "账号接口返回失败");
  return isObject(payload?.data) ? payload.data : payload;
}

function messageFromPayload(payload) {
  return String(payload?.message || payload?.error || payload?.data?.message || "").trim();
}

async function setPluginAuthState(authInput) {
  const auth = normalizeAuthState(authInput);
  const entitlement = entitlementFromAuth(auth);
  auth.active = Boolean(entitlement.active);
  auth.entitlement = entitlement;
  await chrome.storage.local.set({
    [STORAGE_KEYS.auth]: auth,
    [STORAGE_KEYS.apiBaseUrl]: auth.baseUrl,
    [STORAGE_KEYS.entitlement]: entitlement,
    [STORAGE_KEYS.activation]: activationFromEntitlement(entitlement)
  });
  return auth;
}

function buildAuthState({ baseUrl, token, user, account }) {
  const auth = normalizeAuthState({
    baseUrl,
    token,
    user,
    account,
    loggedIn: Boolean(token),
    checkedAt: Date.now()
  }, baseUrl);
  auth.entitlement = buildEntitlement(user, account, auth.loggedIn);
  auth.active = Boolean(auth.entitlement.active);
  const riskWarning = isObject(account?.risk_warning) ? account.risk_warning : null;
  auth.message = String(riskWarning?.message || auth.entitlement.label || "").trim();
  return auth;
}

function normalizeAuthState(value, fallbackBaseUrl = DEFAULT_API_BASE_URL) {
  const raw = isObject(value) ? value : {};
  const user = isObject(raw.user) ? raw.user : {};
  const account = isObject(raw.account) ? raw.account : {};
  const token = String(raw.token || "").trim();
  const loggedIn = Boolean(token);
  const entitlement = normalizeEntitlement(raw.entitlement || buildEntitlement(user, account, loggedIn));
  return {
    baseUrl: normalizeBaseUrl(raw.baseUrl || fallbackBaseUrl || DEFAULT_API_BASE_URL),
    token,
    user,
    account,
    loggedIn,
    active: loggedIn && Boolean(entitlement.active),
    entitlement,
    checkedAt: Number(raw.checkedAt || 0),
    message: String(raw.message || entitlement.label || "").trim()
  };
}

function buildEntitlement(user, account, loggedIn) {
  if (!loggedIn) {
    return { active: false, expireTime: 0, source: "remote_account", label: "请先登录账号" };
  }
  const ban = isObject(account.account_ban) ? account.account_ban : {};
  if (account.is_banned === true || ban.is_banned === true) {
    return {
      active: false,
      expireTime: Date.now() - 1000,
      source: "account_ban",
      label: String(ban.message || account.message || "账号已封禁。如有异议请联系客服审核解封。")
    };
  }
  const userType = String(account.user_type || user.user_type || "").toLowerCase();
  const isAdmin = userType === "admin" || userType === "administrator" || userType.includes("管理员");
  const expiresAt = String(account.expires_at || account.expire_at || user.expires_at || "").trim();
  const expireTime = parseExpireTime(expiresAt);
  const memberStatus = String(account.member_status || account.status || "").trim();
  const licenseStatus = String(account.license_status || account.license || "").trim();
  const expired = !isAdmin && Boolean(expireTime && Date.now() > expireTime);
  const badMember = !isAdmin && /已到期|过期|失效|停用/.test(memberStatus);
  const badLicense = !isAdmin && /需要激活|已过期|过期|失效|停用/.test(licenseStatus);
  const activationRequired = !isAdmin && account.activation_required === true;
  const active = isAdmin || (!expired && !badMember && !badLicense && !activationRequired);
  let label = active ? "账号会员有效" : "账号会员不可用";
  if (active && expiresAt && !isAdmin) label = `会员有效至 ${expiresAt}`;
  if (!active && (expired || badMember || badLicense || activationRequired)) label = "账号会员已到期";
  return { active, expireTime, source: "remote_account", label };
}

function entitlementFromAuth(authInput) {
  const auth = normalizeAuthState(authInput);
  return normalizeEntitlement(auth.entitlement || buildEntitlement(auth.user, auth.account, auth.loggedIn));
}

function isAuthUsable(authInput) {
  const auth = normalizeAuthState(authInput);
  const entitlement = entitlementFromAuth(auth);
  if (!auth.loggedIn || !entitlement.active) return false;
  return !entitlement.expireTime || Date.now() <= entitlement.expireTime;
}

function sanitizeAuthForUi(authInput) {
  const auth = normalizeAuthState(authInput);
  return {
    baseUrl: auth.baseUrl,
    loggedIn: auth.loggedIn,
    active: isAuthUsable(auth),
    user: auth.user,
    account: auth.account,
    entitlement: entitlementFromAuth(auth),
    checkedAt: auth.checkedAt,
    message: auth.message || auth.entitlement?.label || ""
  };
}

async function getStoredBaseUrl() {
  const current = await chrome.storage.local.get([STORAGE_KEYS.apiBaseUrl]);
  return normalizeBaseUrl(current[STORAGE_KEYS.apiBaseUrl] || DEFAULT_API_BASE_URL);
}

function parseExpireTime(value) {
  const text = String(value || "").trim();
  if (!text) return 0;
  const normalized = text.includes("T") ? text : text.replace(" ", "T");
  const time = Date.parse(normalized);
  return Number.isFinite(time) ? time : 0;
}

function normalizeComparableVersion(value) {
  const text = String(value || "").trim();
  if (!text) return "";
  const match = text.match(/\d+(?:\.\d+)+/);
  return match ? match[0] : text;
}

function compareVersions(left, right) {
  const leftParts = String(normalizeComparableVersion(left) || "").match(/\d+/g) || [];
  const rightParts = String(normalizeComparableVersion(right) || "").match(/\d+/g) || [];
  const size = Math.max(leftParts.length, rightParts.length);
  for (let index = 0; index < size; index += 1) {
    const leftNumber = Number(leftParts[index] || 0);
    const rightNumber = Number(rightParts[index] || 0);
    if (leftNumber !== rightNumber) return leftNumber - rightNumber;
  }
  return 0;
}

function normalizeBaseUrl(value) {
  let text = String(value || DEFAULT_API_BASE_URL).trim() || DEFAULT_API_BASE_URL;
  if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(text)) {
    text = `https://${text}`;
  }
  try {
    const url = new URL(text);
    const host = url.hostname.toLowerCase();
    if (host === "mmwgzt.xyz" || host === "www.mmwgzt.xyz") {
      return DEFAULT_API_BASE_URL;
    }
  } catch {}
  return text.replace(/\/+$/, "");
}

function isObject(value) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function handleContentResources(message, sender, sendResponse) {
  const tabId = sender.tab?.id;
  if (typeof tabId !== "number") {
    sendResponse({ ok: false });
    return;
  }
  const pageTitle = sender.tab?.title || "";
  const pageUrl = sender.tab?.url || "";
  for (const item of message.resources || []) {
    const parsed = classifyResource(item.url, item.mimeType, item.kind);
    if (!parsed) continue;
    addResource(tabId, {
      ...parsed,
      id: makeId(item.url, item.source || "dom"),
      url: item.url,
      source: item.source || "dom",
      name: String(item.name || item.fileName || "").trim(),
      fileName: String(item.fileName || item.name || "").trim(),
      extension: String(item.extension || parsed.extension || "").trim().toLowerCase(),
      width: Number(item.width) || 0,
      height: Number(item.height) || 0,
      size: Number(item.size) || 0,
      cardId: String(item.cardId || "").trim(),
      authorName: String(item.authorName || "").trim(),
      likeCountText: String(item.likeCountText || "").trim(),
      videoTitle: normalizeVideoTitleText(item.videoTitle),
      awemeId: String(item.awemeId || "").trim(),
      sourceTraceUrl: String(item.sourceTraceUrl || "").trim(),
      pageTitle,
      pageUrl,
      requestHeaders: normalizeDownloadHeaders(item.requestHeaders, pageUrl),
      time: Date.now()
    });
  }
  sendResponse({ ok: true });
}

function handleContentCardMeta(message, sender, sendResponse) {
  const tabId = sender.tab?.id;
  if (typeof tabId !== "number") {
    sendResponse({ ok: false });
    return;
  }
  const meta = normalizeCardMeta(message.meta || {});
  if (!meta.authorName && !meta.likeCountText && !meta.sourceTraceUrl && !meta.videoTitle) {
    sendResponse({ ok: false });
    return;
  }
  tabActiveCardMeta.set(tabId, {
    ...meta,
    pageUrl: sender.tab?.url || "",
    time: Date.now()
  });
  const patched = applyCardMetaToRecentResources(tabId, meta);
  if (patched) chrome.tabs.sendMessage(tabId, { type: "FLOATING_RESOURCES_CHANGED" }).catch(() => {});
  sendResponse({ ok: true });
}

function handleGetResources(sender, sendResponse) {
  const tabId = sender.tab?.id;
  sendResponse({
    ok: true,
    resources: typeof tabId === "number" ? getResources(tabId) : [],
    count: typeof tabId === "number" ? getResources(tabId).length : 0
  });
}

async function injectDouyinHook(message, sender, sendResponse) {
  sendResponse({ ok: true, migrated: "aix-douyin" });
}

function handleClearResources(sender, sendResponse) {
  const tabId = sender.tab?.id;
  if (typeof tabId === "number") {
    tabResources.delete(tabId);
    tabFingerprints.delete(tabId);
    updateVideoBadge(tabId, 0);
  }
  sendResponse({ ok: true });
}

function promptLegalConsentDialog(sender) {
  const tabId = Number(sender?.tab?.id);
  if (!Number.isInteger(tabId) || tabId < 0) return;
  chrome.tabs.sendMessage(tabId, { type: "OPEN_FLOATING_DESK", view: "video" }).catch(() => {});
}

async function handleDownloadSelected(items, sendResponse, mode = "all") {
  const results = [];
  const unique = uniqueItems(items);
  for (let index = 0; index < unique.length; index += 1) {
    const item = unique[index];
    if (!item?.url) continue;
    if (!isDownloadCandidate(item)) {
      results.push({ ok: false, url: item.url, error: "不是可下载媒体资源" });
      continue;
    }
    try {
      if (!shouldSkipPlayableProbe(item)) {
        await validateDownloadResourceSafe(item);
        if (["video", "audio"].includes(item.kind)) await validatePlayableDownloadCandidate(item);
      }
      if (shouldUseBackupDownloader(item)) {
        const backup = await openBackupDownloadForItem(item, index);
        results.push({ ok: true, backup: true, requestId: backup.requestId, windowId: backup.windowId, url: item.url });
        if (mode === "firstPlayable") {
          sendResponse({ ok: true, results });
          return;
        }
        if (index < unique.length - 1) await delay(900);
      } else {
        const id = await downloadWithHeaders(item, index);
        results.push({ ok: true, id, url: item.url });
        if (mode === "firstPlayable") {
          sendResponse({ ok: true, results });
          return;
        }
      }
    } catch (error) {
      results.push({ ok: false, url: item.url, error: error.message || String(error) });
    }
  }
  sendResponse({ ok: results.some((item) => item.ok), results });
}

async function handleFloatingDownloadSelected(candidateGroups, sendResponse, options = {}) {
  const groups = normalizeFloatingDownloadGroups(candidateGroups);
  const results = [];
  const pendingBackups = [];
  if (!groups.length) {
    sendResponse({ ok: false, error: "未找到可下载的视频资源", results });
    return;
  }
  for (let groupIndex = 0; groupIndex < groups.length; groupIndex += 1) {
    const group = groups[groupIndex];
    let groupResult = null;
    for (let candidateIndex = 0; candidateIndex < group.length; candidateIndex += 1) {
      const item = group[candidateIndex];
      if (!item?.url) continue;
      if (!isDownloadCandidate(item)) {
        groupResult = { ok: false, url: item.url, error: "不是可下载媒体资源" };
        continue;
      }
      try {
        if (shouldUseFloatingBackupDownloader(item)) {
          pendingBackups.push({
            resultIndex: results.length,
            request: createFloatingBackupDownloadRequest(item)
          });
          groupResult = { ok: true, backup: true, pending: true, url: item.url };
        } else {
          const id = await downloadWithHeaders(item, groupIndex);
          groupResult = { ok: true, id, url: item.url };
        }
        break;
      } catch (error) {
        groupResult = { ok: false, url: item.url, error: error.message || String(error) };
      }
    }
    results.push(groupResult || { ok: false, error: "未找到可下载的视频资源" });
    if (groupIndex < groups.length - 1) await delay(650);
  }
  if (pendingBackups.length) {
    try {
      const sequence = await openFloatingBackupDownloadSequence(pendingBackups.map((item) => item.request));
      const sequenceResults = Array.isArray(sequence.results) ? sequence.results : [];
      for (let index = 0; index < pendingBackups.length; index += 1) {
        const item = pendingBackups[index];
        const sequenceResult = sequenceResults[index] || {};
        results[item.resultIndex] = {
          ...results[item.resultIndex],
          pending: false,
          requestId: sequenceResult.requestId,
          windowId: sequenceResult.windowId,
          error: sequenceResult.ok === false ? sequenceResult.error : undefined,
          ok: sequenceResult.ok !== false
        };
      }
    } catch (error) {
      for (const item of pendingBackups) {
        results[item.resultIndex] = {
          ok: false,
          backup: true,
          url: item.request.url,
          error: error.message || String(error)
        };
      }
    }
  }
  let titleDownloaded = 0;
  if (options.downloadTitles === true) {
    for (let index = 0; index < results.length; index += 1) {
      if (!results[index]?.ok) continue;
      const title = findVideoTitleInGroup(groups[index]);
      if (!title) continue;
      try {
        const titleId = await startVideoTitleDownload(title, groups[index]?.[0], index);
        results[index].titleId = titleId;
        results[index].titleDownloaded = true;
        titleDownloaded += 1;
      } catch (error) {
        results[index].titleError = error.message || String(error);
      }
    }
  }
  sendResponse({ ok: results.some((item) => item?.ok), results, titleDownloaded });
}

function normalizeFloatingDownloadGroups(candidateGroups) {
  const rawGroups = Array.isArray(candidateGroups) ? candidateGroups : [];
  return rawGroups
    .map((group) => uniqueItems(Array.isArray(group) ? group : [group]).filter(Boolean))
    .filter((group) => group.length);
}

function findVideoTitleInGroup(group) {
  for (const item of Array.isArray(group) ? group : []) {
    const title = normalizeVideoTitleText(item?.videoTitle);
    if (title) return title;
  }
  return "";
}

async function handleVideoTitleDownload(message, sendResponse) {
  try {
    const title = normalizeVideoTitleText(message?.title);
    if (!title) throw new Error("未抓取到视频标题");
    const id = await startVideoTitleDownload(title, {
      awemeId: String(message?.awemeId || "").trim(),
      sourceTraceUrl: String(message?.sourceTraceUrl || "").trim()
    });
    sendResponse({ ok: true, id, title });
  } catch (error) {
    sendResponse({ ok: false, error: error.message || "标题下载失败" });
  }
}

function startVideoTitleDownload(title, item = {}, index = 0) {
  const normalizedTitle = normalizeVideoTitleText(title);
  if (!normalizedTitle) return Promise.reject(new Error("未抓取到视频标题"));
  const fallbackName = String(item?.awemeId || "").trim() || `douyin-title-${index + 1}`;
  const fileBase = sanitizeFileName(normalizedTitle).slice(0, 100) || fallbackName;
  const dataUrl = `data:text/plain;charset=utf-8,${encodeURIComponent(`${normalizedTitle}\n`)}`;
  return new Promise((resolve, reject) => {
    chrome.downloads.download({
      url: dataUrl,
      filename: `${fileBase}.txt`,
      saveAs: false,
      conflictAction: "uniquify"
    }, (id) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message || "标题下载失败"));
        return;
      }
      resolve(id);
    });
  });
}

function normalizeVideoTitleText(value) {
  return String(value || "")
    .replace(/\s+/g, " ")
    .replace(/\s*(?:展开|收起)\s*$/u, "")
    .trim()
    .slice(0, 500);
}

function shouldUseFloatingBackupDownloader(item) {
  if (!item?.url || item.kind === "image" || item.kind === "playlist") return false;
  if (item.source === "source-trace-batch" || item.source === "source-trace") return false;
  return ["video", "audio"].includes(item.kind) && isTrustedMediaHost(item.url);
}

function createFloatingBackupDownloadRequest(item) {
  const requestHeaders = normalizeFloatingBackupHeaders(
    item.requestHeaders,
    item.requestHeaders?.Referer || item.requestHeaders?.referer || item.pageUrl || "https://www.douyin.com/"
  );
  return makeBackupDownloadRequest({
    url: item.url,
    referer: requestHeaders.Referer || item.pageUrl || "https://www.douyin.com/",
    suggestedTitle: item.pageTitle || item.name || "",
    name: item.name || "",
    extension: item.extension || "",
    kind: item.kind || "video",
    pageTitle: item.pageTitle || "",
    pageUrl: item.pageUrl || "",
    requestHeaders
  });
}

async function openFloatingBackupDownloadSequence(requests) {
  const usable = requests.filter((request) => request?.url);
  if (!usable.length) throw new Error("未找到可下载的视频资源");
  recordAdminDebugEvent("floating-backup-download-sequence", {
    count: usable.length,
    hosts: [...new Set(usable.map((item) => getHost(item.url)).filter(Boolean))].slice(0, 5)
  });
  const results = [];
  for (let index = 0; index < usable.length; index += 1) {
    const request = usable[index];
    try {
      const opened = await openBackupDownloadWindow(request, { focused: true });
      results.push({ ok: true, ...opened, url: request.url });
      await waitForBackupDownloadWindow(opened.windowId);
      if (index < usable.length - 1) await delay(220);
    } catch (error) {
      results.push({ ok: false, url: request.url, error: error.message || String(error) });
    }
  }
  return { results };
}

function waitForBackupDownloadWindow(windowId) {
  if (!Number.isInteger(windowId)) return Promise.resolve("missing");
  return new Promise((resolve) => {
    let settled = false;
    const finish = (reason) => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      chrome.windows.onRemoved.removeListener(onRemoved);
      resolve(reason);
    };
    const onRemoved = (closedWindowId) => {
      if (closedWindowId === windowId) finish("closed");
    };
    const timer = setTimeout(() => finish("timeout"), BACKUP_REQUEST_TTL_MS);
    chrome.windows.onRemoved.addListener(onRemoved);
    chrome.windows.get(windowId, () => {
      if (chrome.runtime.lastError) finish("missing");
    });
  });
}

function normalizeFloatingBackupHeaders(headers = {}, fallbackReferer = "") {
  const result = normalizeDownloadHeaders(headers, fallbackReferer);
  delete result.Range;
  return result;
}

async function handleSourceTraceBatchDownload(items, sendResponse, options = {}) {
  const results = [];
  let titleDownloaded = 0;
  const unique = uniqueSourceTraceItems(items);
  if (!unique.length) {
    sendResponse({ ok: false, error: "未找到可溯源的抖音视频链接", results });
    return;
  }
  let auth;
  try {
    auth = await requireSourceTraceFeatureAccess();
  } catch (error) {
    sendResponse({ ok: false, error: error.message || "当前账号未开通视频溯源。", results });
    return;
  }
  for (let index = 0; index < unique.length; index += 1) {
    const item = unique[index];
    try {
      const payload = await requestSourceTrace({ url: item.sourceTraceUrl }, { auth });
      const candidate = createSourceTraceDownloadCandidate(payload, item, index);
      if (!candidate?.url) throw new Error("溯源结果没有返回源视频下载地址");
      if (!isDownloadCandidate(candidate)) throw new Error("源视频链接不是可下载媒体资源");
      if (!shouldSkipPlayableProbe(candidate)) {
        await validateDownloadResourceSafe(candidate);
        await validatePlayableDownloadCandidate(candidate);
      }
      const id = await downloadWithHeaders(candidate, index);
      let titleId;
      let titleError = "";
      if (options.downloadTitles === true && item.videoTitle) {
        try {
          titleId = await startVideoTitleDownload(item.videoTitle, item, index);
          titleDownloaded += 1;
        } catch (error) {
          titleError = error.message || String(error);
        }
      }
      results.push({
        ok: true,
        id,
        titleId,
        titleDownloaded: Boolean(titleId),
        titleError,
        sourceTraceUrl: item.sourceTraceUrl,
        url: candidate.url,
        title: candidate.pageTitle || item.pageTitle || item.name || ""
      });
      if (index < unique.length - 1) await delay(350);
    } catch (error) {
      results.push({
        ok: false,
        sourceTraceUrl: item.sourceTraceUrl || "",
        title: item.pageTitle || item.name || "",
        error: error.message || String(error)
      });
    }
  }
  sendResponse({ ok: results.some((item) => item.ok), results, titleDownloaded });
}

function uniqueSourceTraceItems(items) {
  const result = [];
  const seen = new Set();
  for (const raw of items || []) {
    const item = normalizeSourceTraceBatchItem(raw);
    if (!item) continue;
    const key = item.awemeId || safeUrlKey(item.sourceTraceUrl);
    if (!key || seen.has(key)) continue;
    seen.add(key);
    result.push(item);
  }
  return result;
}

function normalizeSourceTraceBatchItem(raw) {
  if (!raw || typeof raw !== "object") return null;
  const sourceTraceUrl = String(raw.sourceTraceUrl || raw.traceUrl || raw.pageUrl || "").trim();
  if (!isSpecificDouyinTraceUrl(sourceTraceUrl)) return null;
  return {
    id: String(raw.id || "").trim(),
    url: String(raw.url || "").trim(),
    name: String(raw.name || raw.fileName || "").trim(),
    pageTitle: String(raw.pageTitle || "").trim(),
    pageUrl: String(raw.pageUrl || "").trim(),
    authorName: String(raw.authorName || "").trim(),
    likeCountText: String(raw.likeCountText || "").trim(),
    videoTitle: normalizeVideoTitleText(raw.videoTitle),
    awemeId: String(raw.awemeId || extractAwemeId(sourceTraceUrl)).trim(),
    cardId: String(raw.cardId || "").trim(),
    sourceTraceUrl
  };
}

function createSourceTraceDownloadCandidate(payload, item, index) {
  const selected = globalThis.MMWSourceTraceSelector?.selectPreferredMedia(payload);
  const source = selected || (isObject(payload?.source) ? payload.source : {});
  const url = String(source.download_url || source.url || payload?.download_url || "").trim();
  if (!/^https?:\/\//i.test(url)) return null;
  const extension = getExtension(url) || "mp4";
  const title = sanitizeFileName(
    payload?.title
    || item.pageTitle
    || item.name
    || item.authorName
    || (item.awemeId ? `douyin-${item.awemeId}` : `douyin-source-${index + 1}`)
  );
  const resolution = String(source.resolution || "").trim();
  const sizeText = String(source.size_text || source.size || "").trim();
  const suffix = [resolution, sizeText].filter(Boolean).join(" ");
  const name = sanitizeFileName(`${title}${suffix ? ` ${suffix}` : ""}`);
  return {
    id: `source-trace-batch-${payload?.request_id || item.awemeId || index}`,
    url,
    kind: "video",
    extension,
    mimeType: "video/mp4",
    name,
    pageTitle: title,
    pageUrl: item.sourceTraceUrl,
    source: "source-trace-batch",
    sourceTraceUrl: item.sourceTraceUrl,
    awemeId: item.awemeId,
    requestHeaders: normalizeDownloadHeaders({
      Referer: item.sourceTraceUrl,
      Origin: "https://www.douyin.com"
    }, item.sourceTraceUrl)
  };
}

function isSpecificDouyinTraceUrl(url) {
  try {
    const parsed = new URL(url);
    if (!/(^|\.)douyin\.com$/i.test(parsed.hostname)) return false;
    return /\/(video|note|jingxuan)\b/i.test(parsed.pathname) || parsed.searchParams.has("modal_id");
  } catch {
    return false;
  }
}

function extractAwemeId(value) {
  const match = String(value || "").match(/\d{8,}/);
  return match ? match[0] : "";
}

async function handleDouyinDirectDownload(item, sendResponse) {
  try {
    if (!item?.url) throw new Error("缺少下载链接");
    if (!isDownloadCandidate(item)) throw new Error("不是可下载媒体资源");
    const result = await startAixStyleDownload(item);
    sendResponse({ ok: Boolean(result?.success), ...result });
  } catch (error) {
    sendResponse({ ok: false, error: error.message || "下载启动失败" });
  }
}

async function startTitleSelector(message, sender, sendResponse) {
  const tabId = Number(message?.tabId || sender.tab?.id);
  if (!Number.isInteger(tabId) || tabId < 0) {
    sendResponse({ ok: false, error: "当前页面不可用" });
    return;
  }
  try {
    await chrome.scripting.executeScript({ target: { tabId }, files: ["selector.js"] });
    sendResponse({ ok: true });
  } catch (error) {
    sendResponse({ ok: false, error: error.message || "打开规则选择失败" });
  }
}

function addResource(tabId, item) {
  if (!item?.url || isIgnoredScheme(item.url)) return;
  const fingerprint = normalizeFingerprint(item.url);
  item.fingerprint = fingerprint;
  item.id = makeId(fingerprint, item.kind || item.source || "resource");
  const resources = tabResources.get(tabId) || [];
  const seen = tabFingerprints.get(tabId) || new Set();
  if (seen.has(fingerprint)) {
    const existing = resources.find((resource) => resource.fingerprint === fingerprint);
    if (existing) {
      const previousMeta = `${existing.authorName || ""}|${existing.likeCountText || ""}|${existing.videoTitle || ""}`;
      mergeResource(existing, item);
      const nextMeta = `${existing.authorName || ""}|${existing.likeCountText || ""}|${existing.videoTitle || ""}`;
      if (nextMeta !== previousMeta) {
        chrome.tabs.sendMessage(tabId, { type: "FLOATING_RESOURCES_CHANGED" }).catch(() => {});
      }
    }
    return;
  }
  seen.add(fingerprint);
  tabFingerprints.set(tabId, seen);
  resources.unshift(item);
  if (resources.length > MAX_ITEMS_PER_TAB) resources.length = MAX_ITEMS_PER_TAB;
  tabResources.set(tabId, resources);
  updateVideoBadge(tabId, resources.filter((resource) => VIDEO_KINDS.has(resource.kind)).length);
  chrome.tabs.sendMessage(tabId, { type: "FLOATING_RESOURCES_CHANGED" }).catch(() => {});
}

function getResources(tabId) {
  return [...(tabResources.get(tabId) || [])].sort((a, b) => (b.time || 0) - (a.time || 0));
}

function mergeResource(target, source) {
  if ((source.size || 0) > (target.size || 0)) target.size = source.size;
  if ((source.width || 0) > (target.width || 0)) target.width = source.width;
  if ((source.height || 0) > (target.height || 0)) target.height = source.height;
  if (source.mimeType && !target.mimeType) target.mimeType = source.mimeType;
  if (source.extension && !target.extension) target.extension = source.extension;
  if (source.fileName && !target.fileName) target.fileName = source.fileName;
  if (source.kind && target.kind !== source.kind) target.kind = source.kind;
  if (source.cardId && !target.cardId) target.cardId = source.cardId;
  if (source.authorName && !target.authorName) target.authorName = source.authorName;
  if (source.likeCountText && !target.likeCountText) target.likeCountText = source.likeCountText;
  if (source.videoTitle && !target.videoTitle) target.videoTitle = source.videoTitle;
  if (source.awemeId && !target.awemeId) target.awemeId = source.awemeId;
  if (source.sourceTraceUrl && !target.sourceTraceUrl) target.sourceTraceUrl = source.sourceTraceUrl;
  if (source.requestHeaders) target.requestHeaders = { ...(target.requestHeaders || {}), ...source.requestHeaders };
  target.time = Math.max(target.time || 0, source.time || 0);
}

function normalizeCardMeta(value) {
  const meta = value && typeof value === "object" ? value : {};
  return {
    cardId: String(meta.cardId || "").trim(),
    authorName: String(meta.authorName || "").trim().slice(0, 32),
    likeCountText: String(meta.likeCountText || "").trim().slice(0, 24),
    videoTitle: normalizeVideoTitleText(meta.videoTitle),
    awemeId: String(meta.awemeId || "").trim(),
    sourceTraceUrl: String(meta.sourceTraceUrl || "").trim(),
    resourceKey: normalizeResourceMatchKey(meta.resourceKey || "")
  };
}

function getRecentCardMeta(tabId, pageUrl) {
  const meta = tabActiveCardMeta.get(tabId);
  if (!meta || Date.now() - (meta.time || 0) > CARD_META_TTL_MS) return {};
  if (meta.pageUrl && pageUrl && safeUrlKey(meta.pageUrl) !== safeUrlKey(pageUrl)) return {};
  return {
    cardId: meta.cardId || "",
    authorName: meta.authorName || "",
    likeCountText: meta.likeCountText || "",
    videoTitle: meta.videoTitle || "",
    awemeId: meta.awemeId || "",
    sourceTraceUrl: meta.sourceTraceUrl || "",
    resourceKey: meta.resourceKey || ""
  };
}

function applyCardMetaToRecentResources(tabId, meta) {
  const resources = tabResources.get(tabId) || [];
  const cutoff = Date.now() - CARD_META_PATCH_WINDOW_MS;
  const canPatch = (resource) =>
    VIDEO_KINDS.has(resource.kind)
    && (
      (!resource.authorName && meta.authorName)
      || (!resource.likeCountText && meta.likeCountText)
      || (!resource.videoTitle && meta.videoTitle)
      || (!resource.awemeId && meta.awemeId)
      || (!resource.sourceTraceUrl && meta.sourceTraceUrl)
    )
    && (resource.time || 0) >= cutoff
    && (!resource.authorName || !meta.authorName || resource.authorName === meta.authorName);
  const target = resources.find((resource) =>
    canPatch(resource)
      && meta.resourceKey
      && normalizeResourceMatchKey(resource.url) === meta.resourceKey
  ) || resources.find((resource) =>
    canPatch(resource) && resource.cardId && resource.cardId === meta.cardId
  );
  if (!target) return false;
  mergeResource(target, { ...meta, time: Date.now() });
  return true;
}

function safeUrlKey(url) {
  try {
    const parsed = new URL(url);
    return `${parsed.origin}${parsed.pathname}`;
  } catch {
    return String(url || "").split("?")[0];
  }
}

async function downloadWithHeaders(item, index) {
  const ruleId = DOWNLOAD_RULE_ID_BASE + index;
  const rule = buildDownloadHeaderRule(item, ruleId);
  let ruleApplied = false;
  if (rule) {
    try {
      await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId], addRules: [rule] });
      ruleApplied = true;
    } catch {
      ruleApplied = false;
    }
  }
  try {
    return await startChromeDownload(item);
  } finally {
    if (ruleApplied) {
      setTimeout(() => {
        chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId] }).catch(() => {});
      }, DOWNLOAD_RULE_TTL_MS);
    }
  }
}

function startChromeDownload(item) {
  return new Promise((resolve, reject) => {
    let settled = false;
    const timer = setTimeout(() => {
      settled = true;
      reject(new Error("\u4e0b\u8f7d\u542f\u52a8\u8d85\u65f6\uff0c\u5df2\u5c1d\u8bd5\u4e0b\u4e00\u4e2a\u5019\u9009\u94fe\u63a5"));
    }, CHROME_DOWNLOAD_START_TIMEOUT_MS);
    try {
      chrome.downloads.download({
        url: item.url,
        filename: buildDownloadName(item),
        conflictAction: "uniquify",
        saveAs: false
      }, (downloadId) => {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        if (chrome.runtime.lastError || downloadId === undefined) {
          reject(new Error(chrome.runtime.lastError?.message || "下载启动失败"));
          return;
        }
        resolve(downloadId);
      });
    } catch (error) {
      if (!settled) {
        settled = true;
        clearTimeout(timer);
        reject(error);
      }
    }
  });
}

function startAixStyleDownload(item) {
  return new Promise((resolve) => {
    chrome.downloads.download({
      url: item.url,
      filename: buildAixStyleDownloadName(item),
      conflictAction: "uniquify",
      saveAs: false
    }, (downloadId) => {
      if (chrome.runtime.lastError || !downloadId) {
        resolve({ success: false, error: chrome.runtime.lastError?.message || "下载启动失败" });
        return;
      }
      let settled = false;
      const finish = (payload) => {
        if (settled) return;
        settled = true;
        chrome.downloads.onChanged.removeListener(onChanged);
        resolve(payload);
      };
      const onChanged = (delta) => {
        if (delta.id !== downloadId) return;
        if (delta.state?.current === "interrupted") {
          finish({ success: false, id: downloadId, error: delta.error?.current || "下载中断" });
          return;
        }
        if (delta.state?.current === "in_progress") {
          finish({ success: true, id: downloadId });
        }
      };
      chrome.downloads.onChanged.addListener(onChanged);
      setTimeout(() => finish({ success: true, id: downloadId }), 1000);
    });
  });
}

function createBackupDownload(message, sendResponse) {
  if (!message.url) {
    sendResponse({ ok: false, error: "缺少下载链接" });
    return;
  }
  const request = makeBackupDownloadRequest({
    url: message.url,
    referer: message.referer || message.pageUrl || "",
    suggestedTitle: message.suggestedTitle || message.name || "",
    name: message.name || "",
    extension: message.extension || "",
    kind: message.kind || "video",
    pageTitle: message.pageTitle || "",
    pageUrl: message.pageUrl || "",
    requestHeaders: message.requestHeaders || fallbackRequestHeaders(message.referer || message.pageUrl)
  });
  openBackupDownloadWindow(request)
    .then((result) => sendResponse({ ok: true, ...result }))
    .catch((error) => sendResponse({ ok: false, error: error.message || "打开下载窗口失败" }));
}

function openBackupDownloadForItem(item) {
  const request = makeBackupDownloadRequest({
    url: item.url,
    referer: item.requestHeaders?.Referer || item.requestHeaders?.referer || item.pageUrl || "https://www.douyin.com/",
    suggestedTitle: item.pageTitle || item.name || "",
    name: item.name || "",
    extension: item.extension || "",
    kind: item.kind || "video",
    pageTitle: item.pageTitle || "",
    pageUrl: item.pageUrl || "",
    requestHeaders: item.requestHeaders || fallbackRequestHeaders(item.pageUrl)
  });
  return openBackupDownloadWindow(request);
}

function makeBackupDownloadRequest(input) {
  const url = String(input.url || "").trim();
  const requestHeaders = normalizeDownloadHeaders(input.requestHeaders, input.referer || input.pageUrl);
  return {
    url,
    referer: requestHeaders.Referer || input.referer || "",
    suggestedTitle: input.suggestedTitle || "",
    name: input.name || "",
    extension: input.extension || getExtension(url) || defaultExtensionForKind(input.kind),
    kind: input.kind || "video",
    pageTitle: input.pageTitle || "",
    pageUrl: input.pageUrl || "",
    requestHeaders,
    createdAt: Date.now()
  };
}

async function openBackupDownloadWindow(request, options = {}) {
  const isBatch = Array.isArray(request.items) && request.items.length;
  if (!isBatch) {
    if (!/^https?:\/\//i.test(request.url || "")) throw new Error("下载链接不可用");
    if (isPageLikeResource(getExtension(request.url), "") || isKnownPageUrl(request.url)) {
      throw new Error("链接返回的是网页，不是视频文件");
    }
  }
  cleanupOldBackupRequests().catch(() => {});
  const requestId = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  request.requestId = requestId;
  request.createdAt = request.createdAt || Date.now();
  if (isBatch) {
    request.items = request.items
      .filter((item) => /^https?:\/\//i.test(item?.url || "") && !isPageLikeResource(getExtension(item.url), "") && !isKnownPageUrl(item.url))
      .map((item, index) => ({
        ...item,
        requestId: `${requestId}_${index}`,
        ruleId: getDownloaderRuleId(`${requestId}_${index}`),
        createdAt: Date.now()
      }));
    if (!request.items.length) throw new Error("下载链接不可用");
  } else {
    request.ruleId = getDownloaderRuleId(requestId);
  }
  const pageUrl = chrome.runtime.getURL(`downloader.html?requestId=${encodeURIComponent(requestId)}`);
  await chrome.storage.local.set({ [BACKUP_REQUEST_PREFIX + requestId]: request });
  const createdWindow = await new Promise((resolve, reject) => {
    const createOptions = { url: pageUrl, type: "popup", focused: options.focused !== false };
    if (options.minimized) {
      createOptions.state = "minimized";
    } else {
      createOptions.width = 520;
      createOptions.height = 420;
    }
    chrome.windows.create(createOptions, (win) => {
      if (chrome.runtime.lastError || !win) reject(new Error(chrome.runtime.lastError?.message || "打开下载窗口失败"));
      else resolve(win);
    });
  });
  return { requestId, windowId: createdWindow.id };
}

function getBackupDownloadRequest(message, sendResponse) {
  const requestId = message.requestId || "";
  if (!requestId) {
    sendResponse({ ok: false, error: "缺少请求 ID" });
    return;
  }
  chrome.storage.local.get(BACKUP_REQUEST_PREFIX + requestId, (data) => {
    const request = data[BACKUP_REQUEST_PREFIX + requestId];
    if (!request) {
      sendResponse({ ok: false, error: "下载请求不存在或已失效" });
      return;
    }
    if (Date.now() - Number(request.createdAt || 0) > BACKUP_REQUEST_TTL_MS) {
      chrome.storage.local.remove(BACKUP_REQUEST_PREFIX + requestId);
      sendResponse({ ok: false, error: "下载请求已过期，请重新点击下载" });
      return;
    }
    sendResponse({ ok: true, request });
  });
}

function clearBackupDownloadRequest(message, sendResponse) {
  const requestId = message.requestId || "";
  if (!requestId) {
    sendResponse({ ok: true });
    return;
  }
  chrome.storage.local.remove(BACKUP_REQUEST_PREFIX + requestId, () => sendResponse({ ok: true }));
}

async function cleanupOldBackupRequests() {
  const all = await chrome.storage.local.get(null);
  const removeKeys = Object.keys(all).filter((key) => {
    if (!key.startsWith(BACKUP_REQUEST_PREFIX)) return false;
    const request = all[key];
    return Date.now() - Number(request?.createdAt || 0) > BACKUP_REQUEST_TTL_MS;
  });
  if (removeKeys.length) await chrome.storage.local.remove(removeKeys);
}

function updateVideoBadge(tabId, count) {
  chrome.action.setBadgeText({ tabId, text: count > 0 ? String(Math.min(count, 99)) : "" });
  chrome.action.setBadgeBackgroundColor({ tabId, color: "#e53935" });
  chrome.storage.local.set({ [`videoCount_${tabId}`]: count });
}

function openUrls(rawUrls, sendResponse) {
  const urls = Array.isArray(rawUrls) ? rawUrls.filter(Boolean) : [];
  if (!urls.length) {
    sendResponse({ ok: false, error: "缺少跳转链接" });
    return;
  }
  Promise.all(urls.map((url, index) => chrome.tabs.create({ url, active: index === 0 })))
    .then((tabs) => sendResponse({ ok: true, tabIds: tabs.map((tab) => tab.id).filter(Boolean) }))
    .catch((error) => sendResponse({ ok: false, error: error.message || "跳转失败" }));
}

async function handleNewPageTaskCompat(body, sender, sendResponse) {
  try {
    const action = String(body.action || "").trim();
    const taskName = String(body.name || "").trim();

    if (action === "create-task") {
      const openUrl = String(body.openUrl || "").trim();
      if (!openUrl) {
        sendResponse({ ok: false, error: "缺少跳转地址" });
        return;
      }
      const tab = await createSiblingTab(openUrl, sender);
      if (!tab?.id) throw new Error("任务页面打开失败");
      const tasks = await getPendingPageTasks();
      tasks.push({
        name: taskName,
        tabId: tab.id,
        data: body.data ?? null,
        createdAt: Date.now()
      });
      await chrome.storage.local.set({ [PENDING_PAGE_TASKS_KEY]: cleanupExpiredPageTasks(tasks) });
      sendResponse({ ok: true, tabId: tab.id });
      return;
    }

    if (action === "get-task") {
      const tasks = cleanupExpiredPageTasks(await getPendingPageTasks());
      const currentTabId = sender.tab?.id || 0;
      const matched = tasks.filter((task) => task.tabId === currentTabId && task.name === taskName);
      const remained = tasks.filter((task) => !(task.tabId === currentTabId && task.name === taskName));
      await chrome.storage.local.set({ [PENDING_PAGE_TASKS_KEY]: remained });
      sendResponse(matched.map((task) => task.data));
      return;
    }

    sendResponse({ ok: true });
  } catch (error) {
    sendResponse({ ok: false, error: error.message || "任务打开失败" });
  }
}

async function createSiblingTab(url, sender) {
  const senderIndex = Number.isInteger(sender?.tab?.index) ? sender.tab.index + 1 : undefined;
  const senderWindowId = Number.isInteger(sender?.tab?.windowId) ? sender.tab.windowId : undefined;
  const createOptions = { url, active: true };
  if (typeof senderIndex === "number") createOptions.index = senderIndex;
  if (typeof senderWindowId === "number") createOptions.windowId = senderWindowId;
  return chrome.tabs.create(createOptions);
}

async function getPendingPageTasks() {
  const values = await chrome.storage.local.get([PENDING_PAGE_TASKS_KEY]);
  return Array.isArray(values[PENDING_PAGE_TASKS_KEY]) ? values[PENDING_PAGE_TASKS_KEY] : [];
}

function cleanupExpiredPageTasks(tasks) {
  const now = Date.now();
  return tasks.filter((task) => task && now - (Number(task.createdAt) || 0) <= 10 * 60 * 1000);
}

async function clearPendingPageTasksForTab(tabId) {
  const tasks = cleanupExpiredPageTasks(await getPendingPageTasks());
  const remained = tasks.filter((task) => task.tabId !== tabId);
  if (remained.length !== tasks.length) {
    await chrome.storage.local.set({ [PENDING_PAGE_TASKS_KEY]: remained });
  }
}

function getMessageUrls(message) {
  const urls = [];
  if (Array.isArray(message.urls)) urls.push(...message.urls);
  if (message.url) urls.push(message.url);
  return [...new Set(urls.map((url) => String(url || "").trim()).filter(Boolean))];
}

function fetchVideo(message, sendResponse) {
  const url = message.url;
  if (!url) {
    sendResponse({ ok: false, error: "缺少下载链接" });
    return;
  }
  const referer = message.referer || "";
  const suggestedTitle = message.suggestedTitle || "";
  let filename = suggestedTitle ? `${sanitizeFileName(suggestedTitle)}.mp4` : getFileName(url, "mp4");
  Promise.resolve()
    .then(() => (referer ? setRefererRule(REFERER_RULE_ID_MAIN, url, referer) : null))
    .catch(() => {})
    .then(() => fetch(url, { method: "HEAD", credentials: "omit", headers: referer ? { Referer: referer } : {} }))
    .then((response) => {
      const disposition = response.headers.get("content-disposition");
      if (disposition && !suggestedTitle) {
        const match = disposition.match(/filename\*?=["']?(?:UTF-8'')?([^"';\r\n]+)/i);
        if (match) filename = `${sanitizeFileName(decodeURIComponent(match[1].trim()))}.mp4`;
      }
    })
    .catch(() => {})
    .finally(() => {
      setTimeout(() => {
        chrome.downloads.download({ url, filename, saveAs: false, conflictAction: "uniquify" }, (downloadId) => {
          if (chrome.runtime.lastError || downloadId === undefined) {
            sendResponse({ ok: false, error: chrome.runtime.lastError?.message || "下载启动失败" });
            return;
          }
          sendResponse({ ok: true, downloadId });
        });
      }, 80);
    });
}

function fetchVideoSafe(message, sender, sendResponse) {
  Promise.resolve()
    .then(async () => {
      const url = String(message.url || "").trim();
      if (!/^https?:\/\//i.test(url)) throw new Error("缺少下载链接");
      const referer = String(message.referer || message.pageUrl || "https://www.douyin.com/").trim();
      const requestHeaders = normalizeFloatingBackupHeaders({
        ...(message.requestHeaders || {}),
        Referer: referer,
        Origin: "https://www.douyin.com"
      }, referer);
      const request = makeBackupDownloadRequest({
        url,
        referer,
        suggestedTitle: message.suggestedTitle || message.pageTitle || message.name || "",
        name: message.name || "",
        extension: message.extension || "mp4",
        kind: message.kind || "video",
        pageTitle: message.pageTitle || message.suggestedTitle || "",
        pageUrl: message.pageUrl || referer,
        requestHeaders
      });
      const requestId = `safe_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
      request.requestId = requestId;
      request.ruleId = getDownloaderRuleId(requestId);
      request.createdAt = Date.now();
      if (Number.isInteger(sender?.tab?.id)) request.tabId = sender.tab.id;
      recordAdminDebugEvent("safe-video-download", {
        host: getHost(url),
        title: request.suggestedTitle || request.name || "",
        headers: Object.keys(requestHeaders),
        transport: "offscreen"
      });
      const result = await startOffscreenVideoDownload(request);
      sendResponse({ ok: true, offscreen: true, requestId, ...result });
    })
    .catch((error) => {
      sendResponse({ ok: false, error: error.message || "下载启动失败" });
    });
}

async function startOffscreenVideoDownload(request) {
  await ensureOffscreenDocument();
  const response = await sendExtensionMessage({
    type: "OFFSCREEN_FETCH_VIDEO",
    request
  });
  if (!response?.ok) throw new Error(response?.error || "隐藏下载启动失败");
  return response;
}

async function ensureOffscreenDocument() {
  if (!chrome.offscreen?.createDocument) {
    throw new Error("当前浏览器不支持隐藏下载，请升级 Chrome 后重试");
  }
  if (await hasOffscreenDocument()) return;
  if (!offscreenDocumentPromise) {
    offscreenDocumentPromise = chrome.offscreen.createDocument({
      url: OFFSCREEN_DOCUMENT_URL,
      reasons: ["BLOBS"],
      justification: "Create blob URLs for hidden Douyin video downloads without opening a tab or popup."
    }).catch((error) => {
      if (/single offscreen document/i.test(error?.message || "")) return null;
      throw error;
    }).finally(() => {
      offscreenDocumentPromise = null;
    });
  }
  await offscreenDocumentPromise;
}

async function hasOffscreenDocument() {
  if (chrome.offscreen?.hasDocument) {
    return chrome.offscreen.hasDocument();
  }
  if (chrome.runtime.getContexts) {
    const contexts = await chrome.runtime.getContexts({
      contextTypes: ["OFFSCREEN_DOCUMENT"],
      documentUrls: [chrome.runtime.getURL(OFFSCREEN_DOCUMENT_URL)]
    });
    return contexts.length > 0;
  }
  if (globalThis.clients?.matchAll) {
    const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_URL);
    const matchedClients = await globalThis.clients.matchAll();
    return matchedClients.some((client) => client.url === offscreenUrl);
  }
  return false;
}

function sendExtensionMessage(message) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(message, (response) => {
      const error = chrome.runtime.lastError;
      if (error) {
        reject(new Error(error.message || "消息发送失败"));
        return;
      }
      resolve(response);
    });
  });
}

function saveOffscreenBlobUrl(message, sendResponse) {
  const url = String(message.url || "").trim();
  if (!url.startsWith("blob:")) {
    sendResponse({ ok: false, error: "缺少 blob 下载地址" });
    return;
  }
  const filename = normalizeAixDownloadFilename(message.filename || `douyin-video-${Date.now()}.mp4`, "douyin-video.mp4");
  chrome.downloads.download({
    url,
    filename,
    saveAs: false,
    conflictAction: "uniquify"
  }, (downloadId) => {
    if (chrome.runtime.lastError || downloadId === undefined) {
      sendResponse({ ok: false, error: chrome.runtime.lastError?.message || "保存失败" });
      return;
    }
    waitForDownloadStart(downloadId)
      .then((state) => sendResponse({ ok: true, downloadId, state }))
      .catch((error) => sendResponse({ ok: false, downloadId, error: error.message || "下载中断" }));
  });
}

function waitForDownloadStart(downloadId) {
  return new Promise((resolve, reject) => {
    let settled = false;
    const finish = (handler) => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      chrome.downloads.onChanged.removeListener(listener);
      handler();
    };
    const listener = (delta) => {
      if (delta.id !== downloadId) return;
      if (delta.state?.current === "interrupted") {
        finish(() => reject(new Error(delta.error?.current || "浏览器下载中断")));
      }
      if (delta.state?.current === "in_progress" || delta.state?.current === "complete") {
        finish(() => resolve(delta.state.current));
      }
    };
    const timer = setTimeout(() => finish(() => resolve("started")), 6000);
    chrome.downloads.onChanged.addListener(listener);
  });
}

function handleOffscreenVideoResult(message, sendResponse) {
  notifyOffscreenVideoResult(message);
  recordAdminDebugEvent("offscreen-video-result", {
    ok: message.ok === true,
    requestId: message.requestId || "",
    host: getHost(message.url || ""),
    filename: message.filename || "",
    size: Number(message.size || 0),
    downloadId: message.downloadId,
    error: message.error || ""
  }, message.ok === true ? "info" : "warn");
  sendResponse({ ok: true });
}

function notifyOffscreenVideoResult(message) {
  if (message.ok === true) return;
  const tabId = Number(message.tabId);
  if (!Number.isInteger(tabId) || tabId < 0) return;
  chrome.tabs.sendMessage(tabId, {
    type: "VD_SAFE_VIDEO_RESULT",
    ok: false,
    requestId: message.requestId || "",
    filename: message.filename || "",
    error: message.error || "下载失败"
  }).catch(() => {});
}

function setRefererRule(ruleId, targetUrl, referer, resourceTypes) {
  if (!chrome.declarativeNetRequest?.updateSessionRules) {
    return Promise.reject(new Error("declarativeNetRequest API 不可用"));
  }
  if (!targetUrl) return Promise.reject(new Error("缺少目标链接"));
  const rule = {
    id: ruleId,
    priority: 1,
    action: {
      type: "modifyHeaders",
      requestHeaders: [{ header: "Referer", operation: "set", value: referer }]
    },
    condition: { urlFilter: targetUrl.split("?")[0] }
  };
  if (Array.isArray(resourceTypes) && resourceTypes.length) rule.condition.resourceTypes = resourceTypes;
  return chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId], addRules: [rule] });
}

function setDownloadHeadersRule(ruleId, targetUrl, headers) {
  if (!chrome.declarativeNetRequest?.updateSessionRules) {
    return Promise.reject(new Error("declarativeNetRequest API 不可用"));
  }
  if (!targetUrl) return Promise.reject(new Error("缺少目标链接"));
  const requestHeaders = Object.entries(normalizeDownloadHeaders(headers, "https://www.douyin.com/"))
    .filter(([, value]) => value)
    .map(([header, value]) => ({ header, operation: "set", value: String(value) }));
  if (!requestHeaders.length) return clearRule(ruleId);
  const rule = {
    id: ruleId,
    priority: ruleId,
    action: { type: "modifyHeaders", requestHeaders },
    condition: {
      ...getDnrUrlCondition(targetUrl),
      resourceTypes: ["xmlhttprequest", "media", "other"]
    }
  };
  return chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId], addRules: [rule] })
    .catch((error) => {
      const reducedHeaders = requestHeaders.filter((item) => item.header !== "User-Agent");
      if (reducedHeaders.length === requestHeaders.length) throw error;
      return chrome.declarativeNetRequest.updateSessionRules({
        removeRuleIds: [ruleId],
        addRules: [{ ...rule, action: { type: "modifyHeaders", requestHeaders: reducedHeaders } }]
      });
    });
}

function clearRule(ruleId) {
  if (!chrome.declarativeNetRequest?.updateSessionRules) return Promise.resolve();
  return chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId] }).catch(() => {});
}

function getDownloaderRuleId(value) {
  const numeric = Number(value);
  if (Number.isInteger(numeric) && numeric >= DOWNLOADER_RULE_ID_BASE && numeric < DOWNLOADER_RULE_ID_BASE + DOWNLOADER_RULE_ID_RANGE) {
    return numeric;
  }
  const text = String(value || "");
  let hash = 0;
  for (let index = 0; index < text.length; index += 1) {
    hash = (hash * 31 + text.charCodeAt(index)) >>> 0;
  }
  return DOWNLOADER_RULE_ID_BASE + (hash % DOWNLOADER_RULE_ID_RANGE);
}

function normalizeDownloadHeaders(headers = {}, fallbackReferer = "") {
  const result = {};
  const allowed = new Map([
    ["referer", "Referer"],
    ["origin", "Origin"],
    ["cookie", "Cookie"],
    ["authorization", "Authorization"],
    ["user-agent", "User-Agent"],
    ["accept", "Accept"],
    ["accept-language", "Accept-Language"],
    ["range", "Range"]
  ]);
  for (const [rawName, rawValue] of Object.entries(headers || {})) {
    const name = allowed.get(String(rawName || "").toLowerCase());
    const value = String(rawValue || "").trim();
    if (name && value) result[name] = value;
  }
  if (fallbackReferer && !result.Referer) result.Referer = fallbackReferer;
  return result;
}

function getDnrUrlCondition(targetUrl) {
  try {
    const parsed = new URL(stripHash(targetUrl));
    const withoutHash = parsed.href;
    const regexFilter = `^${escapeRegex(withoutHash)}$`;
    if (regexFilter.length < 1900) return { regexFilter };
    return { urlFilter: parsed.hostname };
  } catch {
    return { urlFilter: String(targetUrl || "").split("?")[0] };
  }
}

function classifyResource(url, headersOrMime, requestType) {
  const headerInfo = Array.isArray(headersOrMime) ? parseHeaders(headersOrMime) : {};
  const mimeType = normalizeMime(typeof headersOrMime === "string" ? headersOrMime : headerInfo.mimeType);
  const extension = getExtension(url) || extensionFromMime(mimeType);
  if (isPageLikeResource(extension, mimeType)) return null;
  const kind = getKind(extension, mimeType, requestType);
  if (!kind) return null;
  return {
    name: getFileName(url, extension),
    extension: extension || "",
    kind,
    mimeType: mimeType || "",
    size: headerInfo.size || 0,
    isPlaylist: PLAYLIST_EXTENSIONS.has(extension || "")
  };
}

function getKind(extension, mimeType, requestType) {
  if (requestType === "image" || mimeType.startsWith("image/")) return "image";
  if (requestType === "video") return "video";
  if (requestType === "audio") return "audio";
  if (requestType === "media" || mimeType.startsWith("video/") || mimeType.startsWith("audio/")) {
    return mimeType.startsWith("audio/") ? "audio" : "video";
  }
  if (mimeType.includes("mpegurl") || mimeType.includes("dash+xml")) return "playlist";
  if (extension && PLAYLIST_EXTENSIONS.has(extension)) return "playlist";
  if (extension && MEDIA_EXTENSIONS.has(extension)) {
    if (["jpg", "jpeg", "png", "gif", "webp", "avif", "bmp", "svg", "apng"].includes(extension)) return "image";
    if (["mp3", "aac", "flac", "m4a", "oga", "ogg", "wav"].includes(extension)) return "audio";
    return "video";
  }
  return "";
}

function parseHeaders(headers = []) {
  const result = { mimeType: "", size: 0 };
  for (const header of headers) {
    const name = (header.name || "").toLowerCase();
    const value = header.value || "";
    if (name === "content-type") result.mimeType = value.split(";")[0].trim().toLowerCase();
    if (name === "content-length") result.size = Math.max(result.size, Number(value) || 0);
    if (name === "content-range") {
      const match = value.match(/\/(\d+)$/);
      if (match) result.size = Math.max(result.size, Number(match[1]) || 0);
    }
  }
  return result;
}

function pickRequestHeaders(headers = []) {
  const result = {};
  const allowed = new Map([
    ["referer", "Referer"],
    ["origin", "Origin"],
    ["cookie", "Cookie"],
    ["authorization", "Authorization"],
    ["user-agent", "User-Agent"],
    ["accept", "Accept"],
    ["accept-language", "Accept-Language"],
    ["range", "Range"]
  ]);
  for (const header of headers) {
    const name = (header.name || "").toLowerCase();
    const value = String(header.value || "").trim();
    const canonical = allowed.get(name);
    if (canonical && value) result[canonical] = value;
  }
  return Object.keys(result).length ? result : null;
}

function fallbackRequestHeaders(pageUrl) {
  return /^https?:\/\//i.test(pageUrl || "") ? { Referer: pageUrl } : null;
}

function buildDownloadHeaderRule(item, ruleId) {
  const headers = {
    ...(item.requestHeaders || {}),
    ...(item.pageUrl && !item.requestHeaders?.Referer ? { Referer: item.pageUrl } : {})
  };
  const requestHeaders = Object.entries(headers)
    .filter(([, value]) => value)
    .map(([header, value]) => ({ header, operation: "set", value: String(value) }));
  if (!requestHeaders.length || !chrome.declarativeNetRequest?.updateSessionRules) return null;
  const requestUrl = stripHash(item.url);
  const regexFilter = `^${escapeRegex(requestUrl)}$`;
  return {
    id: ruleId,
    priority: ruleId,
    action: { type: "modifyHeaders", requestHeaders },
    condition: {
      ...(regexFilter.length < 1900 ? { regexFilter } : { urlFilter: new URL(requestUrl).host }),
      resourceTypes: downloadResourceTypes()
    }
  };
}

function downloadResourceTypes() {
  if (chrome.declarativeNetRequest?.ResourceType) return Object.values(chrome.declarativeNetRequest.ResourceType);
  return ["main_frame", "sub_frame", "xmlhttprequest", "media", "other"];
}

function uniqueItems(items) {
  const result = [];
  const seen = new Set();
  for (const item of items) {
    const key = item?.fingerprint || (item?.url ? normalizeFingerprint(item.url) : "");
    if (!key || seen.has(key)) continue;
    seen.add(key);
    result.push(item);
  }
  return result;
}

function isDownloadCandidate(item) {
  if (!item?.url || isIgnoredScheme(item.url)) return false;
  const extension = item.extension || getExtension(item.url);
  const nameExtension = getExtension(item.name || "");
  const mimeType = normalizeMime(item.mimeType || "");
  if (isPageLikeResource(extension, mimeType) || isPageLikeResource(nameExtension, mimeType) || isKnownPageUrl(item.url)) return false;
  if (item.kind === "image") return mimeType.startsWith("image/") || isImageExtension(extension);
  if (["video", "playlist", "audio"].includes(item.kind)) {
    return isLikelyMediaUrl(item.url, extension, mimeType, item.kind);
  }
  return Boolean(extension && MEDIA_EXTENSIONS.has(extension.toLowerCase()));
}

function shouldUseBackupDownloader(item) {
  if (!item?.url || item.kind === "image" || item.kind === "playlist") return false;
  if (isDouyinDirectDownloadUrl(item.url)) return false;
  return ["video", "audio"].includes(item.kind) && isTrustedMediaHost(item.url);
}

function isDouyinDirectDownloadUrl(url) {
  try {
    const parsed = new URL(url);
    const host = parsed.hostname.toLowerCase();
    if (/(^|\.)douyin\.com$/i.test(host) && /\/aweme\/v1\/play\//i.test(parsed.pathname)) return true;
    if (/(^|\.)(douyinvod|bytev|365yg)\.com$/i.test(host)) return true;
  } catch {}
  return false;
}

function shouldSkipPlayableProbe(item) {
  return Boolean(
    item
    && ["video", "audio"].includes(item.kind)
    && isDouyinDirectDownloadUrl(item.url)
  );
}


async function probeResource(url) {
  const response = await fetchWithTimeout(
    url,
    { method: "HEAD", credentials: "include", cache: "no-store" },
    PROBE_HEAD_TIMEOUT_MS,
    "\u94fe\u63a5\u63a2\u6d4b\u8d85\u65f6"
  );
  const mimeType = normalizeMime(response.headers.get("content-type") || "");
  const size = Number(response.headers.get("content-length") || 0);
  return {
    url: response.url || url,
    extension: getExtension(response.url || url) || extensionFromMime(mimeType),
    mimeType,
    size
  };
}

async function validateDownloadResourceSafe(item) {
  if (!/^https?:\/\//i.test(item?.url || "")) return;
  const check = await probeResource(item.url).catch(() => null);
  if (!check) return;
  if (isPageLikeResource(check.extension, check.mimeType) || isKnownPageUrl(check.url)) {
    throw new Error("\u94fe\u63a5\u8fd4\u56de\u7684\u662f\u7f51\u9875\uff0c\u4e0d\u662f\u89c6\u9891\u6587\u4ef6");
  }
  if (!item.mimeType && check.mimeType) item.mimeType = check.mimeType;
  if (!item.extension && check.extension) item.extension = check.extension;
  if (!item.size && check.size) item.size = check.size;
  if (check.url && check.url !== item.url && !isKnownPageUrl(check.url)) item.url = check.url;
}

async function validatePlayableDownloadCandidate(item) {
  if (!item || item.kind === "image" || item.kind === "playlist") return;
  const extension = String(item.extension || getExtension(item.url) || "").toLowerCase();
  if (["m3u8", "mpd"].includes(extension)) return;
  const sample = await fetchResourceSample(item, { rangeHeader: `bytes=0-${PROBE_SAMPLE_BYTES - 1}` });
  const sampleMimeType = normalizeMime(sample.mimeType || item.mimeType || "");
  if (!isPlayableMediaSample(sample.bytes, sample.mimeType || item.mimeType, extension)) {
    throw new Error("当前资源不是可播放视频，已跳过");
  }
  const trackInfo = await probeMediaTrackInfo(item, sample, extension);
  if (sampleMimeType.startsWith("audio/") || (trackInfo.audio && !trackInfo.video)) {
    throw new Error("当前资源只有声音没有画面，已自动跳过");
  }
  if (trackInfo.video && !trackInfo.audio) {
    throw new Error("当前资源只有画面没有声音，已自动跳过");
  }
}

async function probeMediaTrackInfo(item, sample, extension) {
  if (!isMp4LikeResource(sample.bytes, sample.mimeType || item.mimeType, extension)) {
    return { audio: false, video: false };
  }
  let trackInfo = readMp4TrackInfo(sample.bytes);
  if (trackInfo.audio || trackInfo.video) {
    return trackInfo;
  }
  const tailSample = await fetchResourceSample(item, { rangeHeader: `bytes=-${PROBE_SAMPLE_BYTES}` }).catch(() => null);
  if (!tailSample?.bytes?.length) return trackInfo;
  const tailInfo = readMp4TrackInfo(tailSample.bytes);
  return {
    audio: trackInfo.audio || tailInfo.audio,
    video: trackInfo.video || tailInfo.video
  };
}

async function fetchResourceSample(item, options = {}) {
  const rangeHeader = String(options.rangeHeader || "").trim() || `bytes=0-${PROBE_SAMPLE_BYTES - 1}`;
  const maxBytes = Math.max(4096, Number(options.maxBytes) || PROBE_SAMPLE_BYTES);
  const ruleId = getDownloaderRuleId(`probe_${Date.now()}_${Math.random().toString(36).slice(2)}`);
  const headers = normalizeDownloadHeaders({
    ...(item.requestHeaders || {}),
    Range: rangeHeader
  }, item.requestHeaders?.Referer || item.pageUrl || "https://www.douyin.com/");
  await setDownloadHeadersRule(ruleId, item.url, headers).catch(() => {});
  await delay(80);
  const timeout = createRequestTimeout(PROBE_SAMPLE_TIMEOUT_MS, "\u89c6\u9891\u63a2\u6d4b\u8d85\u65f6\uff0c\u5df2\u8df3\u8fc7\u8be5\u5019\u9009\u94fe\u63a5");
  try {
    const response = await fetch(item.url, { credentials: "include", cache: "no-store", signal: timeout.signal });
    const mimeType = normalizeMime(response.headers.get("content-type") || item.mimeType || "");
    if (!response.ok && response.status !== 206) {
      throw new Error(`视频读取失败：HTTP ${response.status}`);
    }
    if (isPageLikeResource("", mimeType)) {
      throw new Error("链接返回的是网页，不是视频文件");
    }
    if (response.body?.getReader) {
      const reader = response.body.getReader();
      const chunks = [];
      let total = 0;
      while (total < maxBytes) {
        const next = await reader.read();
        if (next.done) break;
        if (next.value?.length) {
          const chunk = next.value instanceof Uint8Array ? next.value : new Uint8Array(next.value);
          chunks.push(chunk);
          total += chunk.length;
        }
      }
      await reader.cancel().catch(() => {});
      return { bytes: mergeUint8Chunks(chunks, maxBytes), mimeType };
    }
    const buffer = await response.arrayBuffer();
    return { bytes: new Uint8Array(buffer).slice(0, maxBytes), mimeType };
  } catch (error) {
    if (timeout.signal.aborted) throw new Error(timeout.message);
    throw error;
  } finally {
    timeout.cancel();
    await clearRule(ruleId);
  }
}

async function fetchWithTimeout(url, options = {}, timeoutMs = PROBE_HEAD_TIMEOUT_MS, message = "\u7f51\u7edc\u8bf7\u6c42\u8d85\u65f6") {
  const timeout = createRequestTimeout(timeoutMs, message);
  try {
    return await fetch(url, { ...options, signal: timeout.signal });
  } catch (error) {
    if (timeout.signal.aborted) throw new Error(timeout.message);
    throw error;
  } finally {
    timeout.cancel();
  }
}

function createRequestTimeout(timeoutMs, message) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), Math.max(500, Number(timeoutMs) || 0));
  return {
    signal: controller.signal,
    message,
    cancel: () => clearTimeout(timer)
  };
}

function isPlayableMediaSample(bytes, mimeType, extension) {
  if (!bytes || bytes.length < 12) return false;
  const ext = String(extension || "").toLowerCase();
  const mime = normalizeMime(mimeType || "");
  const text = asciiFromBytes(bytes, 0, Math.min(32, bytes.length)).trimStart().toLowerCase();
  if (text.startsWith("<!doctype html") || text.startsWith("<html")) return false;
  if (text.startsWith("#extm3u")) return ext === "m3u8" || mime.includes("mpegurl");
  if (bytes[0] === 0x1a && bytes[1] === 0x45 && bytes[2] === 0xdf && bytes[3] === 0xa3) return true;
  if (bytes[0] === 0x47 && (ext === "ts" || mime.includes("mp2t"))) return true;
  const box = asciiFromBytes(bytes, 4, 4);
  if (box === "ftyp" || box === "styp") return true;
  return false;
}

function isMp4LikeResource(bytes, mimeType, extension) {
  const ext = String(extension || "").toLowerCase();
  const mime = normalizeMime(mimeType || "");
  if (["mp4", "m4v", "mov", "m4s"].includes(ext)) return true;
  if (mime === "video/mp4" || mime === "audio/mp4" || mime === "application/mp4") return true;
  const box = asciiFromBytes(bytes || new Uint8Array(), 4, 4);
  return box === "ftyp" || box === "styp";
}

function readMp4TrackInfo(bytes) {
  let audio = false;
  let video = false;
  const length = bytes?.length || 0;
  for (let index = 0; index <= length - 16; index += 1) {
    if (asciiFromBytes(bytes, index, 4) !== "hdlr") continue;
    const handlerType = asciiFromBytes(bytes, index + 12, 4);
    if (handlerType === "soun") audio = true;
    if (handlerType === "vide") video = true;
    if (audio && video) break;
  }
  return { audio, video };
}

function mergeUint8Chunks(chunks, maxBytes) {
  const total = Math.min(maxBytes, chunks.reduce((sum, chunk) => sum + (chunk?.length || 0), 0));
  const merged = new Uint8Array(total);
  let offset = 0;
  for (const chunk of chunks) {
    if (!chunk?.length || offset >= total) continue;
    const slice = chunk.slice(0, total - offset);
    merged.set(slice, offset);
    offset += slice.length;
  }
  return merged;
}

function asciiFromBytes(bytes, start, length) {
  let text = "";
  for (let index = start; index < start + length && index < bytes.length; index += 1) {
    text += String.fromCharCode(bytes[index]);
  }
  return text;
}

function isLikelyMediaUrl(url, extension, mimeType, kind) {
  const ext = String(extension || "").toLowerCase();
  if (mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType.includes("mpegurl") || mimeType.includes("dash+xml")) return true;
  if (ext && MEDIA_EXTENSIONS.has(ext)) return true;
  if (kind === "playlist" && PLAYLIST_EXTENSIONS.has(ext)) return true;
  return TRUSTED_MEDIA_HOSTS.test(getHost(url));
}

function isTrustedMediaHost(url) {
  return TRUSTED_MEDIA_HOSTS.test(getHost(url));
}

function shouldCaptureRequestCardMeta(details, cardMeta) {
  if (!cardMeta || (!cardMeta.authorName && !cardMeta.likeCountText && !cardMeta.sourceTraceUrl && !cardMeta.videoTitle)) return false;
  return isPotentialMediaRequest(details.url, details.type);
}

function pickRecentCardMetaForResponse(url, recentCardMeta) {
  if (!recentCardMeta?.resourceKey) return {};
  return normalizeResourceMatchKey(url) === recentCardMeta.resourceKey ? recentCardMeta : {};
}

function isPotentialMediaRequest(url, requestType) {
  if (!url || requestType === "image") return false;
  const extension = getExtension(url);
  if (isImageExtension(extension)) return false;
  if (requestType === "media") return true;
  if (extension && (MEDIA_EXTENSIONS.has(extension) || PLAYLIST_EXTENSIONS.has(extension))) return true;
  if (!isTrustedMediaHost(url)) return false;
  try {
    const parsed = new URL(url);
    const path = `${parsed.pathname}${parsed.search}`.toLowerCase();
    if (/[?&](video_id|vid|item_id|aweme_id|group_id|rid)=/i.test(parsed.search)) return true;
    return /\/(play|playwm|stream|video|aweme|media)\b/i.test(path);
  } catch {
    return /video|play|stream|aweme/i.test(String(url || ""));
  }
}

function isImageExtension(extension) {
  return ["jpg", "jpeg", "png", "gif", "webp", "avif", "bmp", "svg", "apng"].includes(String(extension || "").toLowerCase());
}

function isPageLikeResource(extension, mimeType) {
  return Boolean(extension && PAGE_EXTENSIONS.has(extension.toLowerCase()))
    || mimeType === "text/html"
    || mimeType === "application/xhtml+xml";
}

function isKnownPageUrl(url) {
  try {
    const parsed = new URL(url);
    return /(^|\.)douyin\.com$/i.test(parsed.hostname)
      && /^\/(video|search|user|discover|note)(\/|$)/i.test(parsed.pathname);
  } catch {
    return false;
  }
}

function getExtension(url) {
  try {
    const pathname = new URL(url).pathname.toLowerCase();
    const match = pathname.match(/\.([a-z0-9]{2,8})$/i);
    return match ? match[1] : "";
  } catch {
    const match = String(url || "").toLowerCase().match(/\.([a-z0-9]{2,8})(?:[?#]|$)/);
    return match ? match[1] : "";
  }
}

function getHost(url) {
  try {
    return new URL(url).hostname;
  } catch {
    return "";
  }
}

function extensionFromMime(mimeType) {
  const map = {
    "image/jpeg": "jpg",
    "image/png": "png",
    "image/webp": "webp",
    "video/mp4": "mp4",
    "video/webm": "webm",
    "audio/mpeg": "mp3",
    "audio/mp4": "m4a",
    "application/vnd.apple.mpegurl": "m3u8",
    "application/x-mpegurl": "m3u8",
    "application/dash+xml": "mpd"
  };
  return map[mimeType] || "";
}

function normalizeMime(value) {
  return String(value || "").split(";")[0].trim().toLowerCase();
}

function getFileName(url, extension = "") {
  try {
    const parsed = new URL(url);
    const raw = decodeURIComponent(parsed.pathname.split("/").filter(Boolean).pop() || "");
    const rawExtension = (raw.match(/\.([a-z0-9]{2,8})$/i)?.[1] || "").toLowerCase();
    if (raw && raw.includes(".") && !PAGE_EXTENSIONS.has(rawExtension)) return sanitizeFileName(raw);
  } catch {}
  const ext = extension || getExtension(url) || "media";
  return `media-${Date.now()}.${ext}`;
}

function buildDownloadName(item) {
  let base = sanitizeFileName(item.name || getFileName(item.url, item.extension || defaultExtensionForKind(item.kind)));
  const extension = getExtension(base) || getExtension(item.url) || item.extension || defaultExtensionForKind(item.kind);
  if (extension && !getExtension(base)) base = `${base}.${extension}`;
  return base;
}

function buildAixStyleDownloadName(item) {
  const extension = getExtension(item.name || "") || getExtension(item.url) || item.extension || defaultExtensionForKind(item.kind) || "mp4";
  let base = sanitizeFileName(item.name || item.pageTitle || getFileName(item.url, extension) || "download");
  if (getExtension(base)) base = base.replace(/\.[^.]+$/, "");
  return `${base}-${formatAixTimestamp(new Date())}.${extension}`;
}

function formatAixTimestamp(date) {
  const pad = (value) => String(value).padStart(2, "0");
  return [
    date.getFullYear(),
    pad(date.getMonth() + 1),
    pad(date.getDate()),
    "_",
    pad(date.getHours()),
    pad(date.getMinutes()),
    pad(date.getSeconds())
  ].join("");
}

function defaultExtensionForKind(kind) {
  if (kind === "video") return "mp4";
  if (kind === "audio") return "mp3";
  if (kind === "playlist") return "m3u8";
  return "";
}

function sanitizeFileName(value) {
  return String(value || "media")
    .replace(/[\\/:*?"<>|\r\n]+/g, "_")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, 180) || "media";
}

function normalizeFingerprint(url) {
  try {
    const parsed = new URL(url);
    parsed.hash = "";
    for (const key of ["range", "bytestart", "byteend", "start", "end"]) parsed.searchParams.delete(key);
    return parsed.href;
  } catch {
    return url;
  }
}

function normalizeResourceMatchKey(url) {
  try {
    const parsed = new URL(url);
    return `${parsed.origin}${parsed.pathname}`;
  } catch {
    return String(url || "").split("?")[0];
  }
}

function makeId(url, salt) {
  const raw = `${salt}:${url}`;
  let hash = 0;
  for (let index = 0; index < raw.length; index += 1) hash = (hash * 31 + raw.charCodeAt(index)) >>> 0;
  return hash.toString(36);
}

function isIgnoredScheme(url) {
  return /^(chrome|chrome-extension|edge|about|devtools|data|blob):/i.test(url);
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function stripHash(url) {
  try {
    const parsed = new URL(url);
    parsed.hash = "";
    return parsed.href;
  } catch {
    return url;
  }
}

function escapeRegex(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
