import { setTimeout as sleep } from "node:timers/promises";

import {
  humanClick,
  randomPause,
  stepPause,
  waitForManualVerificationIfNeeded,
  waitForVerificationClear,
} from "./human.js";
import {
  build1688ContactPageUrls,
  extractDetectedContacts,
  mergeDetectedContacts,
} from "./contact-utils.js";
import {
  looksLikeGeneratedResourceTitle,
  looksLikeSearchResultTitle,
  sanitizeSearchResultTitle,
} from "./search-result-extractor.js";

const TITLE_SELECTORS = [
  "h1",
  '[class*="title"]',
  '[class*="Title"]',
  '[class*="subject"]',
  '[class*="Subject"]',
  '[class*="offerTitle"]',
  '[class*="offer-title"]',
  '[data-testid*="title"]',
  '[data-role*="title"]',
  '[title]'
];

const CLICKABLE_SELECTORS = [
  "a",
  "button",
  "[role=button]",
  "li",
  "div",
  "span",
];

const PRODUCT_ENTRY_PATTERNS = [
  /^商品$/,
  /^全部商品$/,
  /店铺商品/,
];

const CONTACT_ENTRY_PATTERNS = [
  /^联系方式$/,
  /联系方式/,
  /^联系卖家$/,
  /^联系商家$/,
];
const WANGWANG_ENTRY_XPATHS = [
  {
    xpath: "/html/body/div[2]/div/div/div[3]/div[2]/div/div/div[2]/div[1]/div[4]/div[2]",
    method: "primary_online_chat_xpath",
  },
  {
    xpath: '//*[@id="hd_0_container_0"]/div[1]/div[2]/div/div/div[1]/div[1]/div/div[2]/div/a/img',
    method: "backup_wangwang_icon_xpath",
  },
];
const WANGWANG_CHAT_URL_PATTERN = /^https:\/\/air\.1688\.com\/app\/ocms-fusion-components-1688\/def_cbu_web_im\/index\.html\?/i;
const WANGWANG_CHAT_UI_SIGNALS = [
  "请输入消息",
  "发送",
  "暂无更多消息",
  "搜索联系人",
  "聊天设置",
  "下载客户端",
  "唤起客户端",
  "您正在沟通的商家",
  "Ai询盘",
  "消息",
];
const VERIFICATION_PAGE_PATTERN = /验证码拦截|拖动下方滑块完成验证|请按住滑块|通过验证以确保正常访问|验证失败|二次验证|拖动到最右边|按照说明拖动滑块|拖动滑块出现完整的|拖动到指定位置|拖动滑块完成拼图|请完成下列拼图|请拖动滑块还原图像|x5step=1|\/punish\b/i;
const OFFER_DETAIL_READY_PATTERN = /商品详情|价格|起批|成交|评价|供应|发货|规格|型号|品牌|材质|主图/u;
const OFFER_DETAIL_READY_SELECTORS = [
  "h1",
  '[class*="title"]',
  '[class*="Title"]',
  '[class*="gallery"] img',
  '[class*="Gallery"] img',
  '[class*="main"] img',
  '[class*="Main"] img',
  '[class*="detail"] img',
  '[class*="Detail"] img',
  'img[src*="alicdn"]',
  'img[src*="cbu01"]',
  'img[src*="1688"]',
];
const OFFER_VERIFICATION_OPTIONS = {
  autoAttemptLimit: 5,
  dragDurationMinMs: 2200,
  dragDurationMaxMs: 2800,
  manualOnAutoVerificationFailure: false,
};

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

function isLoginUrl(url) {
  return String(url ?? "").includes("login.taobao.com") || String(url ?? "").includes("login.1688.com");
}

function normalizeWangwangChatUrl(url) {
  const value = normalizeText(url);
  if (
    !WANGWANG_CHAT_URL_PATTERN.test(value)
    || !/[?&]touid=/i.test(value)
    || !/[?&]siteid=cnalichn/i.test(value)
  ) {
    return "";
  }

  return value.includes("#/") ? value : `${value.replace(/#.*$/u, "")}#/`;
}

