(function () {
  if (window.__gmapsCollectorInstalled) {
    return;
  }

  window.__gmapsCollectorInstalled = true;

  const PANEL_ID = "gmaps-collector-panel";
  const NETWORK_MESSAGE_TYPE = "GMAPS_COLLECTOR_NETWORK_PAYLOAD";
  const SOCIAL_KEYS = ["facebook", "instagram", "linkedin", "youtube", "whatsapp", "twitter"];
  const DOMAIN_SUFFIX_TOKENS = new Set([
    "www",
    "com",
    "net",
    "org",
    "co",
    "io",
    "me",
    "app",
    "biz",
    "info",
    "store",
    "shop",
    "site",
    "online",
    "us",
    "uk",
    "cn",
    "cc",
    "tv",
    "nyc"
  ]);
  const BUSINESS_NAME_STOP_WORDS = new Set([
    "the",
    "and",
    "of",
    "for",
    "inc",
    "llc",
    "ltd",
    "corp",
    "corporation",
    "company",
    "co",
    "ny",
    "nyc"
  ]);
  const BUSINESS_NAME_WEAK_TOKENS = new Set([
    "door",
    "doors",
    "window",
    "windows",
    "service",
    "services",
    "supplier",
    "suppliers",
    "contractor",
    "contractors",
    "store",
    "stores",
    "shop",
    "shops",
    "brooklyn",
    "manhattan",
    "queens",
    "bronx",
    "staten",
    "island",
    "new",
    "york",
    "city"
  ]);
  const BUSINESS_NAME_ACRONYM_EXCLUDED_TOKENS = new Set([
    "the",
    "and",
    "of",
    "for",
    "inc",
    "llc",
    "ltd",
    "corp",
    "corporation",
    "co",
    "sdn",
    "bhd",
    "plc",
    "pte",
    "pty",
    "group",
    "holding",
    "holdings"
  ]);
  const STATE = {
    running: false,
    enrichEmail: true,
    collectionId: "",
    currentQuery: "",
    knownKeys: new Set(),
    knownLeadMap: new Map(),
    candidateMap: new Map(),
    attemptedResultKeys: new Set(),
    queuedCandidateKeys: new Set(),
    seenResultKeys: new Set(),
    failedResultKeys: new Map(),
    enrichmentQueuedKeys: new Set(),
    processedPayloadKeys: new Set(),
    pendingLeadBuffer: [],
    pendingLeadBufferKeys: new Set(),
    pendingLeadBufferReason: "",
    pendingFlushPromise: null,
    persistUiStateTimer: null,
    resumeRequested: false,
    restoredDiagnostics: {
      candidates: 0,
      queued: 0
    },
    lastStatus: {
      text: "等待开始采集。",
      tone: "neutral"
    },
    storedCount: 0,
    lastResultsUrl: "",
    processedThisRun: 0,
    networkLeadCount: 0,
    resultsEndReached: false,
    resultsListDirty: false,
    autostartRequested: false,
    pendingAutostartJob: null,
    autostartRetryTimer: null,
    lastBatchAt: 0,
    networkHookReady: false,
    resultsObserver: null,
    runStats: {
      success: 0,
      deduped: 0,
      skipped: 0,
      failed: 0,
      fallbackSaved: 0
    }
  };
  const RESULT_IDLE_LIMIT = 4;
  const SCROLL_WAIT_MS = 320;
  const DETAIL_WAIT_MS = 1100;
  const OPEN_DETAIL_WAIT_MS = 5000;
  const DETAIL_RETRY_DELAYS_MS = [0, 220, 520];
  const CONTACT_RECOVERY_RETRY_DELAYS_MS = [0, 360, 800, 1400];
  const FORCE_LOAD_TRIES = 3;
  const FORCE_LOAD_WAIT_MS = 650;
  const SAVE_BUFFER_MAX_SIZE = 12;
  const SAVE_BUFFER_MAX_AGE_MS = 900;
  const MESSAGE_TIMEOUT_MS = 15000;
  const MESSAGE_TIMEOUT_BY_ACTION = {
    ENRICH_WEBSITE: 45000,
    GET_LEADS: 10000,
    MERGE_LEADS: 10000,
    UPDATE_ACTIVE_COLLECTION_STATE: 8000,
    GET_ACTIVE_COLLECTION_STATE: 8000
  };

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

  function createFingerprint(value) {
    const text = String(value || "");
    let hash = 2166136261;

    for (let index = 0; index < text.length; index += 1) {
      hash ^= text.charCodeAt(index);
      hash = Math.imul(hash, 16777619);
    }

    return `fp_${(hash >>> 0).toString(16)}_${text.length}`;
  }

  function sendMessage(message) {
    return new Promise((resolve) => {
      let settled = false;
      const action = normalizeText(message?.action);
      const timeoutMs = MESSAGE_TIMEOUT_BY_ACTION[action] || MESSAGE_TIMEOUT_MS;
      const timeoutId = setTimeout(() => {
        if (settled) {
          return;
        }
        settled = true;
        resolve({ ok: false, error: `消息超时: ${action || "UNKNOWN_ACTION"}` });
      }, timeoutMs);

      chrome.runtime.sendMessage(message, (response) => {
        if (settled) {
          return;
        }
        settled = true;
        clearTimeout(timeoutId);

        if (chrome.runtime.lastError) {
          resolve({ ok: false, error: chrome.runtime.lastError.message });
          return;
        }

        resolve(response || { ok: false, error: "No response." });
      });
    });
  }

  function safeGet(input, path, defaultValue = "") {
    let current = input;

    for (const key of path) {
      if (current === null || current === undefined) {
        return defaultValue;
      }

      current = current[key];
    }

    return current === undefined ? defaultValue : current;
  }

  function injectNetworkHook() {
    if (STATE.networkHookReady) {
      return;
    }

    const existing = document.getElementById("gmaps-collector-network-hook");
    if (existing) {
      STATE.networkHookReady = true;
      return;
    }

    const script = document.createElement("script");
    script.id = "gmaps-collector-network-hook";
    script.src = chrome.runtime.getURL("injected.js");
    script.onload = () => {
      STATE.networkHookReady = true;
      script.remove();
    };
    (document.head || document.documentElement).appendChild(script);
  }

  function normalizeHours(rawHours) {
    const weeklyHours = {
      monday: "",
      tuesday: "",
      wednesday: "",
      thursday: "",
      friday: "",
      saturday: "",
      sunday: ""
    };

    if (!Array.isArray(rawHours)) {
      return weeklyHours;
    }

    const dayMap = [
      "sunday",
      "monday",
      "tuesday",
      "wednesday",
      "thursday",
      "friday",
      "saturday"
    ];

    function resolveDayKey(label, fallbackIndex = -1, weekdayNumber = NaN) {
      const normalizedLabel = normalizeText(label).toLowerCase();
      const explicitKey = dayMap.find((day) => normalizedLabel.includes(day.slice(0, 3))) ||
        (normalizedLabel.includes("周一") ? "monday" : "") ||
        (normalizedLabel.includes("周二") ? "tuesday" : "") ||
        (normalizedLabel.includes("周三") ? "wednesday" : "") ||
        (normalizedLabel.includes("周四") ? "thursday" : "") ||
        (normalizedLabel.includes("周五") ? "friday" : "") ||
        (normalizedLabel.includes("周六") ? "saturday" : "") ||
        (normalizedLabel.includes("周日") || normalizedLabel.includes("周天") ? "sunday" : "");

      if (explicitKey) {
        return explicitKey;
      }

      const numericDay = Number(weekdayNumber);
      if (Number.isFinite(numericDay) && numericDay >= 1 && numericDay <= 7) {
        return ["", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"][numericDay];
      }

      return dayMap[fallbackIndex] || "";
    }

    rawHours.forEach((entry, index) => {
      if (!Array.isArray(entry)) {
        return;
      }

      const label = normalizeText(entry[0]);
      const directHours = Array.isArray(entry?.[1]) ? entry[1].join(", ") : normalizeText(entry?.[1]);
      const batchHours = Array.isArray(entry?.[3])
        ? entry[3]
            .map((item) => normalizeText(Array.isArray(item) ? item[0] : item))
            .filter(Boolean)
            .join(", ")
        : "";
      const hours = normalizeText(directHours || batchHours);
      const key = resolveDayKey(label, index, entry?.[1]);

      if (key && hours) {
        weeklyHours[key] = normalizeText(hours);
      }
    });

    return weeklyHours;
  }

  function isLikelyFeedEntry(entry) {
    if (!Array.isArray(entry) || !entry.length) {
      return false;
    }

    const item = entry[entry.length - 1];
    if (!Array.isArray(item)) {
      return false;
    }

    const name = normalizeText(safeGet(item, [11], ""));
    const coords = safeGet(item, [9], []);
    const address = safeGet(item, [2], []);
    const website = cleanUrl(safeGet(item, [7, 0], ""));
    const placeID = normalizeText(safeGet(item, [78], ""));
    const cID = normalizeText(safeGet(item, [37, 0, 0, 29, 1], ""));
    const category = safeGet(item, [13], []);
    const hasCoords = Array.isArray(coords) && coords.length >= 2;
    const hasAddress = Array.isArray(address) ? address.length > 0 : typeof address === "string" && Boolean(normalizeText(address));
    const hasCategory = Array.isArray(category) ? category.length > 0 : typeof category === "string" && Boolean(normalizeText(category));

    return Boolean(
      name &&
      (hasCoords || hasAddress || hasCategory || website || placeID || cID)
    );
  }

  function isLikelyBusinessDataArray(item) {
    if (!Array.isArray(item) || item.length < 8) {
      return false;
    }

    const name = normalizeText(safeGet(item, [11], ""));
    if (!name || name.length < 2) {
      return false;
    }

    const coords = safeGet(item, [9], []);
    const addressList = safeGet(item, [2], []);
    const addressText = normalizeText(safeGet(item, [18], ""));
    const website = normalizeText(safeGet(item, [7, 0], ""));
    const phone = normalizeText(safeGet(item, [178, 0, 0], ""));
    const placeID = normalizeText(safeGet(item, [78], ""));
    const category = safeGet(item, [13], []);
    const hasCoords = Array.isArray(coords) && coords.length >= 2;
    const hasAddressList = Array.isArray(addressList) && addressList.some((value) => normalizeText(value));
    const hasAddressText = Boolean(addressText);
    const hasCategory = Array.isArray(category) ? category.some((value) => normalizeText(value)) : Boolean(normalizeText(category));

    return Boolean(
      placeID ||
      website ||
      phone ||
      hasCoords ||
      hasAddressList ||
      hasAddressText ||
      hasCategory
    );
  }

  function collectBusinessDataArrays(root) {
    const stack = [root];
    const businessArrays = [];
    const visited = new Set();
    const seenBusinessArrays = new Set();

    while (stack.length) {
      const current = stack.pop();
      if (!current || typeof current !== "object") {
        continue;
      }

      if (visited.has(current)) {
        continue;
      }

      visited.add(current);

      if (Array.isArray(current)) {
        if (isLikelyBusinessDataArray(current) && !seenBusinessArrays.has(current)) {
          seenBusinessArrays.add(current);
          businessArrays.push(current);
        }

        const matchingEntries = current.filter(isLikelyFeedEntry);
        matchingEntries.forEach((entry) => {
          const businessItem = Array.isArray(entry) ? entry[entry.length - 1] : null;
          if (Array.isArray(businessItem) && !seenBusinessArrays.has(businessItem)) {
            seenBusinessArrays.add(businessItem);
            businessArrays.push(businessItem);
          }
        });

        current.forEach((child) => {
          if (child && typeof child === "object") {
            stack.push(child);
          }
        });
        continue;
      }

      Object.values(current).forEach((child) => {
        if (child && typeof child === "object") {
          stack.push(child);
        }
      });
    }

    return businessArrays;
  }

  function parsePayloadRoots(body) {
    const roots = [];
    const rawBody = String(body || "").replace(/^\/\*""\*\//, "");
    if (!rawBody) {
      return roots;
    }

    try {
      const outer = JSON.parse(rawBody);
      roots.push(outer);

      let innerRaw = String(outer?.d || "");
      innerRaw = innerRaw.replace(/^\)\]\}'\s*/, "");
      if (innerRaw) {
        try {
          roots.push(JSON.parse(innerRaw));
        } catch (_error) {
          // Ignore payload variants that do not contain JSON in `d`.
        }
      }
    } catch (_error) {
      const stripped = rawBody.replace(/^\)\]\}'\s*/, "");
      try {
        roots.push(JSON.parse(stripped));
      } catch (_innerError) {
        return [];
      }
    }

    return roots;
  }

  function collectPayloadUrls(value, maxNodes = 1200) {
    const urls = new Set();
    const stack = [value];
    const visited = new Set();

    while (stack.length && visited.size < maxNodes) {
      const current = stack.pop();
      if (!current) {
        continue;
      }

      if (typeof current === "string") {
        extractUrlsFromText(current).forEach((url) => urls.add(cleanUrl(unwrapGoogleRedirectUrl(url))));
        continue;
      }

      if (typeof current !== "object" || visited.has(current)) {
        continue;
      }

      visited.add(current);

      if (Array.isArray(current)) {
        current.forEach((item) => {
          if (item && (typeof item === "string" || typeof item === "object")) {
            stack.push(item);
          }
        });
        continue;
      }

      Object.values(current).forEach((item) => {
        if (item && (typeof item === "string" || typeof item === "object")) {
          stack.push(item);
        }
      });
    }

    return [...urls].filter(Boolean);
  }

  function extractWebsiteAndSocialsFromPayloadItem(item) {
    const socials = {
      facebook: "",
      instagram: "",
      linkedin: "",
      youtube: "",
      whatsapp: "",
      twitter: ""
    };
    const directWebsite = coerceUrl(safeGet(item, [7, 0], ""));
    const payloadUrls = collectPayloadUrls(item);

    payloadUrls.forEach((url) => assignSocialFromUrl(socials, url));

    const businessName = normalizeText(safeGet(item, [11], ""));
    const preferredDirectWebsite = isLikelyOfficialWebsiteUrl(directWebsite, businessName)
      ? directWebsite
      : "";
    const website = preferredDirectWebsite || chooseBestWebsite(payloadUrls, {
      businessName,
      requireNameMatch: true
    });
    return { website, socials };
  }

  function extractLeadFromBusinessDataArray(item) {
    if (!isLikelyBusinessDataArray(item)) {
      return null;
    }

    const name = normalizeText(safeGet(item, [11], ""));
    if (!name) {
      return null;
    }

    const addressValue = safeGet(item, [18], "") || safeGet(item, [2], []);
    const categoryValue = safeGet(item, [13], []);
    const primaryHours = safeGet(item, [34, 1], []);
    const fallbackHours = safeGet(item, [203, 0], []);
    const hours = normalizeHours(Array.isArray(primaryHours) && primaryHours.length ? primaryHours : fallbackHours);
    const phone = normalizeText(safeGet(item, [178, 0, 0], "")) || normalizeText(safeGet(item, [178, 0], ""));
    const reviewCount = normalizeText(safeGet(item, [4, 8], ""));
    const averageRating = normalizeText(safeGet(item, [4, 7], ""));
    const directMapsUrl = cleanUrl(safeGet(item, [27], ""));
    const placeID = normalizeText(safeGet(item, [78], "")) || parsePlaceId(directMapsUrl);
    const cID = normalizeText(safeGet(item, [37, 0, 0, 29, 1], "")) || parseCid(directMapsUrl);
    const latitude = normalizeText(safeGet(item, [9, 2], ""));
    const longitude = normalizeText(safeGet(item, [9, 3], ""));
    const { website, socials } = extractWebsiteAndSocialsFromPayloadItem(item);
    const rawAddress = Array.isArray(addressValue)
      ? normalizeText(addressValue.join(", "))
      : normalizeText(String(addressValue));
    const comparableAddress = rawAddress.toLowerCase();
    const comparableName = name.toLowerCase();
    const address = comparableAddress.startsWith(`${comparableName},`)
      ? normalizeText(rawAddress.slice(name.length + 1))
      : (comparableAddress === comparableName ? "" : rawAddress);
    const category = Array.isArray(categoryValue) ? normalizeText(categoryValue.join("; ")) : normalizeText(categoryValue);
    const googleMapsUrl =
      directMapsUrl ||
      (
        placeID && latitude && longitude
          ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${latitude},${longitude}`)}&query_place_id=${encodeURIComponent(placeID)}`
          : ""
      );

    return {
      query: getSearchQuery(),
      name,
      category,
      address,
      phone,
      website,
      email: "",
      placeID,
      placeId: placeID,
      cID,
      cid: cID,
      reviewCount,
      averageRating,
      rating: averageRating,
      latitude,
      longitude,
      googleMapsUrl,
      sourceHref: googleMapsUrl,
      source: "network",
      openingHours: Object.values(hours).filter(Boolean).join(" | "),
      ...hours,
      ...socials
    };
  }

  function tryParseBatchPayload(body) {
    const roots = parsePayloadRoots(body);
    if (!roots.length) {
      return [];
    }

    const leadMap = new Map();

    roots.forEach((root) => {
      collectBusinessDataArrays(root).forEach((item) => {
        const lead = extractLeadFromBusinessDataArray(item);
        if (!lead) {
          return;
        }

        const key = getLeadKey(lead);
        if (key && !leadMap.has(key)) {
          leadMap.set(key, lead);
        }
      });
    });

    return [...leadMap.values()];
  }

  async function updateKnownKeys(leads) {
    STATE.knownKeys.clear();
    STATE.knownLeadMap.clear();
    (leads || []).forEach((lead) => {
      const key = getLeadKey(lead);
      if (key) {
        STATE.knownKeys.add(key);
      }

      getLeadIdentityKeys(lead).forEach((identityKey) => {
        STATE.knownLeadMap.set(identityKey, lead);
      });
    });

    STATE.storedCount = leads.length;
    setCount(leads.length);
  }

  function normalizeMergeStats(stats, totalIncoming = 0, previousCount = STATE.storedCount, nextCount = previousCount) {
    const insertedCount = Number(stats?.insertedCount ?? Math.max(0, nextCount - previousCount)) || 0;
    const incomingCount = Number(stats?.totalIncoming ?? totalIncoming) || 0;
    const updatedCount = Number(stats?.updatedCount ?? 0) || 0;
    const unchangedCount = Number(stats?.unchangedCount ?? Math.max(0, incomingCount - insertedCount - updatedCount)) || 0;

    return {
      totalIncoming: incomingCount,
      insertedCount,
      updatedCount,
      unchangedCount
    };
  }

  function hasLiveCandidateMetrics() {
    return Boolean(
      STATE.candidateMap.size ||
      STATE.seenResultKeys.size ||
      STATE.attemptedResultKeys.size
    );
  }

  function getStableCandidateCount() {
    if (!hasLiveCandidateMetrics()) {
      return Number(STATE.restoredDiagnostics.candidates || 0);
    }

    return Math.max(
      STATE.candidateMap.size,
      STATE.seenResultKeys.size,
      Number(STATE.restoredDiagnostics.candidates || 0)
    );
  }

  function getStablePendingCandidateCount() {
    if (!hasLiveCandidateMetrics()) {
      return Number(STATE.restoredDiagnostics.queued || 0);
    }

    let pendingCount = 0;
    for (const key of STATE.candidateMap.keys()) {
      if (!STATE.attemptedResultKeys.has(key)) {
        pendingCount += 1;
      }
    }

    return pendingCount;
  }

  function updateRunStatsView() {
    const panel = document.getElementById(PANEL_ID);
    if (!panel) {
      return;
    }

    const mapping = {
      success: "success-count",
      deduped: "deduped-count",
      skipped: "skipped-count",
      failed: "failed-count"
    };

    Object.entries(mapping).forEach(([key, role]) => {
      const node = panel.querySelector(`[data-role='${role}']`);
      if (node) {
        node.textContent = String(STATE.runStats[key] || 0);
      }
    });
    scheduleCollectionStateSync();
  }

  function updateDiagnosticsView() {
    const panel = document.getElementById(PANEL_ID);
    if (!panel) {
      return;
    }

    const candidateCount = getStableCandidateCount();
    const queuedCount = getStablePendingCandidateCount();
    const mapping = {
      candidates: String(candidateCount),
      queued: String(queuedCount),
      network: String(STATE.networkLeadCount),
      end: STATE.resultsEndReached ? "是" : "否"
    };

    Object.entries(mapping).forEach(([key, value]) => {
      const node = panel.querySelector(`[data-role='diag-${key}']`);
      if (node) {
        node.textContent = value;
      }
    });
    scheduleCollectionStateSync();
  }

  function resetRunStats() {
    Object.assign(STATE.runStats, {
      success: 0,
      deduped: 0,
      skipped: 0,
      failed: 0,
      fallbackSaved: 0
    });
    updateRunStatsView();
    updateDiagnosticsView();
  }

  function buildPersistedCollectionState() {
    return {
      count: Number(STATE.storedCount || 0),
      running: Boolean(STATE.running || STATE.resumeRequested),
      runStats: { ...STATE.runStats },
      diagnostics: {
        candidates: getStableCandidateCount(),
        queued: getStablePendingCandidateCount(),
        network: Number(STATE.networkLeadCount || 0),
        end: Boolean(STATE.resultsEndReached)
      },
      status: { ...STATE.lastStatus },
      updatedAt: Date.now()
    };
  }

  function scheduleCollectionStateSync() {
    if (!STATE.collectionId) {
      return;
    }

    if (STATE.persistUiStateTimer) {
      return;
    }

    STATE.persistUiStateTimer = setTimeout(() => {
      STATE.persistUiStateTimer = null;
      sendMessage({
        action: "UPDATE_ACTIVE_COLLECTION_STATE",
        collectionId: STATE.collectionId,
        state: buildPersistedCollectionState()
      }).catch(() => {});
    }, 180);
  }

  async function flushCollectionStateSync() {
    if (!STATE.collectionId) {
      return;
    }

    if (STATE.persistUiStateTimer) {
      clearTimeout(STATE.persistUiStateTimer);
      STATE.persistUiStateTimer = null;
    }

    await sendMessage({
      action: "UPDATE_ACTIVE_COLLECTION_STATE",
      collectionId: STATE.collectionId,
      state: buildPersistedCollectionState()
    });
  }

  function incrementRunStat(key, amount = 1) {
    if (!Object.prototype.hasOwnProperty.call(STATE.runStats, key)) {
      return;
    }

    STATE.runStats[key] = Math.max(0, Number(STATE.runStats[key] || 0) + Number(amount || 0));
    updateRunStatsView();
  }

  function getRunStatsSummary() {
    const parts = [
      `成功 ${STATE.runStats.success}`,
      `去重 ${STATE.runStats.deduped}`,
      `跳过 ${STATE.runStats.skipped}`,
      `失败 ${STATE.runStats.failed}`
    ];

    if (STATE.runStats.fallbackSaved > 0) {
      parts.push(`兜底 ${STATE.runStats.fallbackSaved}`);
    }

    return parts.join(" / ");
  }

  async function saveLeads(leads) {
    const previousCount = STATE.storedCount;
    const response = await sendMessage({
      action: "MERGE_LEADS",
      leads,
      collectionId: STATE.collectionId
    });

    if (!response.ok) {
      setStatus(`批量保存失败：${response.error || "未知错误"}`, "error");
      return { ok: false, leads: [], stats: normalizeMergeStats(null, Array.isArray(leads) ? leads.length : 0, previousCount, previousCount) };
    }

    const mergedLeads = response.leads || [];
    await updateKnownKeys(mergedLeads);
    STATE.lastBatchAt = Date.now();
    return {
      ok: true,
      leads: mergedLeads,
      stats: normalizeMergeStats(response.stats, Array.isArray(leads) ? leads.length : 0, previousCount, mergedLeads.length)
    };
  }

  function updateBufferedLead(lead) {
    const normalizedLead = mergeLeadSnapshots(null, lead);
    const key = getLeadKey(normalizedLead);
    if (!key) {
      return false;
    }

    const existingIndex = STATE.pendingLeadBuffer.findIndex((item) => getLeadKey(item) === key);
    if (existingIndex >= 0) {
      STATE.pendingLeadBuffer[existingIndex] = mergeLeadSnapshots(STATE.pendingLeadBuffer[existingIndex], normalizedLead);
      return true;
    }

    const knownLead = getKnownLeadForLead(normalizedLead);
    if (knownLead) {
      STATE.pendingLeadBuffer.push(mergeLeadSnapshots(knownLead, normalizedLead));
    } else {
      STATE.pendingLeadBuffer.push(normalizedLead);
    }
    STATE.pendingLeadBufferKeys.add(key);
    return true;
  }

  async function flushLeadBuffer(reason = "") {
    if (STATE.pendingFlushPromise) {
      await STATE.pendingFlushPromise;
    }

    if (!STATE.pendingLeadBuffer.length) {
      return {
        ok: true,
        leads: [],
        stats: normalizeMergeStats(null, 0, STATE.storedCount, STATE.storedCount)
      };
    }

    const leadsToFlush = STATE.pendingLeadBuffer.map((lead) => mergeLeadSnapshots(null, lead)).filter(Boolean);
    STATE.pendingLeadBuffer = [];
    STATE.pendingLeadBufferKeys.clear();
    STATE.pendingLeadBufferReason = "";

    const flushPromise = saveLeads(leadsToFlush);
    STATE.pendingFlushPromise = flushPromise;

    try {
      const result = await flushPromise;
      if (!result.ok) {
        leadsToFlush.forEach((lead) => {
          updateBufferedLead(lead);
        });
      }
      return result;
    } finally {
      if (STATE.pendingFlushPromise === flushPromise) {
        STATE.pendingFlushPromise = null;
      }
      if (reason) {
        STATE.pendingLeadBufferReason = reason;
      }
    }
  }

  async function bufferLeadsForSave(leads, options = {}) {
    const normalizedLeads = Array.isArray(leads) ? leads : [leads];
    let addedCount = 0;

    normalizedLeads.forEach((lead) => {
      if (lead && updateBufferedLead(lead)) {
        addedCount += 1;
      }
    });

    if (!STATE.pendingLeadBuffer.length) {
      return {
        ok: true,
        leads: [],
        stats: normalizeMergeStats(null, 0, STATE.storedCount, STATE.storedCount),
        buffered: true
      };
    }

    const forceFlush = Boolean(options.forceFlush);
    const reachedSizeLimit = STATE.pendingLeadBuffer.length >= SAVE_BUFFER_MAX_SIZE;
    const staleBuffer = STATE.lastBatchAt > 0 && Date.now() - STATE.lastBatchAt >= SAVE_BUFFER_MAX_AGE_MS;

    if (!forceFlush && !reachedSizeLimit && !staleBuffer) {
      return {
        ok: true,
        leads: [],
        stats: normalizeMergeStats(null, addedCount, STATE.storedCount, STATE.storedCount),
        buffered: true
      };
    }

    return flushLeadBuffer(options.reason || "buffer-threshold");
  }

  async function mapWithConcurrency(items, limit, worker) {
    const results = [];
    let cursor = 0;

    async function run() {
      while (cursor < items.length) {
        const currentIndex = cursor;
        cursor += 1;
        results[currentIndex] = await worker(items[currentIndex], currentIndex);
      }
    }

    const workers = Array.from({ length: Math.min(limit, items.length) }, () => run());
    await Promise.all(workers);
    return results;
  }

  async function enrichLeadsInBackground(leads) {
    const pending = leads.filter((lead) => {
      const key = getLeadKey(lead);
      if (!lead.website || !key || STATE.enrichmentQueuedKeys.has(key)) {
        return false;
      }

      STATE.enrichmentQueuedKeys.add(key);
      return true;
    });

    if (!pending.length) {
      return;
    }

    await mapWithConcurrency(pending, 6, async (lead) => {
      try {
        const enriched = await enrichLead(lead);
        const key = getLeadKey(lead);
        if (key) {
          STATE.enrichmentQueuedKeys.delete(key);
        }

        if (JSON.stringify(enriched) !== JSON.stringify(lead)) {
          await bufferLeadsForSave([enriched], {
            forceFlush: false,
            reason: "background-enrich"
          });
        }
      } catch (_error) {
        const key = getLeadKey(lead);
        if (key) {
          STATE.enrichmentQueuedKeys.delete(key);
        }
      }
    });
  }

  async function handleNetworkPayload(event) {
    if (event.source !== window || event.data?.source !== "gmaps-collector" || event.data?.type !== NETWORK_MESSAGE_TYPE) {
      return;
    }

    const payloadKey = `${event.data.transport || ""}|${event.data.url || ""}|${createFingerprint(event.data.body || "")}`;
    if (STATE.processedPayloadKeys.has(payloadKey)) {
      return;
    }

    STATE.processedPayloadKeys.add(payloadKey);
    const leads = tryParseBatchPayload(event.data.body);
    if (!leads.length) {
      return;
    }

    leads.forEach((lead) => {
      registerCandidate({
        href: cleanUrl(lead.googleMapsUrl || lead.sourceHref),
        sourceHref: cleanUrl(lead.sourceHref || lead.googleMapsUrl),
        googleMapsUrl: cleanUrl(lead.googleMapsUrl),
        name: normalizeText(lead.name),
        address: normalizeText(lead.address),
        phone: normalizeText(lead.phone),
        latitude: normalizeText(lead.latitude),
        longitude: normalizeText(lead.longitude),
        placeId: normalizeText(lead.placeId || lead.placeID),
        placeID: normalizeText(lead.placeId || lead.placeID),
        cID: normalizeText(lead.cID || lead.cid),
        cid: normalizeText(lead.cID || lead.cid),
        source: "network"
      });
    });
    STATE.networkLeadCount += leads.length;
    updateDiagnosticsView();

    const result = await bufferLeadsForSave(leads, {
      forceFlush: leads.length >= SAVE_BUFFER_MAX_SIZE,
      reason: "network-payload"
    });
    if (!result.ok) {
      return;
    }

    if (!result.buffered) {
      const delta = result.stats.insertedCount;
      const dedupedCount = Math.max(0, result.stats.totalIncoming - result.stats.insertedCount);

      if (delta > 0) {
        incrementRunStat("success", delta);
        STATE.processedThisRun += delta;
      }

      if (dedupedCount > 0) {
        incrementRunStat("deduped", dedupedCount);
      }

      if (delta > 0) {
        setStatus(`已批量采集 ${STATE.processedThisRun} 条，正在继续扫描结果。${dedupedCount > 0 ? ` 当前${getRunStatsSummary()}。` : ""}`, "success");
      } else if (dedupedCount > 0) {
        setStatus(`检测到重复商家，已自动合并。当前${getRunStatsSummary()}。`, "neutral");
      }
    }

    if (STATE.enrichEmail) {
      enrichLeadsInBackground(leads).catch(() => {});
    }
  }

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

  function cleanUrl(value) {
    const text = normalizeText(String(value || "")
      .replace(/\\u003d/gi, "=")
      .replace(/\\u0026/gi, "&")
      .replace(/\\u002f/gi, "/")
      .replace(/\\\//g, "/")
      .replace(/&amp;/gi, "&"));
    if (!text) {
      return "";
    }

    try {
      const url = new URL(text);
      url.hash = "";
      return url.toString();
    } catch (_error) {
      return text;
    }
  }

  function unwrapGoogleRedirectUrl(value) {
    const cleaned = cleanUrl(value);
    if (!cleaned) {
      return "";
    }

    try {
      const url = new URL(cleaned);
      const isGoogleRedirect =
        /(^|\.)google\./i.test(url.hostname) &&
        (url.pathname === "/url" || url.pathname.includes("/redirect"));

      if (isGoogleRedirect) {
        const redirected = url.searchParams.get("q") || url.searchParams.get("url");
        if (redirected) {
          return cleanUrl(redirected);
        }
      }

      return url.toString();
    } catch (_error) {
      return cleaned;
    }
  }

  function coerceUrl(value) {
    let text = normalizeText(value).replace(/^[<(（【「『\s]+|[)>）,，。；;】」』\s]+$/g, "");
    if (!text) {
      return "";
    }

    if (text.startsWith("//")) {
      text = `https:${text}`;
    } else if (text.startsWith("/url?")) {
      text = `https://www.google.com${text}`;
    } else if (!/^[a-z][a-z\d+.-]*:/i.test(text) && /^(?:www\.)?[a-z\d][a-z\d.-]*\.[a-z]{2,}(?:[/?#]|$)/i.test(text)) {
      text = `https://${text.replace(/^\/+/, "")}`;
    }

    const unwrapped = unwrapGoogleRedirectUrl(text);
    try {
      const url = new URL(unwrapped);
      if (!/^(https?:|mailto:|tel:)/i.test(url.protocol)) {
        return "";
      }

      url.hash = "";
      return url.toString();
    } catch (_error) {
      return "";
    }
  }

  function extractUrlsFromText(value) {
    const text = String(value || "")
      .replace(/\\u003d/gi, "=")
      .replace(/\\u0026/gi, "&")
      .replace(/\\u002f/gi, "/")
      .replace(/\\\//g, "/")
      .replace(/&amp;/gi, "&");
    if (!text) {
      return [];
    }

    const matches = text.match(/(?:https?:\/\/|\/\/|\/url\?(?:q|url)=)[^\s"'<>]+|(?:www\.)?[a-z\d][a-z\d.-]*\.[a-z]{2,}(?:\/[^\s"'<>]*)?/gi) || [];
    const urls = [];

    matches.forEach((match) => {
      const normalized = coerceUrl(match);
      if (normalized) {
        urls.push(normalized);
      }
    });

    return [...new Set(urls)];
  }

  function isGoogleOwnedUrl(value) {
    const url = coerceUrl(value);
    if (!url) {
      return false;
    }

    try {
      const { hostname } = new URL(url);
      return /(^|\.)google\./i.test(hostname) || /(^|\.)gstatic\.com$/i.test(hostname);
    } catch (_error) {
      return /google\./i.test(url);
    }
  }

  function isSocialUrl(value) {
    const url = coerceUrl(value);
    return /facebook\.com|fb\.com|instagram\.com|instagr\.am|linkedin\.com|youtube\.com|youtu\.be|twitter\.com|x\.com|whatsapp\.com|wa\.me/i.test(url);
  }

  function getHostnameTokens(value) {
    const url = coerceUrl(value);
    if (!url) {
      return [];
    }

    try {
      return new URL(url).hostname
        .toLowerCase()
        .split(/[^a-z0-9]+/i)
        .map((token) => token.trim())
        .filter((token) => token && !DOMAIN_SUFFIX_TOKENS.has(token));
    } catch (_error) {
      return [];
    }
  }

  function getBusinessNameTokens(value) {
    return normalizeText(value)
      .toLowerCase()
      .replace(/&/g, " and ")
      .split(/[^a-z0-9]+/i)
      .map((token) => token.trim())
      .filter((token) => token && token.length >= 2 && !BUSINESS_NAME_STOP_WORDS.has(token));
  }

  function getDistinctiveBusinessNameTokens(value) {
    return getBusinessNameTokens(value).filter((token) => !BUSINESS_NAME_WEAK_TOKENS.has(token));
  }

  function getBusinessNameAcronyms(value) {
    const rawTokens = normalizeText(value)
      .toLowerCase()
      .replace(/&/g, " and ")
      .split(/[^a-z0-9]+/i)
      .map((token) => token.trim())
      .filter(Boolean)
      .filter((token) => !BUSINESS_NAME_ACRONYM_EXCLUDED_TOKENS.has(token));

    const acronyms = new Set();
    for (let start = 0; start < rawTokens.length; start += 1) {
      for (let size = 2; size <= 4 && start + size <= rawTokens.length; size += 1) {
        const acronym = rawTokens.slice(start, start + size).map((token) => token[0]).join("");
        if (acronym.length >= 2 && acronym.length <= 6) {
          acronyms.add(acronym);
        }
      }
    }

    return [...acronyms];
  }

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

  function urlMatchesBusinessName(value, businessName) {
    const tokens = getDistinctiveBusinessNameTokens(businessName);
    const acronyms = getBusinessNameAcronyms(businessName);
    if (!tokens.length && !acronyms.length) {
      return false;
    }

    const hostnameTokens = getHostnameTokens(value);
    if (!hostnameTokens.length) {
      return false;
    }

    const joinedHostname = hostnameTokens.join("");
    const matchedTokens = tokens.filter((token) => {
      if (token.length < 3) {
        return false;
      }

      return hostnameTokens.some((hostnameToken) => hostnameToken.includes(token) || token.includes(hostnameToken)) ||
        joinedHostname.includes(token);
    });

    if (!matchedTokens.length) {
      return acronyms.some((acronym) => {
        if (acronym.length < 2) {
          return false;
        }

        return hostnameTokens.some((hostnameToken) => hostnameToken === acronym || hostnameToken.includes(acronym)) ||
          joinedHostname.includes(acronym);
      });
    }

    return matchedTokens.some((token) => token.length >= 4);
  }

  function isGenericLocationLandingUrl(value) {
    const url = coerceUrl(value);
    if (!url) {
      return false;
    }

    try {
      const { pathname } = new URL(url);
      return /^\/(?:location|locations|service-area|service-areas|area|areas|city|cities)\/[a-z0-9-]+\/?$/i.test(pathname);
    } catch (_error) {
      return false;
    }
  }

  function isBlockedWebsiteUrl(value) {
    const url = coerceUrl(value);
    if (!url) {
      return true;
    }

    try {
      const { hostname } = new URL(url);
      return /(^|\.)google\./i.test(hostname) ||
        /(^|\.)gstatic\.com$/i.test(hostname) ||
        /(^|\.)googleusercontent\.com$/i.test(hostname) ||
        /(^|\.)tripadvisor\./i.test(hostname) ||
        /(^|\.)ubereats\.com$/i.test(hostname) ||
        /(^|\.)doordash\.com$/i.test(hostname) ||
        /(^|\.)grubhub\.com$/i.test(hostname) ||
        /(^|\.)postmates\.com$/i.test(hostname) ||
        /(^|\.)wolt\.com$/i.test(hostname) ||
        /(^|\.)bolt\.eu$/i.test(hostname) ||
        /(^|\.)foody\.com\.cy$/i.test(hostname) ||
        /(^|\.)deliveroo\./i.test(hostname) ||
        /(^|\.)opentable\./i.test(hostname) ||
        /(^|\.)resy\.com$/i.test(hostname) ||
        /(^|\.)tablecheck\.com$/i.test(hostname) ||
        /(^|\.)linktr\.ee$/i.test(hostname) ||
        /(^|\.)bit\.ly$/i.test(hostname);
    } catch (_error) {
      return true;
    }
  }

  function isLikelyOfficialWebsiteUrl(value, businessName = "", options = {}) {
    const url = coerceUrl(value);
    if (!url || isGoogleOwnedUrl(url) || isSocialUrl(url) || isBlockedWebsiteUrl(url) || /^mailto:|^tel:/i.test(url)) {
      return false;
    }

    if (isGenericLocationLandingUrl(url) && !urlMatchesBusinessName(url, businessName)) {
      return false;
    }

    if (options.requireNameMatch) {
      return urlMatchesBusinessName(url, businessName);
    }

    return true;
  }

  function chooseBestWebsite(urls, options = {}) {
    const businessName = normalizeText(options.businessName);
    const requireNameMatch = Boolean(options.requireNameMatch);
    const normalizedUrls = [...new Set((urls || []).map((url) => coerceUrl(url)).filter(Boolean))];
    const primary = normalizedUrls.find((url) => isLikelyOfficialWebsiteUrl(url, businessName, { requireNameMatch }));
    if (primary) {
      return primary;
    }

    if (requireNameMatch) {
      return "";
    }

    return normalizedUrls.find((url) => isLikelyOfficialWebsiteUrl(url, businessName)) || "";
  }

  function chooseWebsiteFromExplicitCandidates(urls, options = {}) {
    const businessName = normalizeText(options.businessName);
    const allowLooseFallback = options.allowLooseFallback !== false;
    const normalizedUrls = [...new Set((urls || []).map((url) => coerceUrl(url)).filter(Boolean))];
    const filteredUrls = normalizedUrls.filter((url) => !isGoogleOwnedUrl(url) && !/^mailto:|^tel:/i.test(url));
    if (!filteredUrls.length) {
      return "";
    }

    const matchedPrimary = filteredUrls.find((url) => isLikelyOfficialWebsiteUrl(url, businessName, { requireNameMatch: true }));
    if (matchedPrimary) {
      return matchedPrimary;
    }

    const cleanPrimary = filteredUrls.find((url) => isLikelyOfficialWebsiteUrl(url, businessName));
    if (cleanPrimary) {
      return cleanPrimary;
    }

    if (!allowLooseFallback) {
      return "";
    }

    const externalPrimary = filteredUrls.find((url) => !isSocialUrl(url));
    if (externalPrimary) {
      return externalPrimary;
    }

    return filteredUrls[0] || "";
  }

  function collectDocumentWideUrlCandidates(root = getDetailRoot()) {
    const urls = new Set();
    const scope = root && typeof root.querySelectorAll === "function" ? root : document;
    const nodes = uniqueElements([
      ...scope.querySelectorAll("a[href]"),
      ...scope.querySelectorAll("[data-item-id*='authority']"),
      ...scope.querySelectorAll("[aria-label*='Website']"),
      ...scope.querySelectorAll("[aria-label*='网站']"),
      ...scope.querySelectorAll("[href*='facebook.com'], [href*='instagram.com'], [href*='linkedin.com'], [href*='youtube.com'], [href*='youtu.be'], [href*='twitter.com'], [href*='x.com'], [href*='whatsapp.com'], [href*='wa.me']")
    ]);

    nodes.forEach((node) => {
      collectElementUrlCandidates(node, { includeTextContent: true }).forEach((url) => urls.add(url));
    });

    const htmlChunks = [...scope.querySelectorAll("script[type='application/ld+json']")]
      .slice(0, 12)
      .map((node) => String(node.textContent || "").slice(0, 30000))
      .filter((chunk) => /"url"|"sameAs"|https?:\/\//i.test(chunk));

    htmlChunks.forEach((chunk) => {
      extractUrlsFromText(chunk).forEach((url) => urls.add(url));
    });

    return [...urls];
  }

  function collectElementUrlCandidates(element, options = {}) {
    if (!element) {
      return [];
    }

    const includeTextContent = options.includeTextContent !== false;
    const values = new Set();
    const childNodes = element.querySelectorAll ? [...element.querySelectorAll("*")] : [];
    const nodes = [element, ...childNodes];

    nodes.forEach((node) => {
      if (!node || typeof node !== "object") {
        return;
      }

      if (typeof node.href === "string") {
        values.add(node.href);
      }

      [
        "data-href",
        "data-url",
        "data-value",
        "ping",
        "data-item-id",
        "aria-label",
        "title",
        "data-tooltip",
        "data-tooltip-text",
        "content"
      ].forEach((attribute) => {
        const attributeValue = node.getAttribute?.(attribute);
        if (attributeValue) {
          values.add(attributeValue);
        }
      });

      Array.from(node.attributes || []).forEach((attribute) => {
        if (/(https?:\/\/|\/\/|\/url\?(?:q|url)=|www\.|facebook|instagram|linkedin|youtube|twitter|x\.com|whatsapp|wa\.me)/i.test(attribute.value || "")) {
          values.add(attribute.value);
        }
      });

      const textContent = includeTextContent ? getTextFromNode(node) : "";
      if (textContent) {
        values.add(textContent);
      }
    });

    const urls = [];
    values.forEach((item) => {
      extractUrlsFromText(item).forEach((url) => urls.push(url));
    });

    return [...new Set(urls)];
  }

  function assignSocialFromUrl(socials, value) {
    const url = coerceUrl(value);
    if (!url) {
      return;
    }

    if (!socials.facebook && /facebook\.com|fb\.com/i.test(url)) {
      socials.facebook = url;
    }
    if (!socials.instagram && /instagram\.com|instagr\.am/i.test(url)) {
      socials.instagram = url;
    }
    if (!socials.linkedin && /linkedin\.com/i.test(url)) {
      socials.linkedin = url;
    }
    if (!socials.youtube && /youtube\.com|youtu\.be/i.test(url)) {
      socials.youtube = url;
    }
    if (!socials.whatsapp && /wa\.me|whatsapp\.com/i.test(url)) {
      socials.whatsapp = url;
    }
    if (!socials.twitter && /twitter\.com|x\.com/i.test(url)) {
      socials.twitter = url;
    }
  }

  function countFilledSocials(lead) {
    return SOCIAL_KEYS.filter((key) => cleanUrl(lead?.[key])).length;
  }

  function hasRichContactDetails(lead) {
    return Boolean(
      cleanUrl(lead?.website) ||
      normalizeText(lead?.email) ||
      countFilledSocials(lead)
    );
  }

  function needsContactRecovery(lead) {
    if (!lead) {
      return true;
    }

    const hasWebsite = Boolean(cleanUrl(lead.website));
    const hasEmail = Boolean(normalizeText(lead.email));
    const hasSocials = countFilledSocials(lead) > 0;
    const hasReliableIdentity = Boolean(
      normalizeText(lead?.placeId || lead?.placeID) ||
      normalizeText(lead?.cID || lead?.cid) ||
      buildCoordinateIdentity(lead)
    );
    const baseDetailCount = [
      normalizeText(lead?.name),
      normalizeText(lead?.address),
      normalizeText(lead?.phone),
      normalizeText(lead?.category)
    ].filter(Boolean).length;

    if (!hasReliableIdentity || baseDetailCount < 3) {
      return false;
    }

    if (!hasWebsite) {
      return true;
    }

    if (STATE.enrichEmail && !hasEmail && !hasSocials) {
      return true;
    }

    return false;
  }

  function getLeadCompletenessScore(lead) {
    if (!lead) {
      return 0;
    }

    let score = 0;
    if (normalizeText(lead.name)) {
      score += 1;
    }
    if (normalizeText(lead.address)) {
      score += 2;
    }
    if (normalizeText(lead.phone)) {
      score += 2;
    }
    if (normalizeText(lead.category)) {
      score += 1;
    }
    if (normalizeText(lead.rating || lead.averageRating)) {
      score += 1;
    }
    if (normalizeText(lead.reviewCount)) {
      score += 1;
    }
    if (cleanUrl(lead.website)) {
      score += 5;
    }
    if (normalizeText(lead.email)) {
      score += 4;
    }

    score += countFilledSocials(lead) * 3;
    return score;
  }

  function getSnapshotSourceRank(source) {
    switch (normalizeText(source).toLowerCase()) {
      case "dom":
        return 3;
      case "network":
        return 2;
      case "fallback":
        return 1;
      default:
        return 0;
    }
  }

  function shouldReplaceWebsite(currentWebsite, incomingWebsite, currentSource, incomingSource, businessName) {
    const currentUrl = cleanUrl(currentWebsite);
    const incomingUrl = cleanUrl(incomingWebsite);
    if (!incomingUrl) {
      return false;
    }

    const currentAccepted = isLikelyOfficialWebsiteUrl(currentUrl, businessName);
    const incomingAccepted = isLikelyOfficialWebsiteUrl(incomingUrl, businessName);
    if (incomingAccepted !== currentAccepted) {
      return incomingAccepted;
    }

    const currentMatchesName = urlMatchesBusinessName(currentUrl, businessName);
    const incomingMatchesName = urlMatchesBusinessName(incomingUrl, businessName);
    if (incomingMatchesName !== currentMatchesName) {
      return incomingMatchesName;
    }

    const currentSourceRank = getSnapshotSourceRank(currentSource);
    const incomingSourceRank = getSnapshotSourceRank(incomingSource);
    if (incomingSourceRank !== currentSourceRank) {
      return incomingSourceRank > currentSourceRank;
    }

    return incomingUrl.length > currentUrl.length;
  }

  function mergeLeadSnapshots(baseLead, incomingLead) {
    if (!baseLead) {
      return incomingLead ? { ...incomingLead } : null;
    }

    if (!incomingLead) {
      return { ...baseLead };
    }

    const merged = { ...baseLead };

    Object.entries(incomingLead).forEach(([key, rawValue]) => {
      if (rawValue === null || rawValue === undefined) {
        return;
      }

      if (typeof rawValue === "string") {
        const normalizedIncoming = normalizeText(rawValue);
        if (!normalizedIncoming) {
          return;
        }

        const normalizedCurrent = normalizeText(merged[key]);
        if (!normalizedCurrent) {
          merged[key] = rawValue;
          return;
        }

        if (key === "source") {
          if (getSnapshotSourceRank(normalizedIncoming) >= getSnapshotSourceRank(normalizedCurrent)) {
            merged[key] = normalizedIncoming;
          }
          return;
        }

        if (key === "website") {
          const currentWebsite = cleanUrl(normalizedCurrent);
          const incomingWebsite = cleanUrl(normalizedIncoming);
          const businessName = normalizeText(incomingLead?.name || baseLead?.name);
          if (shouldReplaceWebsite(currentWebsite, incomingWebsite, baseLead?.source, incomingLead?.source, businessName)) {
            merged[key] = incomingWebsite;
          }
          return;
        }

        if (SOCIAL_KEYS.includes(key)) {
          const currentSocial = cleanUrl(normalizedCurrent);
          const incomingSocial = cleanUrl(normalizedIncoming);
          if (!currentSocial && incomingSocial) {
            merged[key] = incomingSocial;
          }
          return;
        }

        if (normalizedIncoming.length > normalizedCurrent.length) {
          merged[key] = rawValue;
        }
        return;
      }

      if (Array.isArray(rawValue)) {
        const nextValues = new Set([...(Array.isArray(merged[key]) ? merged[key] : []), ...rawValue]);
        merged[key] = [...nextValues];
      }
    });

    return merged;
  }

  function getLeadKey(lead) {
    return (
      normalizeText(lead.placeId) ||
      normalizeText(lead.placeID) ||
      normalizeText(lead.cID || lead.cid) ||
      buildCoordinateIdentity(lead) ||
      cleanUrl(lead.sourceHref) ||
      cleanUrl(lead.googleMapsUrl) ||
      [
        normalizeText(lead.name),
        normalizeText(lead.address),
        normalizeText(lead.phone),
        cleanUrl(lead.website)
      ]
        .filter(Boolean)
        .join("|")
    );
  }

  function buildCoordinateIdentity(entity) {
    const latitude = normalizeText(entity?.latitude);
    const longitude = normalizeText(entity?.longitude);
    if (!latitude || !longitude) {
      return "";
    }

    return [
      latitude,
      longitude,
      normalizeText(entity?.name),
      normalizeText(entity?.phone)
    ].filter(Boolean).join("|");
  }

  function getLeadIdentityKeys(lead) {
    const normalizedLead = lead || {};
    return [
      normalizeText(normalizedLead.placeId || normalizedLead.placeID),
      normalizeText(normalizedLead.cID || normalizedLead.cid),
      buildCoordinateIdentity(normalizedLead),
      cleanUrl(normalizedLead.sourceHref),
      cleanUrl(normalizedLead.googleMapsUrl),
      getLeadKey(normalizedLead)
    ].filter(Boolean);
  }

  function getCandidateIdentityKeys(candidate) {
    const href = cleanUrl(candidate?.href || candidate?.sourceHref);
    return [
      normalizeText(candidate?.placeId || candidate?.placeID),
      normalizeText(candidate?.cID || candidate?.cid),
      buildCoordinateIdentity(candidate),
      normalizeText(parsePlaceId(href)),
      normalizeText(parseCid(href)),
      href,
      normalizeText(candidate?.key),
      normalizeText(candidate?.name)
    ].filter(Boolean);
  }

  function getKnownLeadForCandidate(candidate) {
    const identityKeys = getCandidateIdentityKeys(candidate);
    for (const key of identityKeys) {
      if (STATE.knownLeadMap.has(key)) {
        return STATE.knownLeadMap.get(key);
      }
    }

    return null;
  }

  function getKnownLeadForLead(lead) {
    const identityKeys = getLeadIdentityKeys(lead);
    for (const key of identityKeys) {
      if (STATE.knownLeadMap.has(key)) {
        return STATE.knownLeadMap.get(key);
      }
    }

    return null;
  }

  function hasEnoughStoredDetails(lead) {
    const baseDetailCount = [
      normalizeText(lead?.address),
      normalizeText(lead?.phone),
      normalizeText(lead?.category),
      normalizeText(lead?.rating || lead?.averageRating)
    ].filter(Boolean).length;

    const source = normalizeText(lead?.source).toLowerCase();
    const hasReliableIdentity = Boolean(
      normalizeText(lead?.placeId || lead?.placeID) ||
      normalizeText(lead?.cID || lead?.cid) ||
      buildCoordinateIdentity(lead)
    );
    return hasRichContactDetails(lead) || (hasReliableIdentity && source === "dom" && baseDetailCount >= 3);
  }

  function setStatus(text, tone = "neutral") {
    STATE.lastStatus = {
      text,
      tone
    };
    const panel = document.getElementById(PANEL_ID);
    if (!panel) {
      return;
    }

    const status = panel.querySelector("[data-role='status']");
    if (!status) {
      return;
    }

    status.textContent = text;
    status.dataset.tone = tone;
    scheduleCollectionStateSync();
  }

  function getSafeStatusMessage(error, fallbackText) {
    const message = String(error && error.message ? error.message : "").trim();
    if (!message) {
      return fallbackText;
    }

    if (/插件未激活|授权已到期|授权已停用|请求过于频繁|设备请求异常/.test(message)) {
      return message;
    }

    return fallbackText;
  }

  function setCount(value) {
    STATE.storedCount = Number(value || 0);
    const panel = document.getElementById(PANEL_ID);
    if (!panel) {
      return;
    }

    const count = panel.querySelector("[data-role='count']");
    if (count) {
      count.textContent = String(value);
    }
    scheduleCollectionStateSync();
  }

  function updateButtonState() {
    const panel = document.getElementById(PANEL_ID);
    if (!panel) {
      return;
    }

    const startBtn = panel.querySelector("[data-role='start']");
    const stopBtn = panel.querySelector("[data-role='stop']");
    const checkbox = panel.querySelector("[data-role='email-toggle']");

    if (startBtn) {
      startBtn.disabled = STATE.running;
    }

    if (stopBtn) {
      stopBtn.disabled = !STATE.running;
    }

    if (checkbox) {
      checkbox.disabled = STATE.running;
      checkbox.checked = STATE.enrichEmail;
    }
  }

  async function restoreActiveCollectionState() {
    const response = await sendMessage({ action: "GET_ACTIVE_COLLECTION_STATE" });
    if (!response?.ok || !response.collection?.collectionId) {
      return false;
    }

    const collection = response.collection;
    const pageQuery = normalizeText(getSearchQuery({ allowStateFallback: false })).toLowerCase();
    const collectionQuery = normalizeText(collection.query).toLowerCase();
    if (
      pageQuery &&
      collectionQuery &&
      !pageQuery.includes(collectionQuery) &&
      !collectionQuery.includes(pageQuery)
    ) {
      return false;
    }

    const state = collection.state || {};
    STATE.collectionId = normalizeText(collection.collectionId);
    STATE.currentQuery = normalizeText(collection.query);
    STATE.enrichEmail = collection.enrichEmail !== false;
    STATE.resumeRequested = Boolean(state.running);
    STATE.running = false;
    STATE.restoredDiagnostics.candidates = Number(state.diagnostics?.candidates || 0);
    STATE.restoredDiagnostics.queued = Number(state.diagnostics?.queued || 0);
    STATE.networkLeadCount = Number(state.diagnostics?.network || 0);
    STATE.resultsEndReached = Boolean(state.diagnostics?.end);
    Object.assign(STATE.runStats, {
      success: Number(state.runStats?.success || 0),
      deduped: Number(state.runStats?.deduped || 0),
      skipped: Number(state.runStats?.skipped || 0),
      failed: Number(state.runStats?.failed || 0),
      fallbackSaved: Number(state.runStats?.fallbackSaved || 0)
    });

    if (Number.isFinite(Number(state.count))) {
      setCount(Number(state.count));
    }

    updateRunStatsView();
    updateDiagnosticsView();
    updateButtonState();

    if (state.status?.text) {
      setStatus(state.status.text, state.status.tone || "neutral");
    }

    return true;
  }

  async function ensureResultsContext(options = {}) {
    if (canContinueFromResults()) {
      return true;
    }

    const returned = await returnToResults();
    if (returned) {
      return true;
    }

    if (options.allowNavigationRecovery === false) {
      return false;
    }

    const currentUrl = cleanUrl(location.href);
    const lastResultsUrl = cleanUrl(STATE.lastResultsUrl);
    const query = normalizeText(STATE.currentQuery || getSearchQuery({ allowStateFallback: true }));
    const targetUrl = lastResultsUrl || (query ? `https://www.google.com/maps/search/${encodeURIComponent(query)}` : "");
    if (!targetUrl || targetUrl === currentUrl) {
      return false;
    }

    STATE.resumeRequested = true;
    setStatus("正在恢复搜索结果列表，恢复后会继续采集。", "working");
    await flushCollectionStateSync().catch(() => {});
    location.assign(targetUrl);
    return false;
  }

  function parseCoordinates(urlText) {
    const text = String(urlText || "");
    const bangMatches = [...text.matchAll(/!3d(-?\d+\.\d+)!4d(-?\d+\.\d+)/g)];
    if (bangMatches.length) {
      const matchBang = bangMatches[bangMatches.length - 1];
      return {
        latitude: matchBang[1],
        longitude: matchBang[2]
      };
    }

    const atMatches = [...text.matchAll(/@(-?\d+\.\d+),(-?\d+\.\d+)/g)];
    if (atMatches.length) {
      const matchAt = atMatches[atMatches.length - 1];
      return {
        latitude: matchAt[1],
        longitude: matchAt[2]
      };
    }

    return { latitude: "", longitude: "" };
  }

  function parsePlaceId(urlText) {
    const text = String(urlText || "");
    const queryMatch = text.match(/[?&](?:query_place_id|ftid)=([^&#]+)/i);
    if (queryMatch) {
      return decodeURIComponent(queryMatch[1]);
    }

    const placeMatches = [...text.matchAll(/!(?:1|19)s([^!]+)/g)].map((match) => decodeURIComponent(match[1]));
    const exactPlaceMatch = placeMatches.find((value) => /^0x[0-9a-f]+:0x[0-9a-f]+$/i.test(value));
    if (exactPlaceMatch) {
      return exactPlaceMatch;
    }

    const featurePlaceMatch = placeMatches.find((value) => /^\/g\//i.test(value));
    if (featurePlaceMatch) {
      return featurePlaceMatch;
    }

    if (placeMatches.length) {
      return placeMatches[placeMatches.length - 1];
    }

    const featureMatch = text.match(/!16s([^!]+)/);
    if (featureMatch) {
      return decodeURIComponent(featureMatch[1]);
    }

    return "";
  }

  function parseCid(urlText) {
    const text = String(urlText || "");
    const cidMatch = text.match(/[?&]cid=([0-9]+)/i);
    return cidMatch ? cidMatch[1] : "";
  }

  function isPlaceDetailUrl(urlText) {
    const text = cleanUrl(urlText);
    if (!text) {
      return false;
    }

    return (
      text.includes("/maps/place/") ||
      /[?&](?:cid|query_place_id|ftid)=/i.test(text) ||
      /!(?:1|16|19)s/.test(text)
    );
  }

  function pickDetailUrl(fallbackCandidate) {
    const currentUrl = cleanUrl(location.href);
    const fallbackUrl = cleanUrl(fallbackCandidate?.href || fallbackCandidate?.sourceHref);
    const lastResultsUrl = cleanUrl(STATE.lastResultsUrl);

    if (!fallbackUrl) {
      return currentUrl;
    }

    if (!currentUrl || currentUrl === lastResultsUrl || !isPlaceDetailUrl(currentUrl)) {
      return fallbackUrl;
    }

    const currentPlaceId = parsePlaceId(currentUrl);
    const fallbackPlaceId = parsePlaceId(fallbackUrl);
    const currentCid = parseCid(currentUrl);
    const fallbackCid = parseCid(fallbackUrl);

    if (
      (!currentPlaceId && fallbackPlaceId) ||
      (!currentCid && fallbackCid) ||
      (fallbackPlaceId && currentPlaceId && fallbackPlaceId !== currentPlaceId) ||
      (fallbackCid && currentCid && fallbackCid !== currentCid) ||
      /!2m1!1s/i.test(currentUrl)
    ) {
      return fallbackUrl;
    }

    return currentUrl;
  }

  function queryOne(selectors, root = document) {
    for (const selector of selectors) {
      const element = root.querySelector(selector);
      if (element) {
        return element;
      }
    }

    return null;
  }

  function queryAll(selectors, root = document) {
    const result = [];

    selectors.forEach((selector) => {
      result.push(...root.querySelectorAll(selector));
    });

    return result;
  }

  function uniqueElements(elements) {
    return [...new Set((elements || []).filter(Boolean))];
  }

  function getTextFromNode(node) {
    if (!node) {
      return "";
    }

    return normalizeText(node.innerText || node.textContent || "");
  }

  function normalizeComparableText(value) {
    return normalizeText(value)
      .toLowerCase()
      .replace(/&/g, " and ")
      .replace(/[^a-z0-9\u4e00-\u9fff]+/g, "");
  }

  function isLikelySameBusinessName(left, right) {
    const normalizedLeft = normalizeComparableText(left);
    const normalizedRight = normalizeComparableText(right);
    if (!normalizedLeft || !normalizedRight) {
      return false;
    }

    return (
      normalizedLeft === normalizedRight ||
      normalizedLeft.includes(normalizedRight) ||
      normalizedRight.includes(normalizedLeft)
    );
  }

  function extractLabeledText(element, labels) {
    const rawText = getTextFromNode(element);
    const ariaLabel = normalizeText(element?.getAttribute?.("aria-label"));
    const combined = rawText || ariaLabel;

    if (!combined) {
      return "";
    }

    const matchedLabel = labels.find((label) => combined.toLowerCase().startsWith(label.toLowerCase()));
    if (matchedLabel) {
      return normalizeText(combined.slice(matchedLabel.length).replace(/^[:：\s-]+/, ""));
    }

    return combined;
  }

  function findResultsFeed() {
    return document.querySelector("div[role='feed']");
  }

  function hasReachedEndOfResults(feed = findResultsFeed()) {
    if (!feed) {
      return false;
    }

    if (feed.querySelector(".HlvSq")) {
      return true;
    }

    const text = normalizeText(feed.innerText || "");
    if (!text) {
      return false;
    }

    return /没有其他结果了|已显示全部结果|you['’]ve reached the end of the list|end of the list|no more results/i.test(text);
  }

  function canContinueFromResults() {
    const feed = findResultsFeed();
    if (!feed) {
      return false;
    }

    const candidateCount = collectResultCandidates().length;
    return candidateCount > 0 || feed.children.length > 0 || feed.scrollHeight > feed.clientHeight;
  }

  function stopResultsObserver() {
    if (STATE.resultsObserver) {
      STATE.resultsObserver.disconnect();
      STATE.resultsObserver = null;
    }
  }

  function startResultsObserver() {
    stopResultsObserver();

    const feed = findResultsFeed();
    if (!feed || typeof MutationObserver !== "function") {
      return;
    }

    const root = feed.parentElement?.parentElement || feed.parentElement || feed;
    STATE.resultsObserver = new MutationObserver(() => {
      if (!STATE.running) {
        return;
      }

      STATE.resultsListDirty = true;
      queueVisibleCandidates();
    });

    STATE.resultsObserver.observe(root, {
      childList: true,
      subtree: true
    });
  }

  function disableMapMoveResultUpdates() {
    const pattern = /移动地图时更新搜索结果|update results when map moves/i;
    const candidates = uniqueElements([
      ...document.querySelectorAll("label"),
      ...document.querySelectorAll("input[type='checkbox']"),
      ...document.querySelectorAll("[role='checkbox']"),
      ...document.querySelectorAll("button[role='switch']"),
      ...document.querySelectorAll("[aria-label]")
    ]);

    for (const node of candidates) {
      const container =
        node.closest?.("label") ||
        node.closest?.("[role='checkbox']") ||
        node.parentElement ||
        node;
      const text = normalizeText(
        [
          container?.innerText,
          container?.textContent,
          container?.getAttribute?.("aria-label"),
          node?.getAttribute?.("aria-label")
        ].filter(Boolean).join(" ")
      );

      if (!pattern.test(text)) {
        continue;
      }

      const checkbox =
        (container?.querySelector?.("input[type='checkbox']")) ||
        (node.matches?.("input[type='checkbox']") ? node : null);
      if (checkbox) {
        if (checkbox.checked) {
          (container.click ? container : checkbox).click();
          return true;
        }

        return false;
      }

      const roleCheckbox =
        (container?.querySelector?.("[role='checkbox']")) ||
        (node.getAttribute?.("role") === "checkbox" ? node : null);
      if (roleCheckbox) {
        const isChecked = roleCheckbox.getAttribute("aria-checked") === "true";
        if (isChecked) {
          roleCheckbox.click();
          return true;
        }

        return false;
      }
    }

    return false;
  }

  function getResultsPageUrl() {
    const url = new URL(location.href);
    return `${url.origin}${url.pathname}${url.search}`;
  }

  function getSearchQuery(options = {}) {
    const queryFromInput = normalizeText(document.querySelector("#searchboxinput")?.value || "");
    if (queryFromInput) {
      return queryFromInput;
    }

    if (options.allowStateFallback !== false) {
      return normalizeText(STATE.currentQuery);
    }

    return "";
  }

  function getResultCardText(card) {
    return normalizeText([
      card?.getAttribute?.("aria-label"),
      card?.querySelector?.("div.fontHeadlineSmall")?.textContent,
      card?.querySelector?.("[role='heading']")?.textContent,
      card?.innerText
    ].filter(Boolean).join(" "));
  }

  function getRawResultCardText(card) {
    return [
      card?.getAttribute?.("aria-label"),
      card?.querySelector?.("div.fontHeadlineSmall")?.textContent,
      card?.querySelector?.("[role='heading']")?.textContent,
      card?.innerText
    ].filter(Boolean).join("\n");
  }

  function collectResultCandidates() {
    const feed = findResultsFeed();
    const searchQuery = getSearchQuery();
    const searchTokens = searchQuery
      .toLowerCase()
      .split(/\s+/)
      .map((token) => token.trim())
      .filter(Boolean)
      .slice(0, 6);

    function addCandidate(map, candidate) {
      const key = normalizeText(candidate?.key);
      if (!key || map.has(key)) {
        return;
      }

      map.set(key, candidate);
    }

    function isLikelyLeftPaneElement(element) {
      if (!element || typeof element.getBoundingClientRect !== "function") {
        return false;
      }

      const rect = element.getBoundingClientRect();
      if (rect.width <= 0 || rect.height <= 0) {
        return false;
      }

      if (rect.right > window.innerWidth * 0.52) {
        return false;
      }

      if (rect.bottom < 72 || rect.top > window.innerHeight - 24) {
        return false;
      }

      return true;
    }

    function looksRelevantToSearch(text) {
      const normalizedText = normalizeText(text).toLowerCase();
      if (!normalizedText) {
        return false;
      }

      if (!searchTokens.length) {
        return true;
      }

      const matchCount = searchTokens.filter((token) => normalizedText.includes(token)).length;
      return matchCount >= Math.min(2, searchTokens.length);
    }

    function buildCandidateFromCard(card, index) {
      const anchor = queryOne([
        "a[href*='/maps/place/']",
        "a[href*='!1s0x']",
        "a[href*='query_place_id=']",
        "a[href*='ftid=']",
        "a.hfpxzc"
      ], card);
      const href = cleanUrl(anchor?.href);
      const rawDescription = getRawResultCardText(card);
      const lines = splitCandidateDescription(rawDescription);
      const coordinates = parseCoordinates(href);
      const placeId = parsePlaceId(href);
      const cid = parseCid(href);
      const phone = extractPhoneFromText(rawDescription);
      const address = extractAddressFromCandidateLines(lines);
      const title = normalizeText(
        anchor?.getAttribute?.("aria-label") ||
          card?.querySelector?.("div.fontHeadlineSmall")?.textContent ||
          card?.querySelector?.("[role='heading']")?.textContent
      );
      const description = normalizeText(rawDescription);
      const fallbackTitle = splitCandidateDescription(rawDescription)[0] || "";
      const resolvedTitle = title || fallbackTitle;
      const key = href || [resolvedTitle, description.slice(0, 120), String(index)].filter(Boolean).join("|");

      if (!key) {
        return null;
      }

      return {
        key,
        href,
        name: resolvedTitle,
        placeId,
        placeID: placeId,
        cID: cid,
        cid,
        latitude: coordinates.latitude,
        longitude: coordinates.longitude,
        address,
        phone,
        element: anchor || card,
        card,
        description: rawDescription
      };
    }

    function buildCandidateFromAnchor(anchor, index) {
      const href = cleanUrl(anchor?.href);
      if (!href) {
        return null;
      }

      const card = anchor.closest("[role='article']") ||
        anchor.closest("div.Nv2PK") ||
        anchor.closest("div[jsaction*='mouseover:pane']") ||
        anchor.parentElement ||
        anchor;
      const rawDescription = getRawResultCardText(card);
      const lines = splitCandidateDescription(rawDescription);
      const coordinates = parseCoordinates(href);
      const placeId = parsePlaceId(href);
      const cid = parseCid(href);
      const phone = extractPhoneFromText(rawDescription);
      const address = extractAddressFromCandidateLines(lines);
      const title = normalizeText(
        anchor.getAttribute("aria-label") ||
          card?.querySelector?.("div.fontHeadlineSmall")?.textContent ||
          card?.querySelector?.("[role='heading']")?.textContent
      );
      const fallbackTitle = splitCandidateDescription(rawDescription)[0] || "";

      return {
        key: href || `${title || fallbackTitle}|${index}`,
        href,
        name: title || fallbackTitle,
        placeId,
        placeID: placeId,
        cID: cid,
        cid,
        latitude: coordinates.latitude,
        longitude: coordinates.longitude,
        address,
        phone,
        element: anchor,
        card,
        description: rawDescription
      };
    }

    function shouldKeepAnchor(anchor) {
      if (!anchor) {
        return false;
      }

      if (feed && feed.contains(anchor)) {
        return true;
      }

      if (!isLikelyLeftPaneElement(anchor)) {
        return false;
      }

      const card = anchor.closest("[role='article']") ||
        anchor.closest("div.Nv2PK") ||
        anchor.closest("div[jsaction*='mouseover:pane']") ||
        anchor.parentElement ||
        anchor;

      if (!isLikelyLeftPaneElement(card)) {
        return false;
      }

      const combinedText = [
        anchor.getAttribute("aria-label"),
        getRawResultCardText(card)
      ].filter(Boolean).join(" ");

      return looksRelevantToSearch(combinedText);
    }

    const map = new Map();

    if (feed) {
      const cards = uniqueElements([
        ...feed.querySelectorAll("[role='article']"),
        ...feed.querySelectorAll("div.Nv2PK"),
        ...feed.querySelectorAll("div[jsaction*='mouseover:pane']")
      ]);

      cards.forEach((card, index) => {
        addCandidate(map, buildCandidateFromCard(card, index));
      });
    }

    const anchors = uniqueElements([
      ...document.querySelectorAll("a[href*='/maps/place/']"),
      ...document.querySelectorAll("a[href*='!1s0x']"),
      ...document.querySelectorAll("a[href*='query_place_id=']"),
      ...document.querySelectorAll("a[href*='ftid=']"),
      ...document.querySelectorAll("a.hfpxzc")
    ]).filter(shouldKeepAnchor);

    anchors.forEach((anchor, index) => {
      addCandidate(map, buildCandidateFromAnchor(anchor, index));
    });

    return [...map.values()];
  }

  function splitCandidateDescription(text) {
    return String(text || "")
      .split(/\n+/)
      .map((line) => normalizeText(line))
      .filter(Boolean);
  }

  function extractPhoneFromText(text) {
    const match = normalizeText(text).match(/(\+?\d[\d\s().-]{7,}\d)/);
    return match ? cleanPhoneText(match[1]) : "";
  }

  function extractAddressFromCandidateLines(lines) {
    return lines.find((line) => {
      return /(\b\d{1,6}\b.*[,，].*)|([A-Za-z]+,?\s+[A-Z]{2}\s+\d{4,6})|(\d+.*(?:street|st\b|road|rd\b|avenue|ave\b|blvd|boulevard|lane|ln\b|drive|dr\b|way\b|suite|floor))|(路|街|道|号|区|市|省|镇)/i.test(line);
    }) || "";
  }

  function extractCategoryFromCandidateLines(lines, address, phone) {
    return lines.find((line) => {
      if (!line || line === address || line === phone) {
        return false;
      }

      return !/reviews?|评论|评价|星|closed|open|营业|phone|电话|website|网站|hours|route|路线/i.test(line) && line.length <= 40;
    }) || "";
  }

  function buildFallbackLead(candidate) {
    const name = normalizeText(candidate?.name);
    const href = cleanUrl(candidate?.href || candidate?.sourceHref);
    if (!name) {
      return null;
    }

    const description = getRawResultCardText(candidate?.card || candidate?.element || null) || String(candidate?.description || "");
    const lines = splitCandidateDescription(description).filter((line) => !isLikelySameBusinessName(line, name));
    const phone = extractPhoneFromText(description);
    const address = cleanExtractedAddress(extractAddressFromCandidateLines(lines));
    const category = cleanExtractedCategory(extractCategoryFromCandidateLines(lines, address, phone));
    const coordinates = parseCoordinates(href);
    const placeId = parsePlaceId(href);
    const cid = parseCid(href);

    return {
      query: getSearchQuery(),
      name,
      category,
      address,
      phone,
      website: "",
      rating: "",
      averageRating: "",
      reviewCount: "",
      cID: cid,
      cid,
      placeId,
      placeID: placeId,
      googleMapsUrl: href,
      sourceHref: href || "",
      source: "fallback",
      latitude: coordinates.latitude,
      longitude: coordinates.longitude,
      email: ""
    };
  }

  function hasDetailHeader() {
    const heading = queryOne([
      "h1.DUwDvf",
      "h1"
    ]);

    return Boolean(getTextFromNode(heading));
  }

  function getDetailHeadingText() {
    return getTextFromNode(queryOne([
      "h1.DUwDvf",
      "h1"
    ]));
  }

  function getDetailHeadingNode() {
    return queryOne([
      "h1.DUwDvf",
      "h1"
    ]);
  }

  function getDetailRoot() {
    const heading = getDetailHeadingNode();
    let current = heading?.parentElement || null;

    while (current && current !== document.body) {
      if (
        current.querySelector(
          "[data-item-id='address'], [data-item-id*='address'], [data-item-id^='phone:tel:'], [data-item-id*='phone'], a[data-item-id*='authority'], a[href^='tel:'], a[href*='whatsapp'], a[href*='wa.me']"
        )
      ) {
        return current;
      }

      current = current.parentElement;
    }

    return queryOne([
      "div[role='main']",
      "[role='main']",
      "div.m6QErb[aria-label]"
    ]) || document;
  }

  function getDetailLinks(root = getDetailRoot()) {
    return uniqueElements([
      ...queryAll(["a[href]"], root),
      ...queryAll(["button[data-item-id*='authority']", "button[data-item-id*='phone']"], root)
    ]);
  }

  function getLabeledDetailRows(root = getDetailRoot()) {
    return uniqueElements([
      ...queryAll([
        "[data-item-id]",
        "button[aria-label]",
        "a[aria-label]",
        "button",
        "a[href]"
      ], root)
    ]);
  }

  function getDetailSignature() {
    const currentUrl = cleanUrl(location.href);
    const placeId = parsePlaceId(currentUrl);
    const cid = parseCid(currentUrl);
    const heading = getDetailHeadingText();
    return [placeId, cid, normalizeComparableText(heading), currentUrl].filter(Boolean).join("|");
  }

  function isCandidateDetailReady(candidate, previousSignature = "") {
    const heading = getDetailHeadingText();
    if (!heading) {
      return false;
    }

    const currentUrl = cleanUrl(location.href);
    const currentPlaceId = parsePlaceId(currentUrl);
    const currentCid = parseCid(currentUrl);
    const candidateUrl = cleanUrl(candidate?.href);
    const candidatePlaceId = parsePlaceId(candidateUrl);
    const candidateCid = parseCid(candidateUrl);
    const signature = getDetailSignature();
    const changed = !previousSignature || signature !== previousSignature;
    const samePlace =
      (candidatePlaceId && currentPlaceId && candidatePlaceId === currentPlaceId) ||
      (candidateCid && currentCid && candidateCid === currentCid);
    const sameName = isLikelySameBusinessName(heading, candidate?.name);

    if (!candidate?.name && !candidatePlaceId && !candidateCid) {
      return changed;
    }

    return changed && (samePlace || sameName);
  }

  async function waitFor(predicate, timeoutMs = 10000, intervalMs = 250) {
    const startedAt = Date.now();

    while (Date.now() - startedAt < timeoutMs) {
      const value = predicate();
      if (value) {
        return value;
      }

      await sleep(intervalMs);
    }

    return null;
  }

  function extractRatingAndReviews() {
    const candidates = queryAll([
      "[role='main'] [role='img'][aria-label*='star']",
      "[role='main'] [role='img'][aria-label*='Stars']",
      "[role='main'] [role='img'][aria-label*='星']",
      "[role='main'] button[aria-label*='review']",
      "[role='main'] button[aria-label*='评论']",
      "[role='main'] button[aria-label*='评价']"
    ]);

    let rating = "";
    let reviewCount = "";

    candidates.forEach((node) => {
      const text = normalizeText(node.getAttribute("aria-label") || node.textContent || "");
      if (!text) {
        return;
      }

      if (!rating) {
        const ratingMatch = text.match(/([0-9]+(?:[.,][0-9]+)?)/);
        if (ratingMatch) {
          rating = ratingMatch[1].replace(",", ".");
        }
      }

      if (!reviewCount) {
        const reviewMatch = text.match(/([0-9][0-9,.\s]*)\s*(reviews|review|条评论|则评论|则评价)/i);
        if (reviewMatch) {
          reviewCount = normalizeText(reviewMatch[1]).replace(/[^\d]/g, "");
        } else {
          const fallbackMatch = text.match(/\(([0-9][0-9,.\s]*)\)/);
          if (fallbackMatch) {
            reviewCount = normalizeText(fallbackMatch[1]).replace(/[^\d]/g, "");
          }
        }
      }
    });

    return { rating, reviewCount };
  }

  function removePrivateUseCharacters(value) {
    return normalizeText(String(value || "").replace(/[\uE000-\uF8FF]/g, " "));
  }

  function looksLikeAddressText(value) {
    const text = normalizeText(value);
    return /(\b\d{1,6}\b.*[,，].*)|([A-Za-z]+,?\s+[A-Z]{2}\s+\d{4,6})|(\d+.*(?:street|st\b|road|rd\b|avenue|ave\b|blvd|boulevard|lane|ln\b|drive|dr\b|way\b|suite|floor|jalan|jln|lot|km|kampung|persiaran|taman|off\b))|(路|街|道|号|区|市|省|镇)/i.test(text);
  }

  function cleanExtractedCategory(value) {
    const text = removePrivateUseCharacters(value)
      .replace(/\s*[·•]\s*/g, " ")
      .replace(/\s+/g, " ")
      .trim();

    if (!text) {
      return "";
    }

    if (/^(route|路线|website|网站|电话|phone|menu|菜单|share|分享)$/i.test(text)) {
      return "";
    }

    if (/\b(?:reviews?|评论|评价|stars?|评分)\b/i.test(text) || /\d\.\d/.test(text)) {
      return "";
    }

    if (looksLikeAddressText(text) || text.length > 48) {
      return "";
    }

    return text;
  }

  function cleanExtractedAddress(value, category = "") {
    let text = removePrivateUseCharacters(value)
      .replace(/\s*[•·]\s*/g, " · ")
      .replace(/\s+/g, " ")
      .trim();

    if (!text || /^(route|路线)$/i.test(text)) {
      return "";
    }

    const normalizedCategory = cleanExtractedCategory(category);
    if (normalizedCategory) {
      const prefixRegex = new RegExp(`^${escapeRegExp(normalizedCategory)}\\s*·\\s*`, "i");
      text = text.replace(prefixRegex, "").trim();
    }

    const segments = text.split(/\s*·\s*/).map((segment) => normalizeText(segment)).filter(Boolean);
    if (segments.length > 1) {
      const firstSegmentCategory = cleanExtractedCategory(segments[0]);
      const remaining = segments.slice(1).join(" · ");
      if (firstSegmentCategory && looksLikeAddressText(remaining)) {
        text = remaining;
      }
    }

    return text.replace(/^[^\p{L}\p{N}#]+/u, "").trim();
  }

  function extractCategory() {
    const element = queryOne([
      "button[jsaction*='pane.rating.category']",
      "button[jsaction*='category']",
      "[role='main'] button.DkEaL",
      "[role='main'] .skqShb"
    ]);

    return cleanExtractedCategory(getTextFromNode(element));
  }

  function extractAddress() {
    const root = getDetailRoot();
    const node = queryOne([
      "[data-item-id='address']",
      "[data-item-id*='address']",
      "button[aria-label^='Address:']",
      "button[aria-label^='地址：']",
      "button[aria-label^='地址:']",
      "button[data-tooltip*='address']",
      "div[role='main'] button[aria-label*='Address']"
    ], root);

    if (node) {
      return cleanExtractedAddress(extractLabeledText(node, ["Address", "地址"]));
    }

    const candidates = getLabeledDetailRows(root);

    const row = candidates.find((element) => {
      const text = `${getTextFromNode(element)} ${normalizeText(element.getAttribute?.("aria-label"))}`;
      return /(\b\d{1,6}\b.*[,，].*)|([A-Za-z]+,?\s+[A-Z]{2}\s+\d{4,6})/.test(text);
    });

    return cleanExtractedAddress(getTextFromNode(row));
  }

  function extractWebsite(businessName = "") {
    const root = getDetailRoot();
    const preferredNodes = uniqueElements([
      ...queryAll([
        "[data-item-id='authority']",
        "[data-item-id*='authority']",
        "a[aria-label*='Website']",
        "a[aria-label*='网站']",
        "a[aria-label^='Website:']",
        "a[aria-label^='网站：']",
        "a[aria-label^='网站:']",
        "button[aria-label*='Website']",
        "button[aria-label*='网站']",
        "button[aria-label^='Website:']",
        "button[aria-label^='网站：']",
        "button[aria-label^='网站:']",
        "a[data-tooltip='Open website']",
        "a[data-tooltip*='website']",
        "button[data-tooltip='Open website']",
        "button[data-tooltip*='website']",
        "[data-tooltip*='Website']",
        "[data-tooltip*='website']",
        "[title*='Website']",
        "[title*='网站']"
      ], root),
      ...getLabeledDetailRows(root).filter((element) => {
        const label = `${getTextFromNode(element)} ${normalizeText(element.getAttribute?.("aria-label"))}`.toLowerCase();
        return /website|site|官网|网站|official/.test(label);
      })
    ]);

    const preferredUrls = preferredNodes.flatMap((element) => {
      const directValues = [
        extractLabeledText(element, ["Website", "网站"]),
        normalizeText(element.getAttribute?.("aria-label")),
        normalizeText(element.getAttribute?.("title")),
        normalizeText(element.getAttribute?.("data-tooltip")),
        normalizeText(element.getAttribute?.("data-value"))
      ].filter(Boolean);

      const directUrls = directValues.flatMap((value) => extractUrlsFromText(value));
      return [...collectElementUrlCandidates(element, { includeTextContent: true }), ...directUrls];
    });

    const bestPreferred = chooseWebsiteFromExplicitCandidates(preferredUrls, {
      businessName,
      allowLooseFallback: true
    });
    if (bestPreferred) {
      return bestPreferred;
    }

    const fallbackUrls = uniqueElements([
      ...getDetailLinks(root),
      ...getLabeledDetailRows(root)
    ]).flatMap((element) => collectElementUrlCandidates(element, { includeTextContent: false }));

    const fallbackWebsite = chooseBestWebsite(fallbackUrls, {
      businessName,
      requireNameMatch: true
    });
    if (fallbackWebsite) {
      return fallbackWebsite;
    }

    return chooseBestWebsite(collectDocumentWideUrlCandidates(), {
      businessName,
      requireNameMatch: true
    });
  }

  function cleanPhoneText(value) {
    return normalizeText(String(value || "").replace(/^[^\d+()]+/, ""));
  }

  function extractPhone() {
    const root = getDetailRoot();
    const node = queryOne([
      "button[data-item-id^='phone:tel:']",
      "button[data-item-id*='phone']",
      "button[aria-label^='Phone:']",
      "button[aria-label^='电话：']",
      "button[aria-label^='电话:']",
      "a[href^='tel:']",
      "button[aria-label*='Call']",
      "button[aria-label*='电话']"
    ], root);

    if (node) {
      return cleanPhoneText(extractLabeledText(node, ["Phone", "电话"]));
    }

    const telLink = getDetailLinks(root).find((element) => /^tel:/i.test(element.href || ""));
    if (telLink?.href) {
      return cleanPhoneText(telLink.href.replace(/^tel:/i, ""));
    }

    const candidates = getLabeledDetailRows(root);

    const row = candidates.find((element) => {
      const text = `${getTextFromNode(element)} ${normalizeText(element.getAttribute?.("aria-label"))}`;
      return /(\+?\d[\d\s().-]{7,}\d)/.test(text);
    });
    const text = `${getTextFromNode(row)} ${normalizeText(row?.getAttribute?.("aria-label"))}`;
    const match = text.match(/(\+?\d[\d\s().-]{7,}\d)/);
    return match ? cleanPhoneText(match[1]) : "";
  }

  function extractSocialsFromDetail() {
    const root = getDetailRoot();
    const socials = {
      facebook: "",
      instagram: "",
      linkedin: "",
      youtube: "",
      whatsapp: "",
      twitter: ""
    };

    const candidateNodes = uniqueElements([
      ...getDetailLinks(root),
      ...getLabeledDetailRows(root).filter((element) => {
        const text = `${getTextFromNode(element)} ${normalizeText(element.getAttribute?.("aria-label"))}`;
        return /facebook|instagram|linkedin|youtube|youtu\.be|twitter|x\.com|whatsapp|wa\.me/i.test(text);
      })
    ]);

    candidateNodes.forEach((element) => {
      collectElementUrlCandidates(element, { includeTextContent: false }).forEach((url) => assignSocialFromUrl(socials, url));
    });

    if (!Object.values(socials).some(Boolean)) {
      collectDocumentWideUrlCandidates().forEach((url) => assignSocialFromUrl(socials, url));
    }

    return socials;
  }

  function markCandidateProcessed(candidateKey) {
    if (candidateKey) {
      STATE.attemptedResultKeys.add(candidateKey);
      STATE.queuedCandidateKeys.delete(candidateKey);
      STATE.failedResultKeys.delete(candidateKey);
    }
  }

  function markCandidateFailed(candidateKey) {
    if (!candidateKey) {
      return;
    }

    const nextCount = (STATE.failedResultKeys.get(candidateKey) || 0) + 1;
    STATE.failedResultKeys.set(candidateKey, nextCount);

    if (STATE.queuedCandidateKeys.has(candidateKey)) {
      STATE.queuedCandidateKeys.delete(candidateKey);
      if (nextCount < 2) {
        STATE.queuedCandidateKeys.add(candidateKey);
      }
    }

    if (nextCount >= 2) {
      STATE.attemptedResultKeys.add(candidateKey);
      STATE.queuedCandidateKeys.delete(candidateKey);
    }
  }

  function buildCandidateKey(candidate) {
    return (
      normalizeText(candidate?.key) ||
      normalizeText(candidate?.placeId || candidate?.placeID) ||
      normalizeText(candidate?.cID || candidate?.cid) ||
      buildCoordinateIdentity(candidate) ||
      cleanUrl(candidate?.href || candidate?.sourceHref || candidate?.googleMapsUrl) ||
      [
        normalizeText(candidate?.name),
        normalizeText(candidate?.address),
        normalizeText(candidate?.phone)
      ]
        .filter(Boolean)
        .join("|")
    );
  }

  function registerCandidate(candidate) {
    const key = buildCandidateKey(candidate);
    if (!key) {
      return { candidate: null, isNewSeen: false };
    }

    const existingCandidate = STATE.candidateMap.get(key) || {};
    const nextCandidate = {
      ...existingCandidate,
      ...candidate,
      key,
      href: cleanUrl(candidate?.href || candidate?.sourceHref || candidate?.googleMapsUrl || existingCandidate.href),
      sourceHref: cleanUrl(candidate?.sourceHref || candidate?.href || candidate?.googleMapsUrl || existingCandidate.sourceHref),
      googleMapsUrl: cleanUrl(candidate?.googleMapsUrl || candidate?.sourceHref || candidate?.href || existingCandidate.googleMapsUrl),
      name: normalizeText(candidate?.name || existingCandidate.name),
      address: normalizeText(candidate?.address || existingCandidate.address),
      phone: normalizeText(candidate?.phone || existingCandidate.phone),
      latitude: normalizeText(candidate?.latitude || existingCandidate.latitude),
      longitude: normalizeText(candidate?.longitude || existingCandidate.longitude),
      placeId: normalizeText(candidate?.placeId || candidate?.placeID || existingCandidate.placeId || existingCandidate.placeID),
      cID: normalizeText(candidate?.cID || candidate?.cid || existingCandidate.cID || existingCandidate.cid)
    };

    nextCandidate.placeID = nextCandidate.placeId;
    nextCandidate.cid = nextCandidate.cID;

    if (!candidate?.element && existingCandidate.element) {
      nextCandidate.element = existingCandidate.element;
    }

    if (!candidate?.card && existingCandidate.card) {
      nextCandidate.card = existingCandidate.card;
    }

    STATE.candidateMap.set(key, nextCandidate);
    updateDiagnosticsView();

    const isNewSeen = !STATE.seenResultKeys.has(key);
    if (isNewSeen) {
      STATE.seenResultKeys.add(key);
    }

    if (!STATE.attemptedResultKeys.has(key)) {
      STATE.queuedCandidateKeys.add(key);
    }

    return {
      candidate: nextCandidate,
      isNewSeen
    };
  }

  function queueVisibleCandidates() {
    const visibleCandidates = collectResultCandidates();
    let newlySeenCount = 0;

    visibleCandidates.forEach((candidate) => {
      const registered = registerCandidate(candidate);
      if (registered.isNewSeen) {
        newlySeenCount += 1;
      }
    });

    return {
      visibleCount: visibleCandidates.length,
      seenCount: STATE.seenResultKeys.size,
      newlySeenCount
    };
  }

  function getNextQueuedCandidate() {
    for (const key of STATE.queuedCandidateKeys) {
      if (STATE.attemptedResultKeys.has(key)) {
        STATE.queuedCandidateKeys.delete(key);
        continue;
      }

      if (STATE.candidateMap.has(key)) {
        return STATE.candidateMap.get(key);
      }
    }

    const visible = collectResultCandidates();
    return visible.find((candidate) => !STATE.attemptedResultKeys.has(candidate.key)) || null;
  }

  function isSameCandidate(left, right) {
    if (!left || !right) {
      return false;
    }

    const leftKey = buildCandidateKey(left);
    const rightKey = buildCandidateKey(right);
    if (leftKey && rightKey && leftKey === rightKey) {
      return true;
    }

    const leftPlaceId = normalizeText(left.placeId || left.placeID);
    const rightPlaceId = normalizeText(right.placeId || right.placeID);
    if (leftPlaceId && rightPlaceId && leftPlaceId === rightPlaceId) {
      return true;
    }

    const leftCid = normalizeText(left.cID || left.cid);
    const rightCid = normalizeText(right.cID || right.cid);
    if (leftCid && rightCid && leftCid === rightCid) {
      return true;
    }

    const leftHref = cleanUrl(left.href || left.sourceHref || left.googleMapsUrl);
    const rightHref = cleanUrl(right.href || right.sourceHref || right.googleMapsUrl);
    if (leftHref && rightHref && leftHref === rightHref) {
      return true;
    }

    return Boolean(
      normalizeText(left.name) &&
      normalizeText(right.name) &&
      isLikelySameBusinessName(left.name, right.name)
    );
  }

  function refreshCandidateReference(candidate) {
    if (!candidate) {
      return null;
    }

    const visibleCandidates = collectResultCandidates();
    const liveCandidate = visibleCandidates.find((item) => isSameCandidate(item, candidate));
    if (!liveCandidate) {
      return STATE.candidateMap.get(candidate.key) || candidate;
    }

    const mergedCandidate = {
      ...candidate,
      ...liveCandidate,
      key: candidate.key || liveCandidate.key
    };
    STATE.candidateMap.set(mergedCandidate.key, mergedCandidate);
    return mergedCandidate;
  }

  function triggerEscapeBack() {
    const eventOptions = {
      key: "Escape",
      code: "Escape",
      keyCode: 27,
      which: 27,
      bubbles: true,
      cancelable: true
    };
    document.dispatchEvent(new KeyboardEvent("keydown", eventOptions));
    document.dispatchEvent(new KeyboardEvent("keyup", eventOptions));
  }

  async function forceLoadMoreResults(feed) {
    if (!feed) {
      return false;
    }

    const beforeSeenCount = STATE.seenResultKeys.size;
    const beforeHeight = feed.scrollHeight;
    const beforeTop = feed.scrollTop;
    const visibleCandidates = collectResultCandidates();
    const lastCandidate = visibleCandidates[visibleCandidates.length - 1];

    for (let attempt = 0; attempt < FORCE_LOAD_TRIES; attempt += 1) {
      if (lastCandidate?.element?.scrollIntoView) {
        lastCandidate.element.scrollIntoView({ block: "end", inline: "nearest" });
      }

      feed.scrollTo({
        top: feed.scrollHeight,
        behavior: "auto"
      });

      feed.dispatchEvent(new WheelEvent("wheel", {
        deltaY: Math.max(feed.clientHeight, 900),
        bubbles: true,
        cancelable: true
      }));

      await sleep(FORCE_LOAD_WAIT_MS);
      queueVisibleCandidates();

      const grew =
        STATE.seenResultKeys.size > beforeSeenCount ||
        feed.scrollHeight > beforeHeight ||
        feed.scrollTop > beforeTop;

      if (grew) {
        return true;
      }
    }

    return false;
  }

  async function advanceResultsFeed(feed) {
    if (!feed) {
      return;
    }

    const visibleCandidates = collectResultCandidates();
    const lastCandidate = visibleCandidates[visibleCandidates.length - 1];
    if (lastCandidate?.element?.scrollIntoView) {
      lastCandidate.element.scrollIntoView({ block: "end", inline: "nearest" });
      await sleep(120);
    }

    const step = Math.max(Math.floor(feed.clientHeight * 0.55), 280);
    feed.scrollBy({
      top: step,
      behavior: "auto"
    });

    feed.dispatchEvent(new WheelEvent("wheel", {
      deltaY: step,
      bubbles: true,
      cancelable: true
    }));

    await sleep(SCROLL_WAIT_MS);
  }

  function extractPlaceDetails(fallbackCandidate) {
    const name = getDetailHeadingText() || normalizeText(fallbackCandidate?.name);
    if (!name) {
      return null;
    }

    const fallbackLead = buildFallbackLead(fallbackCandidate) || {};
    const { rating, reviewCount } = extractRatingAndReviews();
    const googleMapsUrl = pickDetailUrl(fallbackCandidate) || cleanUrl(fallbackLead.googleMapsUrl);
    const coordinates = parseCoordinates(googleMapsUrl);
    const placeId = parsePlaceId(googleMapsUrl) || normalizeText(fallbackLead.placeId || fallbackLead.placeID);
    const cid = parseCid(googleMapsUrl) || normalizeText(fallbackLead.cID || fallbackLead.cid);
    const sourceHref = cleanUrl(fallbackCandidate?.href || fallbackCandidate?.sourceHref || googleMapsUrl || fallbackLead.sourceHref);
    const socials = extractSocialsFromDetail();
    const category = cleanExtractedCategory(extractCategory()) || cleanExtractedCategory(fallbackLead.category);
    const address = cleanExtractedAddress(extractAddress(), category) || cleanExtractedAddress(fallbackLead.address, category);

    return {
      query: getSearchQuery(),
      name,
      category,
      address,
      phone: extractPhone() || normalizeText(fallbackLead.phone),
      website: extractWebsite(name),
      rating: rating || normalizeText(fallbackLead.rating || fallbackLead.averageRating),
      averageRating: rating,
      reviewCount: reviewCount || normalizeText(fallbackLead.reviewCount),
      cID: cid,
      cid,
      placeId,
      placeID: placeId,
      googleMapsUrl,
      sourceHref,
      source: "dom",
      latitude: coordinates.latitude || normalizeText(fallbackLead.latitude),
      longitude: coordinates.longitude || normalizeText(fallbackLead.longitude),
      email: "",
      ...socials
    };
  }

  async function extractPlaceDetailsWithRetries(candidate, existingLead = null, options = {}) {
    const retryDelays = options.forceContactRecovery ? CONTACT_RECOVERY_RETRY_DELAYS_MS : DETAIL_RETRY_DELAYS_MS;
    const fallbackLead = buildFallbackLead(candidate);
    let bestDetails = fallbackLead ? mergeLeadSnapshots(null, fallbackLead) : null;
    if (existingLead) {
      bestDetails = mergeLeadSnapshots(bestDetails, existingLead);
    }
    let bestScore = getLeadCompletenessScore(bestDetails);

    for (let attempt = 0; attempt < retryDelays.length; attempt += 1) {
      if (attempt > 0) {
        await sleep(retryDelays[attempt]);
      }

      const currentDetails = extractPlaceDetails(candidate);
      if (!currentDetails) {
        continue;
      }

      const mergedDetails = mergeLeadSnapshots(bestDetails, currentDetails);
      const mergedScore = getLeadCompletenessScore(mergedDetails);

      if (!bestDetails || mergedScore >= bestScore) {
        bestDetails = mergedDetails;
        bestScore = mergedScore;
      }

      if (hasRichContactDetails(bestDetails)) {
        break;
      }
    }

    return bestDetails;
  }

  async function tryFallbackSave(candidate, reasonText) {
    const fallbackLead = buildFallbackLead(candidate);
    if (!fallbackLead) {
      incrementRunStat("skipped", 1);
      setStatus(`已跳过：${candidate?.name || "未识别商家"}，${reasonText}且缺少可保存的基础信息。`, "neutral");
      return { ok: false, skipped: true };
    }

    const existingLead = getKnownLeadForCandidate(candidate) || getKnownLeadForLead(fallbackLead);
    const leadKey = getLeadKey(fallbackLead);
    const canUseFallbackToFillExisting = existingLead && !hasEnoughStoredDetails(existingLead);

    if (leadKey && STATE.knownKeys.has(leadKey) && !canUseFallbackToFillExisting) {
      incrementRunStat("deduped", 1);
      markCandidateProcessed(candidate?.key);
      setStatus(`已存在，跳过：${fallbackLead.name}。当前${getRunStatsSummary()}。`, "neutral");
      return { ok: true, inserted: false, deduped: true };
    }

    const result = await saveLead(fallbackLead);
    if (!result.ok) {
      return { ok: false };
    }

    if (result.stats.insertedCount > 0) {
      incrementRunStat("success", result.stats.insertedCount);
      incrementRunStat("fallbackSaved", result.stats.insertedCount);
      STATE.processedThisRun += result.stats.insertedCount;
      markCandidateProcessed(candidate?.key);
      setStatus(`详情异常，已兜底保存：${fallbackLead.name}。当前${getRunStatsSummary()}。`, "success");
      return { ok: true, inserted: true };
    }

    if (result.stats.updatedCount > 0) {
      markCandidateProcessed(candidate?.key);
      setStatus(`详情异常，但已补全：${fallbackLead.name}。当前${getRunStatsSummary()}。`, "success");
      return { ok: true, inserted: false, updated: true };
    }

    incrementRunStat("deduped", 1);
    markCandidateProcessed(candidate?.key);
    setStatus(`已存在，跳过：${fallbackLead.name}。当前${getRunStatsSummary()}。`, "neutral");
    return { ok: true, inserted: false, deduped: true };
  }

  async function seedDiscoveredCandidates() {
    const seedLeads = [];

    for (const candidate of STATE.candidateMap.values()) {
      const existingLead = getKnownLeadForCandidate(candidate);
      if (existingLead && hasEnoughStoredDetails(existingLead)) {
        continue;
      }

      const fallbackLead = buildFallbackLead(candidate);
      if (!fallbackLead) {
        continue;
      }

      seedLeads.push(fallbackLead);
    }

    if (!seedLeads.length) {
      return { ok: true, insertedCount: 0, updatedCount: 0, dedupedCount: 0 };
    }

    const result = await saveLeads(seedLeads);
    if (!result.ok) {
      return { ok: false, insertedCount: 0, updatedCount: 0, dedupedCount: 0 };
    }

    const insertedCount = Number(result.stats.insertedCount || 0);
    const updatedCount = Number(result.stats.updatedCount || 0);
    const dedupedCount = Math.max(0, Number(result.stats.totalIncoming || 0) - insertedCount - updatedCount);

    if (insertedCount > 0) {
      incrementRunStat("success", insertedCount);
      incrementRunStat("fallbackSaved", insertedCount);
      STATE.processedThisRun += insertedCount;
    }

    if (dedupedCount > 0) {
      incrementRunStat("deduped", dedupedCount);
    }

    return {
      ok: true,
      insertedCount,
      updatedCount,
      dedupedCount
    };
  }

  async function enrichLead(lead) {
    if (!STATE.enrichEmail || !lead.website) {
      return lead;
    }

    const response = await sendMessage({
      action: "ENRICH_WEBSITE",
      url: lead.website
    });

    if (!response.ok) {
      return lead;
    }

    const enrichedLead = { ...lead };
    const emails = Array.isArray(response.emails) ? response.emails : [];
    if (emails.length) {
      enrichedLead.email = emails.join(", ");
    }

    Object.entries(response.socials || {}).forEach(([key, value]) => {
      if (value) {
        enrichedLead[key] = value;
      }
    });

    return enrichedLead;
  }

  async function loadKnownKeys() {
    const response = await sendMessage({ action: "GET_LEADS" });
    if (!response.ok) {
      return;
    }

    await updateKnownKeys(response.leads || []);
  }

  async function saveLead(lead) {
    return bufferLeadsForSave([lead], {
      forceFlush: true,
      reason: "single-lead"
    });
  }

  async function returnToResults() {
    if (canContinueFromResults()) {
      return true;
    }

    const backButton = queryOne([
      "button[jsaction*='pane.place.backToList']",
      "button[aria-label='Back']",
      "button[aria-label='返回']"
    ]);

    if (backButton) {
      backButton.click();
      const returnedFeed = await waitFor(() => canContinueFromResults(), 8000, 250);
      if (returnedFeed) {
        return true;
      }
    }

    triggerEscapeBack();
    const escapedFeed = await waitFor(() => canContinueFromResults(), 2200, 150);
    if (escapedFeed) {
      return true;
    }

    return false;
  }

  async function openCandidate(candidate, options = {}) {
    const liveCandidate = refreshCandidateReference(candidate);
    const resolvedCandidate = liveCandidate || candidate;
    if (!resolvedCandidate?.card && !resolvedCandidate?.element && !resolvedCandidate?.href) {
      return false;
    }

    const previousSignature = getDetailSignature();
    const primaryTarget = resolvedCandidate.card || resolvedCandidate.element;

    if (primaryTarget && document.contains(primaryTarget)) {
      primaryTarget.scrollIntoView({ block: "center", inline: "nearest" });
      await sleep(120);
      primaryTarget.click();

      if (primaryTarget !== resolvedCandidate.element && resolvedCandidate.element && document.contains(resolvedCandidate.element)) {
        await sleep(120);
        if (!isCandidateDetailReady(resolvedCandidate, previousSignature)) {
          resolvedCandidate.element.click();
        }
      }
    } else {
      return false;
    }

    const opened = await waitFor(() => isCandidateDetailReady(resolvedCandidate, previousSignature), OPEN_DETAIL_WAIT_MS, 120);
    return Boolean(opened);
  }

  async function processCandidate(candidate, options = {}) {
    if (!candidate?.card && !candidate?.element && !candidate?.href) {
      incrementRunStat("skipped", 1);
      markCandidateProcessed(candidate?.key);
      setStatus(`已跳过：${candidate?.name || "未识别商家"}，缺少可打开的商家链接。`, "neutral");
      return false;
    }

    const forceContactRecovery = Boolean(options.forceContactRecovery);
    const existingLead = getKnownLeadForCandidate(candidate);
    if (!forceContactRecovery && existingLead && hasEnoughStoredDetails(existingLead)) {
      incrementRunStat("skipped", 1);
      markCandidateProcessed(candidate.key);
      setStatus(`已快速跳过：${candidate.name || existingLead.name}，列表结果已含基础信息。当前${getRunStatsSummary()}。`, "neutral");
      return true;
    }

    setStatus(`${forceContactRecovery ? "正在补抓联系方式" : "正在抓取"}：${candidate.name || candidate.href}`, "working");

    const opened = await openCandidate(candidate, {
      preferDirectNavigation: false
    });
    if (!opened) {
      const fallbackResult = await tryFallbackSave(candidate, "详情打开失败");
      if (fallbackResult.ok) {
        return true;
      }
      if (fallbackResult.skipped) {
        markCandidateProcessed(candidate.key);
        return false;
      }

      markCandidateFailed(candidate.key);
      incrementRunStat("failed", 1);
      setStatus(`${forceContactRecovery ? "补抓打开失败" : "打开失败"}：${candidate.name || candidate.href}。当前${getRunStatsSummary()}。`, "error");
      return false;
    }

    await waitFor(() => {
      const website = extractWebsite(candidate?.name || existingLead?.name || "");
      const phone = extractPhone();
      const address = extractAddress();
      return Boolean(website || phone || address);
    }, DETAIL_WAIT_MS, 120);

    const details = await extractPlaceDetailsWithRetries(candidate, existingLead, {
      forceContactRecovery
    });
    if (!details) {
      const fallbackResult = await tryFallbackSave(candidate, "详情解析失败");
      if (fallbackResult.ok) {
        return true;
      }
      if (fallbackResult.skipped) {
        markCandidateProcessed(candidate.key);
        return false;
      }

      markCandidateFailed(candidate.key);
      incrementRunStat("failed", 1);
      setStatus(`${forceContactRecovery ? "补抓解析失败" : "详情解析失败"}：${candidate.name || candidate.href}。当前${getRunStatsSummary()}。`, "error");
      return false;
    }

    const detailsToSave = details;
    const leadKey = getLeadKey(detailsToSave);
    const canUseDetailsToFillExisting = existingLead && !hasEnoughStoredDetails(existingLead);
    if (leadKey && STATE.knownKeys.has(leadKey) && !canUseDetailsToFillExisting) {
      incrementRunStat("deduped", 1);
      markCandidateProcessed(candidate.key);
      setStatus(`已存在，跳过：${detailsToSave.name}。当前${getRunStatsSummary()}。`, "neutral");
      return true;
    }

    const result = await saveLead(detailsToSave);
    if (!result.ok) {
      markCandidateFailed(candidate.key);
      incrementRunStat("failed", 1);
      return false;
    }

    markCandidateProcessed(candidate.key);

    if (result.stats.insertedCount > 0) {
      incrementRunStat("success", result.stats.insertedCount);
      STATE.processedThisRun += result.stats.insertedCount;
      setStatus(`${forceContactRecovery ? "已补抓" : "已采集"}：${detailsToSave.name}。当前${getRunStatsSummary()}。`, "success");
    } else if (result.stats.updatedCount > 0) {
      setStatus(`${forceContactRecovery ? "已补抓补全" : "已补全"}：${detailsToSave.name}。当前${getRunStatsSummary()}。`, "success");
    } else {
      incrementRunStat("deduped", 1);
      setStatus(`已存在，跳过：${detailsToSave.name}。当前${getRunStatsSummary()}。`, "neutral");
    }

    if (STATE.enrichEmail && detailsToSave.website && (!normalizeText(detailsToSave.email) || !countFilledSocials(detailsToSave))) {
      enrichLeadsInBackground([detailsToSave]).catch(() => {});
    }

    return true;
  }

  async function discoverResultsPhase() {
    let idleRounds = 0;
    STATE.resultsListDirty = false;
    setStatus("开始采集，正在扫描结果列表。", "working");

    while (STATE.running) {
      let feed = findResultsFeed();
      if (!feed) {
        const recovered = await ensureResultsContext();
        if (!recovered) {
          return { ok: false, reason: "missing-feed" };
        }

        feed = findResultsFeed();
        if (!feed) {
          setStatus("未检测到搜索结果列表，请先打开 Google Maps 搜索结果页。", "error");
          return { ok: false, reason: "missing-feed" };
        }
      }

      STATE.lastResultsUrl = getResultsPageUrl();
      const previousQueueSnapshot = queueVisibleCandidates();
      const previousStoredCount = STATE.storedCount;
      const previousHeight = feed.scrollHeight;
      const previousTop = feed.scrollTop;

      await advanceResultsFeed(feed);

      const nextQueueSnapshot = queueVisibleCandidates();
      const networkProgress = STATE.storedCount > previousStoredCount || Date.now() - STATE.lastBatchAt < 1500;
      const listExpanded = feed.scrollHeight > previousHeight || feed.scrollTop > previousTop;
      const discoveredNewResults = nextQueueSnapshot.seenCount > previousQueueSnapshot.seenCount;
      const nearListEnd = feed.scrollTop + feed.clientHeight >= feed.scrollHeight - 48;
      const reachedEnd = hasReachedEndOfResults(feed);
      STATE.resultsEndReached = Boolean(reachedEnd);
      updateDiagnosticsView();

      if (networkProgress || listExpanded || discoveredNewResults) {
        idleRounds = 0;
        continue;
      }

      const forcedMore = await forceLoadMoreResults(feed);
      if (forcedMore) {
        idleRounds = 0;
        setStatus(`正在继续拉取更多结果。当前${getRunStatsSummary()}。`, "working");
        continue;
      }

      if (nearListEnd || reachedEnd) {
        idleRounds += 1;
        feed.scrollTo({
          top: feed.scrollHeight,
          behavior: "smooth"
        });
        setStatus(`${reachedEnd ? "已到达结果列表底部" : "结果列表暂无新商家"}，继续尝试 ${idleRounds}/${RESULT_IDLE_LIMIT}。当前${getRunStatsSummary()}。`, "neutral");
      } else {
        idleRounds = 0;
      }

      if (idleRounds >= RESULT_IDLE_LIMIT && (reachedEnd || nearListEnd)) {
        return { ok: true, reason: "stable" };
      }
    }

    return { ok: false, reason: "stopped" };
  }

  async function processQueuedCandidatesPhase() {
    while (STATE.running) {
      const current = getNextQueuedCandidate();
      if (!current) {
        return { ok: true, reason: "empty-queue" };
      }

      const saved = await processCandidate(current);

      if (!STATE.running) {
        return { ok: false, reason: "stopped" };
      }

      await sleep(saved ? 120 : 100);
      const returned = await returnToResults();
      if (!returned) {
        const recovered = await ensureResultsContext();
        if (!recovered) {
          incrementRunStat("failed", 1);
          setStatus(`返回结果列表失败，请手动回到搜索结果页后重试。当前${getRunStatsSummary()}。`, "error");
          return { ok: false, reason: "return-failed" };
        }
      }

      const queueSnapshot = queueVisibleCandidates();
      if (queueSnapshot.newlySeenCount > 0) {
        setStatus(`左侧结果有新增，已同步 ${queueSnapshot.newlySeenCount} 个候选。当前${getRunStatsSummary()}。`, "working");
      }

      await sleep(180);
    }

    return { ok: false, reason: "stopped" };
  }

  function buildContactRecoveryCandidates() {
    return [...STATE.candidateMap.values()].filter((candidate) => {
      const knownLead = getKnownLeadForCandidate(candidate);
      return needsContactRecovery(knownLead);
    });
  }

  async function processContactRecoveryPhase() {
    await flushLeadBuffer("before-contact-recovery");

    const recoveryCandidates = buildContactRecoveryCandidates();
    if (!recoveryCandidates.length) {
      return { ok: true, reason: "no-recovery-needed", processedCount: 0 };
    }

    let processedCount = 0;
    setStatus(`开始二次补抓网址与社媒，待处理 ${recoveryCandidates.length} 个候选。`, "working");

    for (const candidate of recoveryCandidates) {
      if (!STATE.running) {
        return { ok: false, reason: "stopped", processedCount };
      }

      const latestLead = getKnownLeadForCandidate(candidate);
      if (!needsContactRecovery(latestLead)) {
        continue;
      }

      await processCandidate(candidate, {
        forceContactRecovery: true
      });
      processedCount += 1;

      if (!STATE.running) {
        return { ok: false, reason: "stopped", processedCount };
      }

      await sleep(150);
      const returned = await returnToResults();
      if (!returned) {
        const recovered = await ensureResultsContext();
        if (!recovered) {
          incrementRunStat("failed", 1);
          setStatus(`二次补抓后返回结果列表失败，请手动回到搜索结果页后重试。当前${getRunStatsSummary()}。`, "error");
          return { ok: false, reason: "return-failed", processedCount };
        }
      }

      queueVisibleCandidates();
      await sleep(220);
    }

    await flushLeadBuffer("after-contact-recovery");
    return { ok: true, reason: "completed", processedCount };
  }

  async function runCollection(options = {}) {
    const resume = Boolean(options.resume);
    clearAutostartRetryTimer();
    STATE.pendingAutostartJob = null;
    STATE.running = true;
    STATE.resumeRequested = true;
    STATE.processedThisRun = resume ? STATE.processedThisRun : 0;
    STATE.networkLeadCount = resume ? STATE.networkLeadCount : 0;
    STATE.resultsEndReached = resume ? STATE.resultsEndReached : false;
    STATE.resultsListDirty = false;
    STATE.restoredDiagnostics.candidates = resume ? STATE.restoredDiagnostics.candidates : 0;
    STATE.restoredDiagnostics.queued = resume ? STATE.restoredDiagnostics.queued : 0;
    STATE.candidateMap.clear();
    STATE.attemptedResultKeys.clear();
    STATE.queuedCandidateKeys.clear();
    STATE.seenResultKeys.clear();
    STATE.failedResultKeys.clear();
    STATE.processedPayloadKeys.clear();
    STATE.pendingLeadBuffer = [];
    STATE.pendingLeadBufferKeys.clear();
    STATE.pendingLeadBufferReason = "";
    STATE.pendingFlushPromise = null;
    STATE.lastBatchAt = 0;
    if (!resume) {
      resetRunStats();
    } else {
      updateRunStatsView();
      updateDiagnosticsView();
    }
    updateButtonState();
    updateDiagnosticsView();
    await loadKnownKeys();
    await flushCollectionStateSync();
    startResultsObserver();

    const disabledMapUpdate = disableMapMoveResultUpdates();
    if (disabledMapUpdate) {
      setStatus("已关闭“移动地图时更新搜索结果”，正在稳定结果列表。", "working");
      await sleep(500);
    }

    let cycleCount = 0;

    while (STATE.running) {
      cycleCount += 1;
      const seenBeforeCycle = STATE.seenResultKeys.size;
      const attemptedBeforeCycle = STATE.attemptedResultKeys.size;

      const discoveryResult = await discoverResultsPhase();
      if (!discoveryResult.ok) {
        break;
      }

      await flushLeadBuffer("after-discovery-phase");

      const seeded = await seedDiscoveredCandidates();
      if (!seeded.ok) {
        break;
      }

      if (STATE.running) {
        setStatus(`结果列表扫描完成，开始补全详情。已发现 ${STATE.candidateMap.size} 个候选，已预存 ${seeded.insertedCount} 个。`, "working");
      }

      const queueResult = await processQueuedCandidatesPhase();
      await flushLeadBuffer("after-queue-phase");
      if (!queueResult.ok) {
        break;
      }

      const seenAfterQueue = STATE.seenResultKeys.size;
      const attemptedAfterQueue = STATE.attemptedResultKeys.size;
      const progressThisCycle =
        seenAfterQueue > seenBeforeCycle ||
        attemptedAfterQueue > attemptedBeforeCycle;

      const visibleSnapshot = queueVisibleCandidates();
      const hasPendingCandidates = [...STATE.queuedCandidateKeys].some((key) => !STATE.attemptedResultKeys.has(key));
      const leftSideMoved = STATE.resultsListDirty || visibleSnapshot.newlySeenCount > 0 || seenAfterQueue > seenBeforeCycle;
      STATE.resultsListDirty = false;

      if (STATE.running && (hasPendingCandidates || leftSideMoved) && cycleCount < 8) {
        setStatus(`左侧结果仍在变化，继续同步扫描。已发现候选 ${STATE.candidateMap.size} 个。`, "working");
        continue;
      }

      if (!progressThisCycle || cycleCount >= 8) {
        break;
      }
    }

    if (STATE.running) {
      const recoveryResult = await processContactRecoveryPhase();
      if (!recoveryResult.ok) {
        STATE.running = false;
      }
    }

    await flushLeadBuffer("collection-finished");

    if (STATE.running) {
      setStatus(`采集完成。本轮${getRunStatsSummary()}。已发现候选 ${STATE.candidateMap.size} 个。`, "success");
    }

    STATE.resumeRequested = false;
    STATE.running = false;
    stopResultsObserver();
    updateButtonState();
    await flushCollectionStateSync();
  }

  function stopCollection() {
    STATE.running = false;
    STATE.resumeRequested = false;
    STATE.autostartRequested = false;
    STATE.pendingAutostartJob = null;
    clearAutostartRetryTimer();
    flushLeadBuffer("stop-collection").catch(() => {});
    stopResultsObserver();
    setStatus("已停止采集。", "neutral");
    updateButtonState();
    flushCollectionStateSync().catch(() => {});
  }

  async function clearLeads() {
    const confirmed = window.confirm("确认清空已采集的数据吗？");
    if (!confirmed) {
      return;
    }

    const response = await sendMessage({ action: "CLEAR_LEADS" });
    if (!response.ok) {
      setStatus(`清空失败：${response.error || "未知错误"}`, "error");
      return;
    }

    STATE.running = false;
    STATE.knownKeys.clear();
    STATE.attemptedResultKeys.clear();
    STATE.candidateMap.clear();
    STATE.queuedCandidateKeys.clear();
    STATE.seenResultKeys.clear();
    STATE.networkLeadCount = 0;
    STATE.resultsEndReached = false;
    STATE.resultsListDirty = false;
    STATE.autostartRequested = false;
    STATE.pendingAutostartJob = null;
    STATE.pendingLeadBuffer = [];
    STATE.pendingLeadBufferKeys.clear();
    STATE.pendingLeadBufferReason = "";
    STATE.pendingFlushPromise = null;
    clearAutostartRetryTimer();
    STATE.collectionId = "";
    STATE.currentQuery = "";
    STATE.resumeRequested = false;
    STATE.restoredDiagnostics.candidates = 0;
    STATE.restoredDiagnostics.queued = 0;
    STATE.lastStatus = {
      text: "等待开始采集。",
      tone: "neutral"
    };
    if (STATE.persistUiStateTimer) {
      clearTimeout(STATE.persistUiStateTimer);
      STATE.persistUiStateTimer = null;
    }
    stopResultsObserver();
    STATE.storedCount = 0;
    resetRunStats();
    setCount(0);
    updateDiagnosticsView();
    setStatus("已清空本地采集数据。", "success");
  }

  async function openDashboard() {
    const response = await sendMessage({ action: "OPEN_DASHBOARD" });
    if (!response?.ok) {
      setStatus(response?.error || "打开结果页失败。请确认 Chrome 当前加载的是包含 dashboard.html 的扩展目录。", "error");
    }
  }

  async function initializeCurrentCollectionSession() {
    const query = getSearchQuery({ allowStateFallback: true });
    if (!query) {
      setStatus("未识别当前搜索关键词，请先输入城市 + 产品关键词。", "error");
      return null;
    }

    const response = await sendMessage({
      action: "INIT_COLLECTION_SESSION",
      query,
      enrichEmail: STATE.enrichEmail
    });

    if (!response.ok || !response.job?.collectionId) {
      setStatus(response.error || "初始化采集任务失败。", "error");
      return null;
    }

    STATE.collectionId = response.job.collectionId;
    STATE.currentQuery = response.job.query || query;
    STATE.pendingAutostartJob = null;
    clearAutostartRetryTimer();
    STATE.resumeRequested = false;
    STATE.restoredDiagnostics.candidates = 0;
    STATE.restoredDiagnostics.queued = 0;
    await updateKnownKeys([]);
    await flushCollectionStateSync();
    return response.job;
  }

  function ensurePanel() {
    if (document.getElementById(PANEL_ID)) {
      return;
    }

    const panel = document.createElement("div");
    panel.id = PANEL_ID;
    panel.innerHTML = `
      <div class="gmaps-collector__title">智枢获客系统</div>
      <div class="gmaps-collector__meta">
        当前关键词已采集 <strong data-role="count">0</strong> 条
      </div>
      <div class="gmaps-collector__stats">
        <div class="gmaps-collector__stat"><span>本轮成功</span><strong data-role="success-count">0</strong></div>
        <div class="gmaps-collector__stat"><span>本轮去重</span><strong data-role="deduped-count">0</strong></div>
        <div class="gmaps-collector__stat"><span>本轮跳过</span><strong data-role="skipped-count">0</strong></div>
        <div class="gmaps-collector__stat"><span>本轮失败</span><strong data-role="failed-count">0</strong></div>
      </div>
      <div class="gmaps-collector__diag">
        <div class="gmaps-collector__diag-item"><span>已发现</span><strong data-role="diag-candidates">0</strong></div>
        <div class="gmaps-collector__diag-item"><span>未处理</span><strong data-role="diag-queued">0</strong></div>
        <div class="gmaps-collector__diag-item"><span>网络</span><strong data-role="diag-network">0</strong></div>
        <div class="gmaps-collector__diag-item"><span>到底</span><strong data-role="diag-end">否</strong></div>
      </div>
      <label class="gmaps-collector__toggle">
        <input type="checkbox" data-role="email-toggle" checked />
        <span>后台补抓邮箱与社媒</span>
      </label>
      <div class="gmaps-collector__actions">
        <button data-role="start">开始采集</button>
        <button data-role="stop" class="secondary" disabled>停止</button>
      </div>
      <div class="gmaps-collector__actions">
        <button data-role="dashboard" class="secondary">查看结果</button>
        <button data-role="clear" class="danger">清空数据</button>
      </div>
      <div class="gmaps-collector__status" data-role="status" data-tone="neutral">
        等待开始采集。
      </div>
    `;

    document.documentElement.appendChild(panel);

    panel.querySelector("[data-role='start']").addEventListener("click", async () => {
      const job = await initializeCurrentCollectionSession();
      if (!job) {
        return;
      }

      setStatus(`已创建新关键词数据表：${job.query}。开始采集中。`, "working");
      runCollection().catch((error) => {
        STATE.running = false;
        STATE.resumeRequested = false;
        updateButtonState();
        setStatus(getSafeStatusMessage(error, "采集异常，请稍后重试。"), "error");
      });
    });

    panel.querySelector("[data-role='stop']").addEventListener("click", stopCollection);
    panel.querySelector("[data-role='dashboard']").addEventListener("click", openDashboard);
    panel.querySelector("[data-role='clear']").addEventListener("click", clearLeads);
    panel.querySelector("[data-role='email-toggle']").addEventListener("change", (event) => {
      STATE.enrichEmail = Boolean(event.target.checked);
    });

    updateButtonState();
    updateRunStatsView();
    updateDiagnosticsView();
  }

  function clearAutostartRetryTimer() {
    if (!STATE.autostartRetryTimer) {
      return;
    }

    clearTimeout(STATE.autostartRetryTimer);
    STATE.autostartRetryTimer = null;
  }

  function scheduleAutostartRetry(delayMs = 1500) {
    clearAutostartRetryTimer();
    STATE.autostartRetryTimer = setTimeout(() => {
      STATE.autostartRetryTimer = null;
      maybeAutostart().catch((error) => {
        STATE.autostartRequested = false;
        updateButtonState();
        setStatus(getSafeStatusMessage(error, "自动采集等待结果页时发生异常，请稍后重试。"), "error");
      });
    }, delayMs);
  }

  async function init() {
    ensurePanel();
    await loadKnownKeys();
    const restored = await restoreActiveCollectionState();
    if (restored && STATE.resumeRequested && !STATE.running) {
      setStatus("检测到上次采集未完成，正在恢复。", "working");
      const ready = await ensureResultsContext();
      if (ready) {
        runCollection({ resume: true }).catch((error) => {
          STATE.running = false;
          STATE.resumeRequested = false;
          updateButtonState();
          setStatus(getSafeStatusMessage(error, "恢复采集异常，请稍后重试。"), "error");
        });
        return;
      }
    }

    if (!restored) {
      setStatus("插件已就绪。你可以在弹窗输入关键词自动采集，也可以手动点击“开始采集”。", "neutral");
    }
    await maybeAutostart();
  }

  window.addEventListener("message", handleNetworkPayload);
  injectNetworkHook();

  async function maybeAutostart() {
    if (STATE.autostartRequested || STATE.running) {
      return;
    }

    let job = STATE.pendingAutostartJob;
    if (!job?.query) {
      const response = await sendMessage({ action: "CLAIM_COLLECTION_JOB" });
      job = response?.job;
      if (!response?.ok || !job?.query) {
        return;
      }

      STATE.pendingAutostartJob = job;
    }

    STATE.autostartRequested = true;
    STATE.enrichEmail = job.enrichEmail !== false;
    STATE.collectionId = job.collectionId || "";
    STATE.currentQuery = job.query || "";
    updateButtonState();
    setStatus(`已接收自动采集任务：${job.query}，正在等待结果页加载。`, "working");

    const ready = await waitFor(() => {
      const query = getSearchQuery();
      const hasFeed = Boolean(findResultsFeed());
      return hasFeed && (!query || query.toLowerCase().includes(job.query.toLowerCase()) || job.query.toLowerCase().includes(query.toLowerCase()));
    }, 15000, 300);

    if (!ready) {
      STATE.autostartRequested = false;
      updateButtonState();
      setStatus("自动采集任务已创建，但当前页还没出现结果列表。页面加载完成后会继续自动尝试，也可以手动点“开始采集”。", "error");
      scheduleAutostartRetry();
      return;
    }

    clearAutostartRetryTimer();
    STATE.pendingAutostartJob = null;
    runCollection().catch((error) => {
      STATE.running = false;
      STATE.resumeRequested = false;
      updateButtonState();
      setStatus(getSafeStatusMessage(error, "自动采集异常，请稍后重试。"), "error");
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init, { once: true });
  } else {
    init().catch(() => {
      setStatus("插件初始化失败，请刷新页面后重试。", "error");
    });
  }
})();
