import { readFile, rm } from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { setTimeout as sleep } from "node:timers/promises";

import { chromium } from "playwright";

const STEP_DELAY_MIN_MS = 500;
const STEP_DELAY_MAX_MS = 2000;
const STEP_WAIT_MIN_MS = 2000;
const STEP_WAIT_MAX_MS = 3000;
const VERIFICATION_SCAN_INTERVAL_MS = 1200;
const VERIFICATION_RETRY_COOLDOWN_MS = 4000;
const MANUAL_VERIFICATION_POLL_MIN_MS = 900;
const MANUAL_VERIFICATION_POLL_MAX_MS = 1400;
const DRAG_TO_END_RETRY_DELAY_MIN_MS = 600;
const DRAG_TO_END_RETRY_DELAY_MAX_MS = 1000;
const DEFAULT_DRAG_TO_END_DURATION_MIN_MS = 1500;
const DEFAULT_DRAG_TO_END_DURATION_MAX_MS = 2000;
const DRAG_TO_END_HANDLE_WAIT_MS = 5000;
const DRAG_TO_END_HANDLE_WAIT_INTERVAL_MS = 200;
const POST_DRAG_VERIFICATION_SETTLE_MS = 4500;
const POST_DRAG_VERIFICATION_SETTLE_INTERVAL_MS = 300;
const DEFAULT_AUTO_VERIFICATION_ATTEMPT_LIMIT = 2;
const SEARCH_CHILD_EVENT_PREFIX = "__SEARCH_EVENT__:";
const VERIFICATION_URL_PATTERN = /x5step=1|\/punish\b/i;
const DRAG_TO_END_TEXT_PATTERN = /拖动到最右边|拖动下方滑块完成验证|请按住滑块|通过验证以确保正常访问/u;
const TARGET_POSITION_TEXT_PATTERN = /按照说明拖动滑块|拖动滑块出现完整的|拖动到指定位置|拖动滑块完成拼图|请完成下列拼图|请拖动滑块还原图像/u;
const VERIFICATION_FAILURE_TEXT_PATTERN = /验证失败|二次验证|点击框体重试|点击框体再试|点击框体继续/u;
const VERIFICATION_TEXT_PATTERN = /验证码拦截|拖动下方滑块完成验证|请按住滑块|通过验证以确保正常访问|验证失败|二次验证|拖动到最右边|按照说明拖动滑块|拖动滑块出现完整的|拖动到指定位置|拖动滑块完成拼图|请完成下列拼图|请拖动滑块还原图像/i;
const SLIDER_HANDLE_SELECTORS = [
  "#scratch-captcha-btn",
  "#scratch-captcha-btn-icon",
  "#nc_1_n1z",
  "[id^='nc_'][id$='_n1z']",
  ".nc_wrapper .btn_slide",
  ".nc_scale .btn_slide",
  ".btn_slide",
  '[class*="slider"][class*="btn"]',
  '[class*="Slider"][class*="btn"]',
  '[class*="slider"][class*="handle"]',
  '[class*="Slider"][class*="handle"]',
  '[class*="captcha"][class*="btn"]',
  '[class*="Captcha"][class*="btn"]',
  '[class*="scratch"][class*="btn"]',
  '[class*="Scratch"][class*="btn"]',
  '[class*="verify"][class*="btn"]',
  '[class*="Verify"][class*="btn"]',
  '[class*="handle"]',
  '[class*="Handle"]',
  '[role="slider"]',
];
const SLIDER_TRACK_SELECTORS = [
  ".scratch-captcha-slider",
  ".scratch-captcha-container",
  "#nc_1__scale",
  "[id^='nc_'][id*='__scale']",
  ".nc_wrapper .nc_scale",
  ".nc_scale",
  '[class*="slider"][class*="bar"]',
  '[class*="Slider"][class*="bar"]',
  '[class*="slider"][class*="track"]',
  '[class*="Slider"][class*="track"]',
  '[class*="captcha"][class*="slider"]',
  '[class*="Captcha"][class*="slider"]',
  '[class*="scratch"][class*="slider"]',
  '[class*="Scratch"][class*="slider"]',
  '[class*="verify"][class*="bar"]',
  '[class*="Verify"][class*="bar"]',
  '[class*="track"]',
  '[class*="Track"]',
];
const verificationStates = new WeakMap();

function resolveManualInterventionCommandPath() {
  const runtimeRootDir = process.env.SEARCH_TASK_RUNTIME_ROOT_DIR ?? "";
  if (!runtimeRootDir) {
    return "";
  }

  return path.join(runtimeRootDir, "manual-intervention-command.json");
}