function looksLikeWangwangUrl(url) {
  return Boolean(normalizeWangwangChatUrl(url));
}

async function closePageSafely(page) {
  if (!page || page.isClosed()) {
    return;
  }

  await waitForManualVerificationIfNeeded(page).catch(() => false);

  await Promise.race([
    page.close().catch(() => {}),
    sleep(5000).then(() => {
      throw new Error("page_close_timeout_5000");
    }),
  ]).catch(() => {});
}

async function pageLooksLikeVerification(page) {
  if (!page || page.isClosed()) {
    return false;
  }

  if (VERIFICATION_PAGE_PATTERN.test(String(page.url?.() ?? ""))) {
    return true;
  }

  for (const frame of page.frames?.() ?? []) {
    const frameUrl = String(frame.url?.() ?? "");
    if (VERIFICATION_PAGE_PATTERN.test(frameUrl)) {
      return true;
    }

    const frameText = await frame.evaluate(() => document.body?.innerText ?? "").catch(() => "");
    if (VERIFICATION_PAGE_PATTERN.test(String(frameText ?? ""))) {
      return true;
    }
  }

  return false;
}

async function waitForReady(page, timeoutMs, logPrefix, verificationOptions = {}) {
  await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs }).catch(() => {});
  const verificationResolved = await waitForVerificationClear(page, { ...verificationOptions, logPrefix }).catch(() => false);
  if (verificationResolved) {
    await page.waitForLoadState("domcontentloaded", { timeout: Math.min(timeoutMs, 5000) }).catch(() => {});
  }
  await stepPause("contact_page_ready");
}

async function collectPageText(page) {
  return page.evaluate(() => {
    const normalize = (value) => String(value ?? "").replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
    const bodyText = normalize(document.body?.innerText ?? "");
    const hrefValues = Array.from(document.querySelectorAll("a[href]")).map((anchor) => normalize(anchor.getAttribute("href") ?? "")).filter(Boolean);
    const telValues = Array.from(document.querySelectorAll('a[href^="tel:"]')).map((anchor) => normalize(anchor.getAttribute("href") ?? "")).filter(Boolean);
    return [bodyText, ...hrefValues, ...telValues].join("\n");
  }).catch(() => "");
}

async function gotoPage(page, url, timeoutMs, logPrefix, verificationOptions = {}) {
  const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
  await waitForReady(page, timeoutMs, logPrefix, verificationOptions);

  if (isLoginUrl(page.url())) {
    return { ok: false, reason: "login_required", response };
  }

  if (response && !response.ok()) {
    return { ok: false, reason: `http_${response.status()}`, response };
  }

  return { ok: true, reason: "", response };
}

async function waitForOfferVerificationClear(page, timeoutMs, logPrefix) {
  const resolvedTimeoutMs = Number.isFinite(Number(timeoutMs)) ? Number(timeoutMs) : 8000;
  await waitForVerificationClear(page, {
    ...OFFER_VERIFICATION_OPTIONS,
    logPrefix,
    force: true,
  }).catch(() => false);
  await page.waitForLoadState("domcontentloaded", { timeout: Math.min(resolvedTimeoutMs, 5000) }).catch(() => {});
  return !(await pageLooksLikeVerification(page));
}

