import type { Locator, Page } from "playwright";
import { RpaError } from "@/modules/rpa/errors";

const FIELD_VERIFY_MS = 8_000;
const FIELD_VERIFY_POLL_MS = 150;

function dispatchInputCommit(input: HTMLInputElement | HTMLTextAreaElement): void {
  input.dispatchEvent(new Event("input", { bubbles: true }));
  input.dispatchEvent(new Event("change", { bubbles: true }));
  input.blur();
}

/** 触发 change/blur，确保 React 表单捕获变更 */
export async function commitLocatorInput(loc: Locator): Promise<void> {
  await loc.evaluate((el) => {
    if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
      return;
    }
    dispatchInputCommit(el);
  });
}

function valuesEquivalent(expected: string, actual: string): boolean {
  if (expected === actual) {
    return true;
  }
  const expNum = Number(expected);
  const actNum = Number(actual);
  if (Number.isFinite(expNum) && Number.isFinite(actNum)) {
    return expNum === actNum;
  }
  return false;
}

/** 填写后循环校验 value，失败则重填一次 */
export async function fillLocatorWithVerify(
  page: Page,
  loc: Locator,
  expected: string,
  options?: { forceClear?: boolean; commit?: boolean },
): Promise<void> {
  const deadline = Date.now() + FIELD_VERIFY_MS;

  while (Date.now() < deadline) {
    await loc.click({ timeout: 3_000 }).catch(() => undefined);
    if (options?.forceClear) {
      await loc.click({ clickCount: 3, timeout: 3_000 }).catch(() => undefined);
      await loc.press("Control+A").catch(() => undefined);
      await loc.press("Backspace").catch(() => undefined);
    }
    await loc.fill(expected, { timeout: 8_000 });
    let actual = await loc.inputValue().catch(() => "");
    if (actual !== expected) {
      await loc.pressSequentially(expected, { delay: 20 });
      actual = await loc.inputValue().catch(() => "");
    }
    if (options?.commit ?? true) {
      await commitLocatorInput(loc);
      await loc.press("Tab").catch(() => page.keyboard.press("Tab"));
    }
    actual = await loc.inputValue().catch(() => "");
    if (valuesEquivalent(expected, actual)) {
      return;
    }
    await page.waitForTimeout(FIELD_VERIFY_POLL_MS);
  }

  const finalValue = await loc.inputValue().catch(() => "");
  if (valuesEquivalent(expected, finalValue)) {
    return;
  }
  throw new RpaError(
    "CARGO_WEIGHT_MISMATCH",
    `货物字段填写未生效（expected=${expected} actual=${finalValue}）`,
    { retryable: true },
  );
}

export type CargoEditorFieldValues = {
  pallet: string | null;
  weight: string | null;
  length: string | null;
  width: string | null;
  height: string | null;
  weightFieldHint: string | null;
};

/** editor 仍打开时读取各 input value */
export async function readCargoEditorFieldValues(
  page: Page,
  widgetSelector: string,
): Promise<CargoEditorFieldValues> {
  return page.evaluate((sel) => {
    const widget = document.querySelector(sel);
    if (!widget) {
      return {
        pallet: null,
        weight: null,
        length: null,
        width: null,
        height: null,
        weightFieldHint: null,
      };
    }

    const result: CargoEditorFieldValues = {
      pallet: null,
      weight: null,
      length: null,
      width: null,
      height: null,
      weightFieldHint: null,
    };

    const inputs = widget.querySelectorAll<HTMLInputElement>("input");
    for (const inp of inputs) {
      const visible = inp.offsetParent !== null;
      if (!visible) {
        continue;
      }
      const ph = inp.placeholder ?? "";
      const val = inp.value?.trim() ?? "";
      if (!val) {
        continue;
      }
      if (/托盘|pallet/i.test(ph)) {
        result.pallet = val;
      } else if (/weight|重量|lbs|kg|公斤|磅/i.test(ph)) {
        result.weight = val;
        result.weightFieldHint = ph || inp.getAttribute("aria-label") || "";
      } else if (/length|长度/i.test(ph)) {
        result.length = val;
      } else if (/width|宽度/i.test(ph)) {
        result.width = val;
      } else if (/height|高度/i.test(ph)) {
        result.height = val;
      }
    }
    return result;
  }, widgetSelector);
}

export async function waitForCargoEditorFieldValues(
  page: Page,
  widgetSelector: string,
  expected: Record<string, string>,
): Promise<CargoEditorFieldValues> {
  const deadline = Date.now() + FIELD_VERIFY_MS;
  while (Date.now() < deadline) {
    const current = await readCargoEditorFieldValues(page, widgetSelector);
    const ok = Object.entries(expected).every(([key, val]) => {
      const actual = current[key as keyof CargoEditorFieldValues];
      return actual === val;
    });
    if (ok) {
      return current;
    }
    await page.waitForTimeout(FIELD_VERIFY_POLL_MS);
  }
  const failed = await readCargoEditorFieldValues(page, widgetSelector);
  throw new RpaError(
    "CARGO_WEIGHT_MISMATCH",
    `editor 内字段未对齐（expected=${JSON.stringify(expected)} actual=${JSON.stringify(failed)}）`,
    { retryable: true },
  );
}
