import type { Locator, Page } from "playwright";
import { isRpaHumanSubmitStrict, isRpaAddressRushMode } from "@/lib/rpa/env";
import { pageLocator } from "@/lib/rpa/locator";
import { RpaError } from "@/modules/rpa/errors";
import { toAxelWholeInches } from "@/modules/quote/unit-converter";
import {
  expandCargoEditor,
  waitForCargoEditorOpen,
  waitForCargoUiPresent,
} from "@/workers/rpa/cargo-editor";
import {
  attemptCargoCommit,
  probeCargoCommitState,
} from "@/workers/rpa/cargo-lock";
import {
  assertWidgetCargoSummary,
  clickAddFreightItem,
  removeAllFreightRows,
  widgetHasFreightSummary,
} from "@/workers/rpa/cargo-reset";
import {
  buildDimDisplayForUnit,
  inferDimUnitFromFieldHint,
} from "@/workers/rpa/cargo-dimensions";
import {
  buildWeightDisplayForUnit,
  inferWeightUnitFromFieldHint,
  type MotherShipWeightDisplay,
} from "@/workers/rpa/cargo-weight";
import type { ExpectedCargoCommit } from "@/workers/rpa/cargo-lock";

const POLL_MS = 200;
const FIELD_WAIT_MS = 20_000;

export type CargoDrawerFieldType =
  | "pallet"
  | "weight"
  | "length"
  | "width"
  | "height";

/** placeholder / aria-label 匹配（中英多变体） */
const FIELD_HINT_PATTERNS: Record<CargoDrawerFieldType, RegExp> = {
  pallet: /pallet|托盘|how many|件数|数量/i,
  weight: /weight|重量|lbs?|磅|kg|公斤|千克/i,
  length: /length|长度|dimension/i,
  width: /width|宽度/i,
  height: /height|高度|高\s*$/i,
};

export type FillCargoInDrawerInput = {
  palletCount: number;
  weightLbPerPallet: number;
  dimsIn?: { l: number; w: number; h: number };
  blindFlow?: boolean;
};

export type FillCargoInDrawerResult = {
  weightDisplay: MotherShipWeightDisplay;
  expected: ExpectedCargoCommit;
  /** landing Freight details 弹层已视觉填完 */
  landingPopoverFilled?: boolean;
};

function normalizeAxelDimsIn(
  dimsIn?: { l: number; w: number; h: number },
): { l: number; w: number; h: number } {
  return dimsIn
    ? {
        l: toAxelWholeInches(dimsIn.l),
        w: toAxelWholeInches(dimsIn.w),
        h: toAxelWholeInches(dimsIn.h),
      }
    : { l: 48, w: 40, h: 48 };
}

export function classifyCargoFieldHint(hint: string): CargoDrawerFieldType | null {
  const text = hint.trim();
  if (!text) {
    return null;
  }
  if (FIELD_HINT_PATTERNS.pallet.test(text)) {
    return "pallet";
  }
  if (FIELD_HINT_PATTERNS.weight.test(text)) {
    return "weight";
  }
  if (FIELD_HINT_PATTERNS.length.test(text)) {
    return "length";
  }
  if (FIELD_HINT_PATTERNS.width.test(text)) {
    return "width";
  }
  if (FIELD_HINT_PATTERNS.height.test(text)) {
    return "height";
  }
  return null;
}

async function fillDimensionField(
  page: Page,
  field: Locator,
  inches: number,
  label: string,
): Promise<void> {
  const hint = await readFieldHint(field);
  const unit = inferDimUnitFromFieldHint(hint);
  const displayValue = buildDimDisplayForUnit(inches, unit);
  console.log(
    `[rpa] fillCargoInDrawer: ${label} hint="${hint}" unit=${unit} fill=${displayValue} (internalIn=${inches})`,
  );
  await fillFieldWithTab(page, field, displayValue, label);
}

function valuesMatch(expected: string, actual: string): boolean {
  if (expected === actual) {
    return true;
  }
  const a = Number(expected);
  const b = Number(actual);
  return Number.isFinite(a) && Number.isFinite(b) && a === b;
}