async function waitForOfferDetailReady(page, timeoutMs) {
  const resolvedTimeoutMs = Number.isFinite(Number(timeoutMs)) ? Number(timeoutMs) : 8000;
  if (await pageLooksLikeVerification(page)) {
    return false;
  }

  const ready = await page.waitForFunction(
    ({ verificationPatternSource, readyPatternSource, selectors }) => {
      const verificationPattern = new RegExp(verificationPatternSource, "i");
      const readyPattern = new RegExp(readyPatternSource, "i");
      const bodyText = String(document.body?.innerText ?? "");
      if (verificationPattern.test(String(location.href)) || verificationPattern.test(bodyText)) {
        return false;
      }

      const isVisible = (node) => {
        if (!node) return false;
        const style = window.getComputedStyle(node);
        if (style.display === "none" || style.visibility === "hidden") return false;
        const rect = node.getBoundingClientRect();
        return rect.width > 0 || rect.height > 0;
      };
      const hasVisibleSignal = selectors.some((selector) => {
        try {
          return Array.from(document.querySelectorAll(selector)).some((node) => {
            if (!isVisible(node)) return false;
            if (node.tagName === "IMG") {
              return node.getAttribute("src") || node.getAttribute("data-src") || node.getAttribute("data-lazy-src");
            }
            return String(node.textContent ?? "").trim().length >= 4;
          });
        } catch {
          return false;
        }
      });

      return hasVisibleSignal || readyPattern.test(bodyText);
    },
    {
      verificationPatternSource: VERIFICATION_PAGE_PATTERN.source,
      readyPatternSource: OFFER_DETAIL_READY_PATTERN.source,
      selectors: OFFER_DETAIL_READY_SELECTORS,
    },
    { timeout: Math.min(Math.max(resolvedTimeoutMs, 1000), 8000) },
  ).then(() => true).catch(() => false);

  if (!ready || await pageLooksLikeVerification(page)) {
    return false;
  }

  return true;
}

async function captureOfferDetailScreenshot(page, screenshotPath, timeoutMs) {
  const verificationCleared = await waitForOfferVerificationClear(page, timeoutMs, "[contact-nav][详情截图验证码]");
  if (!verificationCleared) {
    return { screenshotPath: "", offerScreenshotStatus: "captcha_unresolved", reason: "offer_detail_captcha_unresolved" };
  }

  if (!screenshotPath) {
    return { screenshotPath: "", offerScreenshotStatus: "skipped", reason: "" };
  }

  const detailReady = await waitForOfferDetailReady(page, timeoutMs);
  if (!detailReady) {
    const reason = await pageLooksLikeVerification(page)
      ? "offer_detail_captcha_unresolved"
      : "offer_detail_not_ready";
    const status = reason === "offer_detail_captcha_unresolved" ? "captcha_unresolved" : "detail_not_ready";
    return { screenshotPath: "", offerScreenshotStatus: status, reason };
  }

  if (await pageLooksLikeVerification(page)) {
    return { screenshotPath: "", offerScreenshotStatus: "captcha_unresolved", reason: "offer_detail_captcha_unresolved" };
  }

  try {
    await page.screenshot({ path: screenshotPath, type: "jpeg", quality: 75 });
    if (await pageLooksLikeVerification(page)) {
      return { screenshotPath: "", offerScreenshotStatus: "captcha_unresolved", reason: "offer_detail_captcha_unresolved" };
    }
    return { screenshotPath, offerScreenshotStatus: "ok", reason: "" };
  } catch {
    return { screenshotPath: "", offerScreenshotStatus: "error", reason: "offer_detail_screenshot_failed" };
  }
}