export function randomBetween(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function normalizeDelayRange(min = STEP_DELAY_MIN_MS, max = STEP_DELAY_MAX_MS) {
  const parsedMin = Number.isFinite(min) ? Math.round(min) : STEP_DELAY_MIN_MS;
  const parsedMax = Number.isFinite(max) ? Math.round(max) : STEP_DELAY_MAX_MS;
  const lower = Math.min(parsedMin, parsedMax);
  const upper = Math.max(parsedMin, parsedMax);

  if (upper <= STEP_DELAY_MIN_MS) {
    return { min: STEP_DELAY_MIN_MS, max: STEP_DELAY_MIN_MS };
  }

  if (lower >= STEP_DELAY_MAX_MS) {
    return { min: STEP_DELAY_MAX_MS, max: STEP_DELAY_MAX_MS };
  }

  return {
    min: Math.max(STEP_DELAY_MIN_MS, lower),
    max: Math.max(STEP_DELAY_MIN_MS, Math.min(STEP_DELAY_MAX_MS, upper))
  };
}

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

function normalizePositiveInteger(value, fallback) {
  const parsed = Number(value);
  return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : fallback;
}

export function resolveDragToEndDurationMs(options = {}) {
  const directDuration = options.dragDurationMs;
  if (Number.isFinite(Number(directDuration)) && Number(directDuration) > 0) {
    return Math.round(Number(directDuration));
  }

  if (directDuration && typeof directDuration === "object") {
    const min = normalizePositiveInteger(directDuration.min, DEFAULT_DRAG_TO_END_DURATION_MIN_MS);
    const max = normalizePositiveInteger(directDuration.max, DEFAULT_DRAG_TO_END_DURATION_MAX_MS);
    return randomBetween(Math.min(min, max), Math.max(min, max));
  }

  const min = normalizePositiveInteger(options.dragDurationMinMs, DEFAULT_DRAG_TO_END_DURATION_MIN_MS);
  const max = normalizePositiveInteger(options.dragDurationMaxMs, DEFAULT_DRAG_TO_END_DURATION_MAX_MS);
  return randomBetween(Math.min(min, max), Math.max(min, max));
}

export function buildDragSegmentPauses(totalDurationMs, segmentCount) {
  const count = Math.max(1, Math.round(segmentCount));
  const total = Math.max(0, Math.round(totalDurationMs));
  if (total === 0) {
    return Array.from({ length: count }, () => 0);
  }

  const weights = Array.from({ length: count }, () => randomBetween(80, 140));
  const weightTotal = weights.reduce((sum, weight) => sum + weight, 0) || count;
  const pauses = weights.map((weight) => Math.floor((total * weight) / weightTotal));
  const assigned = pauses.reduce((sum, pause) => sum + pause, 0);
  pauses[pauses.length - 1] += total - assigned;
  return pauses;
}

async function readBodyText(frame) {
  try {
    return normalizeText(await frame.locator("body").innerText({ timeout: 1200 }));
  } catch {
    try {
      return normalizeText(await frame.evaluate(() => document.body?.innerText ?? ""));
    } catch {
      return "";
    }
  }
}

async function findVisibleLocator(frame, selectors) {
  for (const selector of selectors) {
    const locator = frame.locator(selector).first();
    const count = await locator.count().catch(() => 0);

    if (count === 0) {
      continue;
    }

    if (await locator.isVisible().catch(() => false)) {
      return { locator, selector };
    }
  }

  return null;
}

async function findGeometricSliderLocators(frame) {
  const locator = frame.locator("div,span,button,[role='button'],[role='slider']");
  const count = Math.min(await locator.count().catch(() => 0), 350);
  const candidates = [];

  for (let index = 0; index < count; index += 1) {
    const item = locator.nth(index);
    const visible = await item.isVisible().catch(() => false);
    if (!visible) {
      continue;
    }

    const box = await item.boundingBox().catch(() => null);
    if (!box || box.width <= 0 || box.height <= 0) {
      continue;
    }

    candidates.push({ locator: item, box, index });
  }

  const tracks = candidates.filter(({ box }) => (
    box.width >= 160
    && box.height >= 24
    && box.height <= 96
  ));
  const handles = candidates.filter(({ box }) => (
    box.width >= 24
    && box.width <= 100
    && box.height >= 24
    && box.height <= 100
  ));

  for (const track of tracks) {
    const trackRight = track.box.x + track.box.width;
    const trackBottom = track.box.y + track.box.height;
    for (const handle of handles) {
      if (handle.index === track.index) {
        continue;
      }

      const handleCenterX = handle.box.x + handle.box.width / 2;
      const handleCenterY = handle.box.y + handle.box.height / 2;
      const insideTrackY = handleCenterY >= track.box.y - 12 && handleCenterY <= trackBottom + 12;
      const nearLeftOfTrack = handleCenterX >= track.box.x - 12 && handleCenterX <= track.box.x + Math.max(96, track.box.width * 0.35);
      const narrowerThanTrack = handle.box.width < track.box.width * 0.55;

      if (insideTrackY && nearLeftOfTrack && narrowerThanTrack && trackRight > handleCenterX + 80) {
        return {
          handle: { locator: handle.locator, selector: "geometric_slider_handle" },
          track: { locator: track.locator, selector: "geometric_slider_track" },
        };
      }
    }
  }

  return null;
}

function createManualInterventionAlertId() {
  return `manual-${Date.now()}-${randomBetween(100000, 999999)}`;
}

async function takeManualInterventionCommand() {
  const commandPath = resolveManualInterventionCommandPath();
  if (!commandPath) {
    return null;
  }

  try {
    const rawText = await readFile(commandPath, "utf8");
    await rm(commandPath, { force: true }).catch(() => {});
    const parsed = JSON.parse(rawText);
    return parsed && typeof parsed === "object" ? parsed : null;
  } catch {
    return null;
  }
}

function emitSearchChildEvent(event) {
  try {
    const payload = {
      taskId: process.env.SEARCH_TASK_ID ?? "",
      accountId: process.env.SEARCH_1688_ACCOUNT_ID ?? "",
      materialId: process.env.SEARCH_TASK_CURRENT_MATERIAL_ID ?? "",
      materialName: process.env.SEARCH_TASK_CURRENT_MATERIAL_NAME ?? "",
      ...event,
    };
    console.log(`${SEARCH_CHILD_EVENT_PREFIX}${JSON.stringify(payload)}`);
  } catch (error) {
    console.warn(`[验证码] 输出人工介入事件失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

function classifyVerificationTarget(target) {
  const text = normalizeText(target?.bodyText ?? "");
  const selectorFingerprint = `${target?.handleSelector ?? ""} ${target?.trackSelector ?? ""}`;
  const isDragToEndVerification = DRAG_TO_END_TEXT_PATTERN.test(text);
  const isTargetPositionVerification = TARGET_POSITION_TEXT_PATTERN.test(text);

  if (isTargetPositionVerification) {
    return {
      mode: "manual_only",
      kind: "captcha_target_position",
      manualMessage: "检测到需要拖动到指定位置的验证码，请人工处理后继续。",
    };
  }

  if (isDragToEndVerification) {
    return {
      mode: "auto_retry_until_resolved",
      kind: "captcha_drag_to_end",
      manualMessage: "全屏滑块验证码自动尝试失败，请人工处理后继续。",
    };
  }

  if (target?.blockedOnly) {
    return {
      mode: "manual_only",
      kind: "captcha_blocked_page",
      manualMessage: "检测到验证码拦截页面，请人工完成验证后继续。",
    };
  }

  if (selectorFingerprint.includes("#scratch-captcha-btn")) {
    return {
      mode: "manual_only",
      kind: "captcha_target_position",
      manualMessage: "检测到需要拖动到指定位置的验证码，请人工处理后继续。",
    };
  }

  return {
    mode: "manual_only",
    kind: "captcha_unknown",
    manualMessage: "检测到无法自动处理的验证码，请人工完成验证后继续。",
  };
}

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

  const frames = page.frames();
  const mainFrame = typeof page.mainFrame === "function" ? page.mainFrame() : frames[0];
  for (const frame of frames) {
    const frameUrl = String(frame.url?.() ?? "");
    const bodyText = await readBodyText(frame);
    const hasStrongUrlEvidence = frame === mainFrame && VERIFICATION_URL_PATTERN.test(frameUrl);
    const hasStrongTextEvidence = VERIFICATION_TEXT_PATTERN.test(bodyText);
    const pageLooksBlocked = hasStrongUrlEvidence || hasStrongTextEvidence;
    const handleMatch = await findVisibleLocator(frame, SLIDER_HANDLE_SELECTORS);
    const trackMatch = await findVisibleLocator(frame, SLIDER_TRACK_SELECTORS);
    const geometricMatch = !handleMatch || !trackMatch
      ? await findGeometricSliderLocators(frame).catch(() => null)
      : null;
    const resolvedHandleMatch = handleMatch ?? geometricMatch?.handle ?? null;
    const resolvedTrackMatch = trackMatch ?? geometricMatch?.track ?? null;
    const handle = resolvedHandleMatch?.locator ?? null;
    const track = resolvedTrackMatch?.locator ?? null;

    if (!pageLooksBlocked) {
      continue;
    }

    if (!handle || !track) {
      return {
        frame,
        frameUrl,
        bodyText,
        handle: null,
        track: null,
        handleBox: null,
        trackBox: null,
        handleSelector: resolvedHandleMatch?.selector ?? "",
        trackSelector: resolvedTrackMatch?.selector ?? "",
        blockedOnly: true
      };
    }

    const handleBox = await handle.boundingBox().catch(() => null);
    const trackBox = await track.boundingBox().catch(() => null);
    if (!handleBox || !trackBox || trackBox.width <= handleBox.width || trackBox.width < 120) {
      return {
        frame,
        frameUrl,
        bodyText,
        handle: null,
        track: null,
        handleBox: null,
        trackBox: null,
        handleSelector: resolvedHandleMatch?.selector ?? "",
        trackSelector: resolvedTrackMatch?.selector ?? "",
        blockedOnly: true
      };
    }

    return {
      frame,
      frameUrl,
      bodyText,
      handle,
      track,
      handleBox,
      trackBox,
      handleSelector: resolvedHandleMatch?.selector ?? "",
      trackSelector: resolvedTrackMatch?.selector ?? "",
      blockedOnly: false
    };
  }

  return null;
}

async function waitForPostDragVerificationTarget(page, options = {}) {
  const timeoutMs = normalizePositiveInteger(options.postDragSettleMs, POST_DRAG_VERIFICATION_SETTLE_MS);
  const intervalMs = normalizePositiveInteger(options.postDragSettleIntervalMs, POST_DRAG_VERIFICATION_SETTLE_INTERVAL_MS);
  const startedAt = Date.now();
  let latestTarget = null;

  while (!page.isClosed()) {
    const nextTarget = await detectVerificationTarget(page);
    if (!nextTarget) {
      return null;
    }

    latestTarget = nextTarget;
    if (VERIFICATION_FAILURE_TEXT_PATTERN.test(normalizeText(nextTarget.bodyText))) {
      return nextTarget;
    }

    if (Date.now() - startedAt >= timeoutMs) {
      return latestTarget;
    }

    await sleep(intervalMs);
  }

  return latestTarget;
}

async function waitForDragToEndSliderTarget(page, initialTarget, options = {}) {
  const timeoutMs = normalizePositiveInteger(options.handleWaitMs, DRAG_TO_END_HANDLE_WAIT_MS);
  const startedAt = Date.now();
  let latestTarget = initialTarget;

  while (!page.isClosed()) {
    if (latestTarget?.handle && latestTarget?.track && latestTarget?.handleBox && latestTarget?.trackBox) {
      return latestTarget;
    }

    if (Date.now() - startedAt >= timeoutMs) {
      return latestTarget;
    }

    await sleep(DRAG_TO_END_HANDLE_WAIT_INTERVAL_MS);
    const nextTarget = await detectVerificationTarget(page);
    if (!nextTarget) {
      return null;
    }

    latestTarget = nextTarget;
    if (classifyVerificationTarget(latestTarget).mode !== "auto_retry_until_resolved") {
      return latestTarget;
    }
  }

  return latestTarget;
}

async function refreshDragToEndSliderTarget(page, target, options = {}) {
  const nextTarget = await waitForDragToEndSliderTarget(page, target, options);
  if (!nextTarget) {
    return null;
  }

  if (nextTarget?.handle && nextTarget?.track && nextTarget?.handleBox && nextTarget?.trackBox) {
    return nextTarget;
  }

  return nextTarget;
}

async function dragSliderLikeHuman(page, target, options = {}) {
  const { handleBox, trackBox } = target;
  const startX = handleBox.x + handleBox.width / 2;
  const startY = handleBox.y + handleBox.height / 2 + randomBetween(-2, 2);
  const trackRight = trackBox.x + trackBox.width;
  const rightPadding = Math.max(4, Math.round(handleBox.width * 0.15));
  const targetX = Math.min(trackRight - 2, Math.max(startX + 100, trackRight - rightPadding));
  const checkpoints = [0.05, 0.12, 0.22, 0.34, 0.48, 0.62, 0.75, 0.86, 0.94, 0.985, 1];
  const dragDurationMs = resolveDragToEndDurationMs(options);
  const segmentPauses = buildDragSegmentPauses(dragDurationMs, checkpoints.length);

  await page.mouse.move(Math.max(0, startX - randomBetween(12, 24)), startY + randomBetween(-4, 4), {
    steps: randomBetween(8, 16)
  });
  await randomPause(60, 140);
  await page.mouse.move(startX, startY, { steps: randomBetween(4, 8) });
  await randomPause(140, 260);
  await page.mouse.down();
  await randomPause(120, 220);

  for (const [index, checkpoint] of checkpoints.entries()) {
    const nextX = startX + (targetX - startX) * checkpoint + (checkpoint < 1 ? randomBetween(-6, 6) : 0);
    const nextY = startY + randomBetween(-3, 3);
    await page.mouse.move(nextX, nextY, { steps: randomBetween(18, 34) });
    if (segmentPauses[index] > 0) {
      await sleep(segmentPauses[index]);
    }
  }

  const overshootX = Math.min(trackRight - 2, targetX + randomBetween(3, 8));
  if (overshootX > targetX + 1) {
    await page.mouse.move(overshootX, startY + randomBetween(-2, 2), { steps: randomBetween(4, 8) });
    await randomPause(100, 220);
    await page.mouse.move(targetX, startY + randomBetween(-2, 2), { steps: randomBetween(3, 6) });
  }

  await randomPause(160, 280);
  await page.mouse.up();
}

async function clickPointLikeHuman(page, x, y) {
  const targetX = Math.max(0, x + randomBetween(-2, 2));
  const targetY = Math.max(0, y + randomBetween(-2, 2));

  await page.mouse.move(targetX, targetY, { steps: randomBetween(6, 12) });
  await randomPause(40, 100);
  await page.mouse.click(targetX, targetY, { delay: randomBetween(60, 160) });
}

async function clickDragToEndRetrySurface(page, target, logPrefix) {
  const retryHint = target?.frame?.locator("text=/验证失败|点击框体重试|点击框体再试|点击框体继续/u").first();
  const retryHintVisible = retryHint
    ? await retryHint.isVisible().catch(() => false)
    : false;

  if (retryHintVisible) {
    const retryHintBox = await retryHint.boundingBox().catch(() => null);
    if (retryHintBox) {
      await clickPointLikeHuman(
        page,
        retryHintBox.x + retryHintBox.width / 2,
        retryHintBox.y + retryHintBox.height / 2,
      );
      console.log(`${logPrefix} 检测到全屏滑块失败提示，已点击框体重新尝试。`);
      return true;
    }
  }

  if (target?.trackBox) {
    await clickPointLikeHuman(
      page,
      target.trackBox.x + Math.max(20, Math.round(target.trackBox.width * 0.18)),
      target.trackBox.y + Math.max(10, Math.round(target.trackBox.height / 2)),
    );
    console.log(`${logPrefix} 未定位到失败提示文案，已点击滑块框体重新尝试。`);
    return true;
  }

  return false;
}

async function autoResolveDragToEndVerification(page, initialTarget, options = {}) {
  const logPrefix = options.logPrefix ?? "[验证码]";
  const autoAttemptLimit = Number.isInteger(options.autoAttemptLimit) && options.autoAttemptLimit > 0
    ? options.autoAttemptLimit
    : DEFAULT_AUTO_VERIFICATION_ATTEMPT_LIMIT;
  let attempt = 0;
  let currentTarget = initialTarget;

  while (!page.isClosed()) {
    const currentClassification = classifyVerificationTarget(currentTarget);
    if (currentClassification.mode !== "auto_retry_until_resolved") {
      return waitForManualVerification(page, currentTarget, {
        ...options,
        message: currentClassification.manualMessage,
      });
    }

    if (!currentTarget?.handle || !currentTarget?.track || !currentTarget?.handleBox || !currentTarget?.trackBox) {
      currentTarget = await waitForDragToEndSliderTarget(page, currentTarget, options);
      if (!currentTarget) {
        return true;
      }
      if (!currentTarget?.handle || !currentTarget?.track || !currentTarget?.handleBox || !currentTarget?.trackBox) {
        console.warn(`${logPrefix} 已识别为全屏滑块验证，但未定位到滑块手柄/轨道。`);
        if (options.manualOnAutoVerificationFailure === false) {
          return false;
        }
        return waitForManualVerification(page, currentTarget, {
          ...options,
          message: "全屏滑块验证码未定位到滑块控件，请人工处理后继续。",
        });
      }
    }

    attempt += 1;
    console.log(`${logPrefix} 检测到全屏滑块验证，执行第 ${attempt} 次自动拖动尝试。`);

    try {
      await dragSliderLikeHuman(page, currentTarget, options);
    } catch (error) {
      console.warn(`${logPrefix} 第 ${attempt} 次自动拖动失败: ${error instanceof Error ? error.message : String(error)}`);
    }

    await Promise.race([
      page.waitForLoadState("domcontentloaded", { timeout: 4000 }).catch(() => {}),
      sleep(1200),
    ]);
    await randomPause(500, 900);

    let nextTarget = await waitForPostDragVerificationTarget(page, options);
    if (!nextTarget) {
      console.log(`${logPrefix} 自动拖动后验证已解除。`);
      return true;
    }

    if (attempt >= autoAttemptLimit) {
      console.warn(`${logPrefix} 全屏滑块自动拖动已尝试 ${attempt} 次仍未解除，转入人工处理。`);
      if (options.manualOnAutoVerificationFailure === false) {
        return false;
      }
      return waitForManualVerification(page, nextTarget, {
        ...options,
        message: "全屏滑块验证码自动尝试失败，请人工处理后继续。",
      });
    }

    const nextClassification = classifyVerificationTarget(nextTarget);
    if (nextClassification.mode !== "auto_retry_until_resolved") {
      return waitForManualVerification(page, nextTarget, {
        ...options,
        message: nextClassification.manualMessage,
      });
    }

    await clickDragToEndRetrySurface(page, nextTarget, logPrefix);
    await randomPause(DRAG_TO_END_RETRY_DELAY_MIN_MS, DRAG_TO_END_RETRY_DELAY_MAX_MS);

    nextTarget = await waitForPostDragVerificationTarget(page, options);
    if (!nextTarget) {
      console.log(`${logPrefix} 点击重试框体后验证已解除。`);
      return true;
    }

    if (attempt >= autoAttemptLimit) {
      console.warn(`${logPrefix} 全屏滑块点击重试后仍未解除，转入人工处理。`);
      if (options.manualOnAutoVerificationFailure === false) {
        return false;
      }
      return waitForManualVerification(page, nextTarget, {
        ...options,
        message: "全屏滑块验证码自动尝试失败，请人工处理后继续。",
      });
    }

    currentTarget = await refreshDragToEndSliderTarget(page, nextTarget, options);
    if (!currentTarget) {
      console.log(`${logPrefix} 点击重试框体后验证已解除。`);
      return true;
    }
  }

  return false;
}

function getVerificationState(page) {
  const current = verificationStates.get(page);
  if (current) {
    return current;
  }

  const next = {
    running: false,
    activePromise: null,
    lastAttemptAt: 0,
    manualWaiting: false,
    manualWaitPromise: null,
    activeAlertId: "",
    helperBrowser: null,
    helperContext: null,
    helperPage: null,
    helperObservedVerification: false,
    helperResolvedVerification: false,
  };
  verificationStates.set(page, next);
  return next;
}

export function isManualVerificationPending(page) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = verificationStates.get(page);
  return Boolean(state?.manualWaiting || state?.manualWaitPromise || state?.activePromise);
}

export function isManualVerificationWaiting(page) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = verificationStates.get(page);
  return Boolean(state?.manualWaiting || state?.manualWaitPromise);
}

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

  const state = verificationStates.get(page);
  const pendingPromise = state?.activePromise ?? state?.manualWaitPromise;
  if (!pendingPromise) {
    return false;
  }

  return pendingPromise.catch(() => false);
}

async function closeManualHelperSession(state) {
  const helperBrowser = state.helperBrowser;
  state.helperBrowser = null;
  state.helperContext = null;
  state.helperPage = null;
  state.helperObservedVerification = false;
  state.helperResolvedVerification = false;

  if (!helperBrowser) {
    return;
  }

  await helperBrowser.close().catch(() => {});
}

async function syncManualHelperSessionBack(page, state) {
  const helperContext = state.helperContext;
  if (!helperContext || !page || page.isClosed()) {
    return;
  }

  const cookies = await helperContext.cookies().catch(() => []);
  if (Array.isArray(cookies) && cookies.length > 0) {
    await page.context().addCookies(cookies).catch(() => {});
  }
}

async function openManualVerificationBrowser(page, state, alertId, logPrefix, pageUrl = "") {
  const targetUrl = String(pageUrl || page.url?.() || "");

  if ((process.env.SEARCH_HEADLESS ?? "0") !== "1") {
    await page.bringToFront().catch(() => {});
    emitSearchChildEvent({
      type: "manual_intervention_message",
      alertId,
      kind: "captcha",
      message: "已切换到当前验证码页面，请在自动化浏览器窗口中完成人工验证。",
      pageUrl: targetUrl,
      detectedAt: new Date().toISOString(),
    });
    return;
  }

  if (state.helperPage && !state.helperPage.isClosed()) {
    await state.helperPage.bringToFront().catch(() => {});
    emitSearchChildEvent({
      type: "manual_intervention_message",
      alertId,
      kind: "captcha",
      message: "可视验证码窗口已打开，请在该窗口中完成人工验证。",
      pageUrl: targetUrl,
      detectedAt: new Date().toISOString(),
    });
    return;
  }

  const storageState = await page.context().storageState().catch(() => null);
  const helperBrowser = await chromium.launch({
    channel: "chrome",
    headless: false,
    args: ["--disable-blink-features=AutomationControlled"],
  });
  const helperContext = await helperBrowser.newContext({
    locale: "zh-CN",
    viewport: { width: 1440, height: 960 },
    storageState: storageState ?? undefined,
  });
  const helperPage = await helperContext.newPage();

  if (targetUrl) {
    await helperPage.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 30000 }).catch(() => {});
  }
  await helperPage.bringToFront().catch(() => {});

  state.helperBrowser = helperBrowser;
  state.helperContext = helperContext;
  state.helperPage = helperPage;

  emitSearchChildEvent({
    type: "manual_intervention_message",
    alertId,
    kind: "captcha",
    message: "已打开可视验证码窗口，请在新窗口中完成人工验证。",
    pageUrl: targetUrl,
    detectedAt: new Date().toISOString(),
  });
  console.log(`${logPrefix} 已打开可视验证码窗口。`);
}

async function waitForManualVerification(page, target, options = {}) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = getVerificationState(page);
  if (state.manualWaitPromise) {
    return state.manualWaitPromise;
  }

  const manualWaitPromise = (async () => {
    const logPrefix = options.logPrefix ?? "[验证码]";
    const classification = classifyVerificationTarget(target);
    const alertId = createManualInterventionAlertId();
    const initialMessage = options.message ?? classification.manualMessage;

    state.manualWaiting = true;
    state.activeAlertId = alertId;

    emitSearchChildEvent({
      type: "manual_intervention_required",
      alertId,
      kind: classification.kind,
      message: initialMessage,
      pageUrl: String(page.url?.() ?? ""),
      detectedAt: new Date().toISOString(),
    });
    console.warn(`${logPrefix} ${initialMessage}`);

    while (!page.isClosed()) {
      const command = await takeManualInterventionCommand();
      if (command?.type === "open_manual_browser") {
        await openManualVerificationBrowser(page, state, alertId, logPrefix, String(command.pageUrl ?? page.url?.() ?? ""));
      }

      if (state.helperPage && !state.helperPage.isClosed()) {
        const helperTarget = await detectVerificationTarget(state.helperPage);
        if (helperTarget) {
          state.helperObservedVerification = true;
        } else if (state.helperObservedVerification) {
          await syncManualHelperSessionBack(page, state);
          state.helperObservedVerification = false;
          state.helperResolvedVerification = true;
        }
      }

      await randomPause(MANUAL_VERIFICATION_POLL_MIN_MS, MANUAL_VERIFICATION_POLL_MAX_MS);
      if (state.helperResolvedVerification) {
        emitSearchChildEvent({
          type: "manual_intervention_resolved",
          alertId,
          kind: classification.kind,
          message: "验证码已在可视窗口解除，继续执行自动搜索。",
          pageUrl: String(page.url?.() ?? ""),
          resolvedAt: new Date().toISOString(),
        });
        console.log(`${logPrefix} 可视窗口人工验证已完成，继续执行。`);
        return true;
      }

      const nextTarget = await detectVerificationTarget(page);
      if (nextTarget) {
        continue;
      }

      emitSearchChildEvent({
        type: "manual_intervention_resolved",
        alertId,
        kind: classification.kind,
        message: "验证码已解除，继续执行自动搜索。",
        pageUrl: String(page.url?.() ?? ""),
        resolvedAt: new Date().toISOString(),
      });
      console.log(`${logPrefix} 人工验证已完成，继续执行。`);
      return true;
    }

    return false;
  })();

  state.manualWaitPromise = manualWaitPromise;
  state.activePromise = manualWaitPromise;

  return manualWaitPromise.finally(async () => {
    await closeManualHelperSession(state);
    state.manualWaiting = false;
    state.activeAlertId = "";
    if (state.manualWaitPromise === manualWaitPromise) {
      state.manualWaitPromise = null;
    }
    if (state.activePromise === manualWaitPromise) {
      state.activePromise = null;
    }
  });
}

export async function ensureSlideVerification(page, options = {}) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = getVerificationState(page);
  if (state.activePromise) {
    return state.activePromise;
  }

  if (state.running) {
    return false;
  }

  const currentTarget = await detectVerificationTarget(page);
  if (!currentTarget) {
    return false;
  }

  const now = Date.now();
  if (options.force !== true && now - state.lastAttemptAt < VERIFICATION_RETRY_COOLDOWN_MS) {
    return false;
  }

  const logPrefix = options.logPrefix ?? "[验证码]";
  const classification = classifyVerificationTarget(currentTarget);

  state.running = true;
  state.lastAttemptAt = now;
  const verificationPromise = (async () => {
    try {
      if (classification.mode === "auto_retry_until_resolved") {
        return await autoResolveDragToEndVerification(page, currentTarget, options);
      }

      if (!currentTarget.handle || !currentTarget.track || !currentTarget.handleBox || !currentTarget.trackBox) {
        return await waitForManualVerification(page, currentTarget, {
          ...options,
          message: classification.manualMessage,
        });
      }

      return await waitForManualVerification(page, currentTarget, {
        ...options,
        message: classification.manualMessage,
      });
    } catch (error) {
      console.warn(`${logPrefix} 自动拖动验证失败: ${error instanceof Error ? error.message : String(error)}`);
      if (classification.mode === "auto_retry_until_resolved" && options.manualOnAutoVerificationFailure === false) {
        return false;
      }
      return await waitForManualVerification(page, currentTarget, {
        ...options,
        message: classification.manualMessage,
      });
    }
  })();

  state.activePromise = verificationPromise;
  return verificationPromise.finally(() => {
    state.running = false;
    if (state.activePromise === verificationPromise) {
      state.activePromise = null;
    }
  });
}

export async function waitForVerificationClear(page, options = {}) {
  if (!page || page.isClosed()) {
    return false;
  }

  const state = getVerificationState(page);
  const pendingPromise = state.activePromise ?? state.manualWaitPromise;
  if (pendingPromise) {
    return pendingPromise.catch(() => false);
  }

  const currentTarget = await detectVerificationTarget(page);
  if (!currentTarget) {
    return false;
  }

  return ensureSlideVerification(page, { ...options, force: true });
}

export function attachVerificationMonitor(context, options = {}) {
  let disposed = false;
  let scanning = false;

  const scanAllPages = async (reason) => {
    if (disposed || scanning) {
      return;
    }

    scanning = true;
    try {
      for (const page of context.pages()) {
        await ensureSlideVerification(page, { ...options, reason });
      }
    } finally {
      scanning = false;
    }
  };

  const watchPage = (page) => {
    if (!page || page.isClosed()) {
      return;
    }

    const rescan = () => {
      void ensureSlideVerification(page, options);
    };

    page.on("domcontentloaded", rescan);
    page.on("load", rescan);
    void ensureSlideVerification(page, options);
  };

  for (const page of context.pages()) {
    watchPage(page);
  }

  const onPage = (page) => {
    watchPage(page);
  };
  context.on("page", onPage);

  const interval = setInterval(() => {
    void scanAllPages("interval");
  }, options.intervalMs ?? VERIFICATION_SCAN_INTERVAL_MS);
  interval.unref?.();

  return {
    dispose() {
      if (disposed) {
        return;
      }

      disposed = true;
      clearInterval(interval);
      context.off?.("page", onPage);
    }
  };
}

export async function randomPause(min = 120, max = 360) {
  await sleep(randomBetween(min, max));
}

export async function stepPause(reason = "step", min = STEP_WAIT_MIN_MS, max = STEP_WAIT_MAX_MS) {
  const ms = randomBetween(min, max);
  console.log(`  ⏳ [${reason}] 暂停 ${(ms / 1000).toFixed(1)}s...`);
  await sleep(ms);
}

export async function humanMoveTo(page, locator) {
  await waitForManualVerificationIfNeeded(page);
  await locator.scrollIntoViewIfNeeded().catch(() => {});
  const box = await locator.boundingBox();

  if (!box) {
    return;
  }

  const x = box.x + box.width * (0.25 + Math.random() * 0.5);
  const y = box.y + box.height * (0.25 + Math.random() * 0.5);
  await page.mouse.move(x, y, { steps: randomBetween(12, 28) });
  await randomPause(80, 180);
}

export async function humanClick(page, locator) {
  await waitForManualVerificationIfNeeded(page);
  await humanMoveTo(page, locator);
  await waitForManualVerificationIfNeeded(page);
  await locator.click({ delay: randomBetween(60, 180) });
  await randomPause(140, 320);
}

export async function humanType(page, locator, text, { clear = true } = {}) {
  await waitForManualVerificationIfNeeded(page);
  await humanClick(page, locator);

  if (clear) {
    await page.keyboard.press("Meta+A").catch(() => {});
    await randomPause(60, 140);
    await page.keyboard.press("Backspace").catch(() => {});
    await randomPause(80, 180);
  }

  for (const char of text) {
    await waitForManualVerificationIfNeeded(page);
    await page.keyboard.type(char, { delay: randomBetween(90, 220) });
    await randomPause(30, 120);
  }
}

export async function humanScrollPage(page, steps = 4) {
  for (let index = 0; index < steps; index += 1) {
    await waitForManualVerificationIfNeeded(page);
    await page.mouse.wheel(0, randomBetween(280, 640));
    await randomPause(350, 900);
  }
}

export async function antiDetectPause(min = STEP_DELAY_MIN_MS, max = STEP_DELAY_MAX_MS) {
  const { min: resolvedMin, max: resolvedMax } = normalizeDelayRange(min, max);
  const ms = randomBetween(resolvedMin, resolvedMax);
  console.log(`  🛡️ [反检测] 暂停 ${(ms / 1000).toFixed(1)}s...`);
  await sleep(ms);
}