async function cargoFillScopes(
  page: Page,
  widgetSelector: string,
): Promise<Locator[]> {
  const scopes: Locator[] = [pageLocator(page, widgetSelector)];
  const dialog = page
    .locator('[role="dialog"], [aria-modal="true"]')
    .filter({ has: page.locator("input") })
    .last();
  if (await dialog.isVisible().catch(() => false)) {
    scopes.push(dialog);
  }
  scopes.push(page.locator("body"));
  return scopes;
}

/** 在 drawer / widget 内按 hint 动态定位输入框 */
export async function findCargoFieldLocator(
  page: Page,
  widgetSelector: string,
  fieldType: CargoDrawerFieldType,
): Promise<Locator | null> {
  const pattern = FIELD_HINT_PATTERNS[fieldType];
  for (const scope of await cargoFillScopes(page, widgetSelector)) {
    const inputs = scope.locator("input");
    const count = await inputs.count().catch(() => 0);
    for (let i = 0; i < count; i += 1) {
      const loc = inputs.nth(i);
      const visible = await loc.isVisible().catch(() => false);
      const editable = visible && (await loc.isEditable().catch(() => false));
      if (!editable) {
        continue;
      }
      const placeholder = (await loc.getAttribute("placeholder")) ?? "";
      const ariaLabel = (await loc.getAttribute("aria-label")) ?? "";
      const hint = `${placeholder} ${ariaLabel}`;
      if (pattern.test(hint)) {
        return loc;
      }
    }
  }
  return null;
}

async function waitForCargoFieldLocator(
  page: Page,
  widgetSelector: string,
  fieldType: CargoDrawerFieldType,
  waitMs = FIELD_WAIT_MS,
): Promise<Locator | null> {
  const deadline = Date.now() + waitMs;
  while (Date.now() < deadline) {
    const loc = await findCargoFieldLocator(page, widgetSelector, fieldType);
    if (loc) {
      return loc;
    }
    await page.waitForTimeout(POLL_MS);
  }
  return null;
}

async function readFieldHint(loc: Locator): Promise<string> {
  const placeholder = (await loc.getAttribute("placeholder")) ?? "";
  const ariaLabel = (await loc.getAttribute("aria-label")) ?? "";
  return `${placeholder} ${ariaLabel}`.trim();
}

/** 填值 + Tab 提交，校验 input value */
async function fillFieldWithTab(
  page: Page,
  loc: Locator,
  value: string,
  label: string,
): Promise<boolean> {
  await loc.scrollIntoViewIfNeeded().catch(() => undefined);
  await loc.click({ timeout: 5_000 }).catch(() => undefined);
  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(value, { timeout: 8_000 });
  let actual = await loc.inputValue().catch(() => "");
  if (!valuesMatch(value, actual)) {
    await loc.pressSequentially(value, { delay: 25 });
    actual = await loc.inputValue().catch(() => "");
  }
  await loc.evaluate((el) => {
    if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
      el.dispatchEvent(new Event("input", { bubbles: true }));
      el.dispatchEvent(new Event("change", { bubbles: true }));
    }
  });
  await loc.press("Tab").catch(() => page.keyboard.press("Tab"));
  await page.waitForTimeout(POLL_MS);
  actual = await loc.inputValue().catch(() => "");
  const ok = valuesMatch(value, actual);
  console.log(
    `[rpa] fillCargoInDrawer: ${label} expected=${value} actual=${actual} ok=${ok}`,
  );
  return ok;
}