async function extractDetailTitle(page, merchantName = "") {
  const candidates = await page.evaluate((selectors) => {
    const normalize = (value) => String(value ?? "").replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
    const isVisible = (node) => {
      if (!node) return false;
      const style = window.getComputedStyle(node);
      if (style.display === "none" || style.visibility === "hidden") {
        return false;
      }
      const rect = node.getBoundingClientRect();
      return rect.width > 0 || rect.height > 0;
    };

    const values = [];
    for (const selector of selectors) {
      for (const node of Array.from(document.querySelectorAll(selector))) {
        if (!isVisible(node)) {
          continue;
        }

        const titleValue = normalize(node.getAttribute?.("title") ?? "");
        const textValue = normalize(node.textContent ?? "");
        if (titleValue) values.push(titleValue);
        if (textValue) values.push(textValue);
      }
    }

    const documentTitle = normalize(document.title).replace(/[_|｜-].*阿里巴巴.*$/u, "").trim();
    if (documentTitle) {
      values.push(documentTitle);
    }

    return values.slice(0, 60);
  }, TITLE_SELECTORS).catch(() => []);

  const seen = new Set();
  const sanitizedCandidates = [];

  for (const rawCandidate of Array.isArray(candidates) ? candidates : []) {
    const candidate = sanitizeSearchResultTitle(rawCandidate, merchantName);
    const key = candidate.toLowerCase();
    if (!candidate || seen.has(key)) {
      continue;
    }

    seen.add(key);
    sanitizedCandidates.push(candidate);
  }

  return sanitizedCandidates.find((candidate) => looksLikeSearchResultTitle(candidate, merchantName))
    ?? sanitizedCandidates.sort((left, right) => right.length - left.length)[0]
    ?? "";
}

export function pickPreferredDetailTitle(currentTitle, detailTitle, merchantName = "") {
  const normalizedCurrent = sanitizeSearchResultTitle(currentTitle, merchantName);
  const normalizedDetail = sanitizeSearchResultTitle(detailTitle, merchantName);

  if (!normalizedDetail || looksLikeGeneratedResourceTitle(normalizedDetail)) {
    return normalizedCurrent || normalizeText(currentTitle);
  }

  if (!normalizedCurrent) {
    return normalizedDetail;
  }

  const currentLooksValid = looksLikeSearchResultTitle(normalizedCurrent, merchantName);
  const detailLooksValid = looksLikeSearchResultTitle(normalizedDetail, merchantName);
  const currentLooksLikeInteractionCopy = /点击(?:可直接|可以直接|可|可以)?(?:和|与)(?:卖家|商家)(?:交流|沟通|联系)|语音视频|互动购/u.test(normalizedCurrent);

  if (detailLooksValid && (!currentLooksValid || currentLooksLikeInteractionCopy)) {
    return normalizedDetail;
  }

  if (normalizedDetail.length > normalizedCurrent.length + 4) {
    return normalizedDetail;
  }

  return normalizedCurrent;
}

async function findVisibleTextLocator(page, patterns) {
  for (const pattern of patterns) {
    for (const selector of CLICKABLE_SELECTORS) {
      const locator = page.locator(selector).filter({ hasText: pattern }).first();
      const count = await locator.count().catch(() => 0);
      if (count === 0) {
        continue;
      }

      const visible = await locator.isVisible().catch(() => false);
      if (!visible) {
        continue;
      }

      return locator;
    }
  }

  return null;
}

async function findVisibleXPathLocator(page, xpaths) {
  for (const { xpath, method } of xpaths) {
    const locator = page.locator(`xpath=${xpath}`).first();
    const box = await locator.boundingBox().catch(() => null);
    if (!box || box.width <= 0 || box.height <= 0) {
      continue;
    }

    return { locator, method, box };
  }

  return null;
}

async function clickTextNavigation(page, patterns, timeoutMs, waitPattern, reason) {
  const locator = await findVisibleTextLocator(page, patterns);
  if (!locator) {
    return { ok: false, reason };
  }

  const previousUrl = page.url();

  await humanClick(page, locator).catch(async () => {
    await locator.click({ delay: 80 }).catch(() => {});
  });

  await Promise.race([
    page.waitForURL((url) => String(url) !== previousUrl, { timeout: Math.min(timeoutMs, 5000) }).catch(() => {}),
    page.waitForLoadState("domcontentloaded", { timeout: Math.min(timeoutMs, 5000) }).catch(() => {}),
    randomPause(500, 900),
  ]).catch(() => {});

  await waitForReady(page, timeoutMs, "[contact-nav][验证码]");

  if (isLoginUrl(page.url())) {
    return { ok: false, reason: "login_required" };
  }

  if (waitPattern) {
    const matched = await page.waitForFunction(
      (patternSource) => new RegExp(patternSource, "i").test(document.body?.innerText ?? ""),
      waitPattern.source,
      { timeout: 4000 },
    ).then(() => true).catch(() => false);

    if (!matched) {
      return { ok: false, reason };
    }
  }

  return { ok: true, reason: "" };
}