async function logDiscoveredFields(
  page: Page,
  widgetSelector: string,
): Promise<void> {
  const fields = await page.evaluate((widgetSel) => {
    const roots: Element[] = [];
    const widget = document.querySelector(widgetSel);
    if (widget) {
      roots.push(widget);
    }
    document
      .querySelectorAll('[role="dialog"], [aria-modal="true"]')
      .forEach((el) => {
        if ((el as HTMLElement).offsetParent !== null) {
          roots.push(el);
        }
      });
    const rows: Array<Record<string, string | boolean>> = [];
    for (const root of roots) {
      root.querySelectorAll<HTMLInputElement>("input").forEach((inp) => {
        rows.push({
          placeholder: inp.placeholder ?? "",
          ariaLabel: inp.getAttribute("aria-label") ?? "",
          value: inp.value,
          visible: inp.offsetParent !== null,
          readOnly: inp.readOnly,
        });
      });
    }
    return rows;
  }, widgetSelector);
  console.log(
    `[rpa] fillCargoInDrawer: 当前可见 input 数=${fields.filter((f) => f.visible).length}`,
    JSON.stringify(fields.filter((f) => f.visible)),
  );
}

/** MotherShip 首页横向 widget：点 Freight details → 弹出 Per pallet 表单（对齐 5519e0ea 真人录证） */
async function fillLandingFreightPopover(
  page: Page,
  widgetSelector: string,
  input: FillCargoInDrawerInput,
): Promise<FillCargoInDrawerResult | null> {
  const widget = pageLocator(page, widgetSelector);
  const hasFreightChip = await widget
    .getByText("Freight details", { exact: true })
    .isVisible()
    .catch(() => false);
  if (!hasFreightChip) {
    return null;
  }

  console.log("[rpa] fillCargo: landing Freight details 弹层");
  await waitForCargoUiPresent(page, widgetSelector);
  await expandCargoEditor(page, widgetSelector);
  await waitForCargoEditorOpen(page, widgetSelector);
  await logDiscoveredFields(page, widgetSelector);

  const dims = normalizeAxelDimsIn(input.dimsIn);
  const palletField = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "pallet",
    6_000,
  );
  if (palletField) {
    await fillFieldWithTab(
      page,
      palletField,
      String(input.palletCount),
      "landing-pallet",
    );
  }

  const lengthField = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "length",
    3_000,
  );
  if (lengthField) {
    await fillDimensionField(page, lengthField, dims.l, "landing-length");
  }
  const widthField = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "width",
    3_000,
  );
  if (widthField) {
    await fillDimensionField(page, widthField, dims.w, "landing-width");
  }
  const heightField = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "height",
    3_000,
  );
  if (heightField) {
    await fillDimensionField(page, heightField, dims.h, "landing-height");
  }

  const weightField = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "weight",
    6_000,
  );
  if (!weightField) {
    await logDiscoveredFields(page, widgetSelector);
    throw new RpaError(
      "STRUCT_CHANGE",
      "Freight details 已展开，但未找到重量输入框",
      { retryable: true },
    );
  }
  const weightHint = await readFieldHint(weightField);
  const weightDisplay = buildWeightDisplayForUnit(
    input.weightLbPerPallet,
    inferWeightUnitFromFieldHint(weightHint),
  );
  await fillFieldWithTab(
    page,
    weightField,
    weightDisplay.displayValue,
    "landing-weight",
  );

  const expected: ExpectedCargoCommit = {
    palletCount: input.palletCount,
    weight: {
      ...weightDisplay,
      palletCount: input.palletCount,
    },
  };

  console.log(
    `[rpa] fillCargo: landing 弹层已填 pallet=${input.palletCount} weight=${weightDisplay.displayValue}`,
  );
  if (isRpaHumanSubmitStrict()) {
    await page.waitForTimeout(300);
  }
  return { weightDisplay, expected };
}

/**
 * 货物 drawer 完整填写：Add Item → 动态字段定位 → 填托数/重量/尺寸 → 摘要断言
 * 不使用 RPA_SELECTOR_WEIGHT/LENGTH/WIDTH/HEIGHT 静态选择器。
 */