async function findWangwangUrlOnPage(page) {
  if (!page || page.isClosed?.()) {
    return { url: "", method: "" };
  }

  const pageUrl = normalizeWangwangChatUrl(page.url?.());
  if (pageUrl) {
    return { url: pageUrl, method: "same_page" };
  }

  for (const frame of page.frames?.() ?? []) {
    const frameUrl = normalizeWangwangChatUrl(frame.url?.());
    if (frameUrl) {
      return { url: frameUrl, method: "frame" };
    }
  }

  return { url: "", method: "" };
}

async function collectWangwangChatPageSignals(page) {
  if (!page || page.isClosed?.()) {
    return [];
  }

  return page.evaluate(({ signals }) => {
    const bodyText = String(document.body?.innerText ?? "").replace(/\s+/g, " ").trim();
    const foundSignals = signals.filter((signal) => bodyText.includes(signal));
    if (document.querySelector('textarea, input, [contenteditable="true"]')) {
      foundSignals.push("editable_input");
    }
    return [...new Set(foundSignals)];
  }, { signals: WANGWANG_CHAT_UI_SIGNALS }).catch(() => []);
}

async function waitForWangwangChatPageLoaded(page, timeoutMs) {
  if (!page || page.isClosed?.()) {
    return { loaded: false, signals: [] };
  }

  const resolvedTimeoutMs = Math.min(Math.max(Number(timeoutMs) || 5000, 1500), 35000);
  const startedAt = Date.now();

  await page.bringToFront?.().catch(() => {});
  await page.waitForLoadState?.("domcontentloaded", { timeout: Math.min(resolvedTimeoutMs, 10000) }).catch(() => {});

  while (Date.now() - startedAt < resolvedTimeoutMs) {
    const signals = await collectWangwangChatPageSignals(page);
    if (normalizeWangwangChatUrl(page.url?.()) && signals.length >= 2) {
      return { loaded: true, signals };
    }
    await sleep(500);
  }

  return { loaded: false, signals: await collectWangwangChatPageSignals(page) };
}

async function waitForWangwangUrl(context, page, previousPages, timeoutMs) {
  const startedAt = Date.now();
  const resolvedTimeoutMs = Math.min(Math.max(Number(timeoutMs) || 3000, 1000), 5000);

  while (Date.now() - startedAt < resolvedTimeoutMs) {
    const currentPageResult = await findWangwangUrlOnPage(page);
    if (currentPageResult.url) {
      return { ...currentPageResult, page };
    }

    for (const candidatePage of context.pages?.() ?? []) {
      if (!candidatePage || previousPages.has(candidatePage) || candidatePage.isClosed?.()) {
        continue;
      }

      await candidatePage.waitForLoadState?.("domcontentloaded", { timeout: 1200 }).catch(() => {});
      const newPageResult = await findWangwangUrlOnPage(candidatePage);
      if (newPageResult.url) {
        return { ...newPageResult, method: newPageResult.method === "frame" ? "new_page_frame" : "new_page", page: candidatePage };
      }
    }

    await randomPause(250, 450);
  }

  return { url: "", method: "", page: null };
}

function createEmptyWangwangScan(reason = "contact_page_not_reached") {
  return {
    wangwangUrl: "",
    wangwangFetchStatus: "skipped",
    wangwangFetchMethod: "",
    wangwangFetchReason: reason,
    wangwangChatPageLoaded: false,
    wangwangChatPageSignals: [],
  };
}

async function fetchWangwangUrlFromContactPage(context, page, timeoutMs) {
  const target = await findVisibleXPathLocator(page, WANGWANG_ENTRY_XPATHS);
  if (!target) {
    return {
      wangwangUrl: "",
      wangwangFetchStatus: "skipped",
      wangwangFetchMethod: "",
      wangwangFetchReason: "wangwang_entry_missing",
      wangwangChatPageLoaded: false,
      wangwangChatPageSignals: [],
    };
  }

  const previousPages = new Set(context.pages?.() ?? []);

  await page.mouse.move(
    target.box.x + target.box.width / 2,
    target.box.y + target.box.height / 2,
    { steps: 12 + Math.floor(Math.random() * 12) },
  ).catch(() => {});
  await randomPause(120, 260);
  await page.mouse.down().catch(() => {});
  await randomPause(80, 160);
  await page.mouse.up().catch(async () => {
    await target.locator.click({ delay: 80, force: true }).catch(() => {});
  });

  await Promise.race([
    page.waitForURL((url) => looksLikeWangwangUrl(String(url)), { timeout: Math.min(Number(timeoutMs) || 3000, 5000) }).catch(() => {}),
    page.waitForLoadState("domcontentloaded", { timeout: Math.min(Number(timeoutMs) || 3000, 5000) }).catch(() => {}),
    randomPause(800, 1200),
  ]).catch(() => {});

  const result = await waitForWangwangUrl(context, page, previousPages, timeoutMs);

  if (!result.url) {
    for (const candidatePage of context.pages?.() ?? []) {
      if (candidatePage && candidatePage !== page && !previousPages.has(candidatePage)) {
        await closePageSafely(candidatePage);
      }
    }
    return {
      wangwangUrl: "",
      wangwangFetchStatus: "not_found",
      wangwangFetchMethod: "",
      wangwangFetchReason: "wangwang_url_not_found_after_click",
      wangwangChatPageLoaded: false,
      wangwangChatPageSignals: [],
    };
  }

  const chatPage = result.page ?? page;
  const chatPageStatus = await waitForWangwangChatPageLoaded(chatPage, timeoutMs);

  if (result.page && result.page !== page) {
    await closePageSafely(result.page);
  }

  if (!chatPageStatus.loaded) {
    return {
      wangwangUrl: "",
      wangwangFetchStatus: "not_found",
      wangwangFetchMethod: result.method || target.method,
      wangwangFetchReason: "wangwang_chat_page_ui_not_loaded",
      wangwangChatPageLoaded: false,
      wangwangChatPageSignals: chatPageStatus.signals,
    };
  }

  return {
    wangwangUrl: normalizeWangwangChatUrl(result.url),
    wangwangFetchStatus: "ok",
    wangwangFetchMethod: result.method || target.method,
    wangwangFetchReason: "",
    wangwangChatPageLoaded: true,
    wangwangChatPageSignals: chatPageStatus.signals,
  };
}