export async function fillCargoInDrawer(
  page: Page,
  widgetSelector: string,
  input: FillCargoInDrawerInput,
): Promise<FillCargoInDrawerResult> {
  console.log(
    `[rpa] fillCargoInDrawer: 开始 pallet=${input.palletCount} weightLb=${input.weightLbPerPallet}`,
  );

  const landing = await fillLandingFreightPopover(page, widgetSelector, input);
  if (landing) {
    if (isRpaAddressRushMode() && !isRpaHumanSubmitStrict()) {
      await attemptCargoCommit(page, widgetSelector, input.blindFlow === true);
    }
    console.log("[rpa] fillCargo: landing 弹层已填并 commit");
    return { ...landing, landingPopoverFilled: true };
  }

  const probeBefore = await probeCargoCommitState(page, widgetSelector);
  if (widgetHasFreightSummary(probeBefore.widgetTextPreview)) {
    await removeAllFreightRows(page, widgetSelector);
  }
  await clickAddFreightItem(page, widgetSelector);
  await waitForCargoUiPresent(page, widgetSelector);
  await waitForCargoEditorOpen(page, widgetSelector);

  await logDiscoveredFields(page, widgetSelector);

  const dims = normalizeAxelDimsIn(input.dimsIn);

  const palletLoc = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "pallet",
    12_000,
  );
  if (!palletLoc) {
    console.warn(
      "[rpa] fillCargoInDrawer: 未找到托数字段（可能页面无托数输入），继续",
    );
  } else {
    const palletOk = await fillFieldWithTab(
      page,
      palletLoc,
      String(input.palletCount),
      "pallet",
    );
    if (!palletOk) {
      console.warn("[rpa] fillCargoInDrawer: 托数填写未确认生效，重试一次");
      await fillFieldWithTab(
        page,
        palletLoc,
        String(input.palletCount),
        "pallet-retry",
      );
    }
    await page.waitForTimeout(POLL_MS);
    await logDiscoveredFields(page, widgetSelector);
  }

  const weightLoc = await waitForCargoFieldLocator(
    page,
    widgetSelector,
    "weight",
  );
  if (!weightLoc) {
    await logDiscoveredFields(page, widgetSelector);
    throw new RpaError(
      "STRUCT_CHANGE",
      "货物 drawer 内未找到任何重量输入框（已扫描 placeholder/aria-label）",
      { retryable: true },
    );
  }

  const weightHint = await readFieldHint(weightLoc);
  const weightDisplay = buildWeightDisplayForUnit(
    input.weightLbPerPallet,
    inferWeightUnitFromFieldHint(weightHint),
  );
  console.log(
    `[rpa] fillCargoInDrawer: 重量字段 hint="${weightHint}" unit=${weightDisplay.unit} fill=${weightDisplay.displayValue}`,
  );

  const weightOk = await fillFieldWithTab(
    page,
    weightLoc,
    weightDisplay.displayValue,
    "weight",
  );
  if (!weightOk) {
    await fillFieldWithTab(
      page,
      weightLoc,
      weightDisplay.displayValue,
      "weight-retry",
    );
  }

  if (input.dimsIn) {
    const dimFields: Array<[CargoDrawerFieldType, number]> = [
      ["length", dims.l],
      ["width", dims.w],
      ["height", dims.h],
    ];
    for (const [fieldType, dimValue] of dimFields) {
      const dimLoc = await waitForCargoFieldLocator(
        page,
        widgetSelector,
        fieldType,
        8_000,
      );
      if (!dimLoc) {
        console.warn(
          `[rpa] fillCargoInDrawer: 未找到 ${fieldType} 字段，跳过（页面可能不需要尺寸）`,
        );
        continue;
      }
      await fillDimensionField(page, dimLoc, dimValue, fieldType);
    }
  }

  const expected: ExpectedCargoCommit = {
    palletCount: input.palletCount,
    weight: {
      ...weightDisplay,
      palletCount: input.palletCount,
    },
  };

  await attemptCargoCommit(page, widgetSelector, input.blindFlow === true);
  await assertWidgetCargoSummary(page, widgetSelector, expected);

  console.log("[rpa] fillCargoInDrawer: 摘要断言通过");

  if (process.env.RPA_CARGO_DRAWER_PAUSE === "true") {
    console.log(
      "[rpa] fillCargoInDrawer: RPA_CARGO_DRAWER_PAUSE=true → page.pause()",
    );
    await page.pause();
  }

  return { weightDisplay, expected };
}