export async function scanOfferContactsByNavigation(context, params) {
  const normalizedOfferUrl = normalizeText(params.offerUrl);
  if (!normalizedOfferUrl) {
    return {
      detectedContacts: [],
      detailContacts: [],
      contactPageContacts: [],
      detailTitle: "",
      preferredTitle: "",
      contactPageUrl: "",
      status: "skipped",
      reason: "missing_offer_url",
      screenshotPath: "",
      offerScreenshotStatus: "skipped",
      contactFetchStatus: "skipped",
      contactFetchMethod: "",
      wangwangUrl: "",
      wangwangFetchStatus: "skipped",
      wangwangFetchMethod: "",
      wangwangFetchReason: "missing_offer_url",
      wangwangChatPageLoaded: false,
      wangwangChatPageSignals: [],
    };
  }

  const page = await context.newPage();

  try {
    const offerNavigation = await gotoPage(page, normalizedOfferUrl, params.timeoutMs, "[contact-nav][验证码]", OFFER_VERIFICATION_OPTIONS);
    if (!offerNavigation.ok) {
      return {
        detectedContacts: [],
        detailContacts: [],
        contactPageContacts: [],
        detailTitle: "",
        preferredTitle: "",
        contactPageUrl: "",
        status: offerNavigation.reason === "login_required" ? "blocked" : "error",
        reason: offerNavigation.reason,
        screenshotPath: "",
        offerScreenshotStatus: "skipped",
        contactFetchStatus: "skipped",
        contactFetchMethod: "",
        wangwangUrl: "",
        wangwangFetchStatus: "skipped",
        wangwangFetchMethod: "",
        wangwangFetchReason: offerNavigation.reason,
        wangwangChatPageLoaded: false,
        wangwangChatPageSignals: [],
      };
    }

    const screenshotResult = await captureOfferDetailScreenshot(page, params.screenshotPath, params.timeoutMs);
    const savedScreenshotPath = screenshotResult.screenshotPath;
    const offerScreenshotStatus = screenshotResult.offerScreenshotStatus;
    if (offerScreenshotStatus === "captcha_unresolved" || (params.screenshotPath && offerScreenshotStatus !== "ok")) {
      return {
        detectedContacts: [],
        detailContacts: [],
        contactPageContacts: [],
        detailTitle: "",
        preferredTitle: "",
        contactPageUrl: "",
        status: "error",
        reason: screenshotResult.reason || offerScreenshotStatus,
        screenshotPath: "",
        offerScreenshotStatus,
        contactFetchStatus: "skipped",
        contactFetchMethod: "",
        wangwangUrl: "",
        wangwangFetchStatus: "skipped",
        wangwangFetchMethod: "",
        wangwangFetchReason: screenshotResult.reason || offerScreenshotStatus,
        wangwangChatPageLoaded: false,
        wangwangChatPageSignals: [],
      };
    }

    const detailText = await collectPageText(page);
    const detailContacts = extractDetectedContacts(detailText, "product_detail");
    const detailTitle = await extractDetailTitle(page, params.merchantName ?? "");
    const preferredTitle = pickPreferredDetailTitle(params.currentTitle ?? "", detailTitle, params.merchantName ?? "");

    let contactPageUrl = "";
    let contactPageContacts = [];
    let contactFetchMethod = "";
    let contactFetchStatus = "skipped";
    let navigationReason = "";
    let wangwangScan = createEmptyWangwangScan();

    const directContactUrls = [
      ...build1688ContactPageUrls(params.merchantUrl ?? ""),
      ...build1688ContactPageUrls(params.contactPageUrl ?? ""),
    ].filter((url, index, urls) => url && urls.indexOf(url) === index);

    for (const directUrl of directContactUrls) {
      const directNavigation = await gotoPage(page, directUrl, params.timeoutMs, "[contact-direct][验证码]").catch((error) => ({
        ok: false,
        reason: error instanceof Error ? error.message : String(error),
      }));
      if (!directNavigation.ok) {
        navigationReason = directNavigation.reason || "contact_page_direct_failed";
        contactFetchStatus = "error";
        continue;
      }

      const contactPageText = await collectPageText(page);
      const directContacts = extractDetectedContacts(contactPageText, "contact_page");
      contactPageUrl = page.url();
      contactPageContacts = directContacts;
      if (!wangwangScan.wangwangUrl) {
        wangwangScan = await fetchWangwangUrlFromContactPage(context, page, params.timeoutMs)
          .catch((error) => ({
            wangwangUrl: "",
            wangwangFetchStatus: "error",
            wangwangFetchMethod: "",
            wangwangFetchReason: error instanceof Error ? error.message : String(error),
            wangwangChatPageLoaded: false,
            wangwangChatPageSignals: [],
          }));
      }
      contactFetchMethod = "direct_contact_url";
      contactFetchStatus = directContacts.length > 0 ? "ok" : "no_contacts";
      navigationReason = directContacts.length > 0 ? "" : "contact_page_no_contacts";
      if (directContacts.length > 0) {
        break;
      }
    }

    if (contactPageContacts.length === 0) {
      const backToOffer = await gotoPage(page, normalizedOfferUrl, params.timeoutMs, "[contact-nav][验证码]").catch((error) => ({
        ok: false,
        reason: error instanceof Error ? error.message : String(error),
      }));
      if (backToOffer.ok) {
        const productEntry = await clickTextNavigation(page, PRODUCT_ENTRY_PATTERNS, params.timeoutMs, /商品|店铺|所有类目|联系方式/i, "product_entry_missing");
        if (!productEntry.ok) {
          navigationReason = navigationReason || productEntry.reason;
        } else {
          const contactEntry = await clickTextNavigation(page, CONTACT_ENTRY_PATTERNS, params.timeoutMs, /联系方式|电话|手机|联系/i, "contact_entry_missing");
          if (!contactEntry.ok) {
            navigationReason = navigationReason || contactEntry.reason;
          } else {
            contactPageUrl = page.url();
            const contactPageText = await collectPageText(page);
            contactPageContacts = extractDetectedContacts(contactPageText, "contact_page");
            if (!wangwangScan.wangwangUrl) {
              wangwangScan = await fetchWangwangUrlFromContactPage(context, page, params.timeoutMs)
                .catch((error) => ({
                  wangwangUrl: "",
                  wangwangFetchStatus: "error",
                  wangwangFetchMethod: "",
                  wangwangFetchReason: error instanceof Error ? error.message : String(error),
                  wangwangChatPageLoaded: false,
                  wangwangChatPageSignals: [],
                }));
            }
            contactFetchMethod = "click_navigation";
            contactFetchStatus = contactPageContacts.length > 0 ? "ok" : "no_contacts";
            navigationReason = contactPageContacts.length > 0 ? "" : "contact_entry_no_contacts";
          }
        }
      } else {
        navigationReason = navigationReason || backToOffer.reason || "offer_reload_failed";
      }
    }

    const detectedContacts = mergeDetectedContacts(detailContacts, contactPageContacts);
    const status = navigationReason && detectedContacts.length === 0 ? "error" : "ok";

    return {
      detectedContacts,
      detailContacts,
      contactPageContacts,
      detailTitle,
      preferredTitle,
      contactPageUrl,
      status,
      reason: navigationReason,
      screenshotPath: savedScreenshotPath,
      offerScreenshotStatus,
      contactFetchStatus,
      contactFetchMethod,
      wangwangUrl: wangwangScan.wangwangUrl,
      wangwangFetchStatus: wangwangScan.wangwangFetchStatus,
      wangwangFetchMethod: wangwangScan.wangwangFetchMethod,
      wangwangFetchReason: wangwangScan.wangwangFetchReason,
      wangwangChatPageLoaded: wangwangScan.wangwangChatPageLoaded,
      wangwangChatPageSignals: wangwangScan.wangwangChatPageSignals,
    };
  } catch (error) {
    return {
      detectedContacts: [],
      detailContacts: [],
      contactPageContacts: [],
      detailTitle: "",
      preferredTitle: "",
      contactPageUrl: "",
      status: "error",
      reason: error instanceof Error ? error.message : String(error),
      screenshotPath: "",
      offerScreenshotStatus: "error",
      contactFetchStatus: "error",
      contactFetchMethod: "",
      wangwangUrl: "",
      wangwangFetchStatus: "error",
      wangwangFetchMethod: "",
      wangwangFetchReason: error instanceof Error ? error.message : String(error),
      wangwangChatPageLoaded: false,
      wangwangChatPageSignals: [],
    };
  } finally {
    await closePageSafely(page);
  }
}
