import { RpaError } from "@/modules/rpa/errors";
import {
  clickFirstVisibleFromSpecs,
  getSelectorSpecs,
} from "@/lib/rpa/selector-specs";
import type { IRPAContext } from "@/workers/rpa/kernel/context-validator";
import {
  assertRPAContext,
} from "@/workers/rpa/kernel/context-validator";
import {
  FILL_CARGO_FIELD,
  PROBE_CARGO_UI,
  WAIT_CARGO_INTERACTIVE,
} from "@/workers/rpa/kernel/browser-scripts";
import { buildCargoProbe } from "@/workers/rpa/kernel/normalize";

const CARGO_FIELD_RE =
  /托盘|pallet|长度|length|宽度|width|高度|height|重量|weight/i;
const CARGO_READY_RE = /托盘|pallet|重量|weight/i;
const CARGO_INTERACTIVE_WAIT_MS = 12_000;

export type AddressPhase = "idle" | "pickup" | "delivery" | "committed";

export type CargoUiPhase =
  | "interactive"
  | "hydrating"
  | "no_cargo_inputs"
  | "widget_missing";

export type CargoFieldSnapshot = {
  placeholder: string;
  ariaLabel: string | null;
};

export type CargoBlockedField = CargoFieldSnapshot & {
  blockers: string[];
};

export type CargoStateEvidence = {
  phase: CargoUiPhase;
  mountedCargoFields: CargoFieldSnapshot[];
  interactiveCargoFields: CargoFieldSnapshot[];
  blockedCargoFields: CargoBlockedField[];
};

export type CargoStateProbe = {
  ready: boolean;
  evidence: CargoStateEvidence;
};

const WIDGET_MISSING_EVIDENCE: CargoStateEvidence = {
  phase: "widget_missing",
  mountedCargoFields: [],
  interactiveCargoFields: [],
  blockedCargoFields: [],
};

export function isCargoStateReady(evidence: CargoStateEvidence): boolean {
  return evidence.interactiveCargoFields.some((field) =>
    CARGO_READY_RE.test(`${field.placeholder} ${field.ariaLabel ?? ""}`),
  );
}

/** 地址 / 货物 UI 阶段（DOM 填表专用；报价与 StateEngine 解耦） */
export class StateEngine {
  addressPhase: AddressPhase = "idle";
  /** 已确认 MotherShip 候选时与 RPA_BLIND_ADDRESS_FLOW 等效 */
  blindAddressFlow = false;
  axelPlaceLocations: Partial<
    Record<"pickup" | "delivery", Record<string, unknown>>
  > = {};
  cargoSectionTriggered = false;
  lastCargoProbe: CargoStateProbe | null = null;

  constructor(private readonly ctx: IRPAContext) {
    assertRPAContext(ctx, "StateEngine.constructor", {
      allowMissingState: true,
    });
  }

  markAddressSide(side: "pickup" | "delivery"): void {
    this.addressPhase = side;
  }

  markAddressCommitted(): void {
    this.addressPhase = "committed";
  }

  async settleAddressEditing(): Promise<void> {
    const widget = this.ctx.selectors.widget(this.ctx.page);
    const count = await widget.count().catch(() => 0);
    if (count === 0) {
      throw new RpaError(
        "STRUCT_CHANGE",
        "RPA_SELECTOR_QUOTE_WIDGET 未匹配节点，无法 settle",
        { retryable: false },
      );
    }
    await widget
      .click({ position: { x: 12, y: 12 }, timeout: 4_000 })
      .catch(() => undefined);
    await this.ctx.page.waitForTimeout(200);
    this.markAddressCommitted();
  }

  async triggerCargoSection(): Promise<void> {
    for (const label of ["货运详情", "Freight details", "Shipment details"]) {
      const header = this.ctx.page
        .locator("div")
        .filter({ hasText: label })
        .nth(4);
      if (await header.isVisible().catch(() => false)) {
        await header.click({ timeout: 8_000 });
        this.cargoSectionTriggered = true;
        await this.ctx.page.waitForTimeout(400);
        return;
      }
    }
    const widget = this.ctx.selectors.widget(this.ctx.page);
    for (const label of ["货运详情", "Freight details", "Shipment details"]) {
      const loc = widget.getByText(label, { exact: false }).first();
      if (await loc.isVisible().catch(() => false)) {
        await loc.click({ timeout: 8_000 });
        this.cargoSectionTriggered = true;
        await this.ctx.page.waitForTimeout(400);
        return;
      }
    }
    for (const label of [
      "What are you shipping",
      "What are you shipping?",
      "你在运送什么",
    ]) {
      const loc = widget.getByText(label, { exact: false }).first();
      if (await loc.isVisible().catch(() => false)) {
        await loc.click({ timeout: 8_000 });
        this.cargoSectionTriggered = true;
        await this.ctx.page.waitForTimeout(500);
        return;
      }
    }
    const summary = widget.locator("text=/\\d+\\s*Pallet/i").first();
    if (await summary.isVisible().catch(() => false)) {
      await summary.click({ timeout: 8_000 });
      this.cargoSectionTriggered = true;
      await this.ctx.page.waitForTimeout(500);
      return;
    }
    const clicked = await clickFirstVisibleFromSpecs(
      this.ctx.page,
      getSelectorSpecs("RPA_SELECTOR_CARGO_SECTION"),
    );
    if (clicked) {
      this.cargoSectionTriggered = true;
      await this.ctx.page.waitForTimeout(500);
    }
  }

  async probeCargo(): Promise<CargoStateProbe> {
    const widget = this.ctx.selectors.widget(this.ctx.page);
    const count = await widget.count().catch(() => 0);
    if (count === 0) {
      const probe = { ready: false, evidence: WIDGET_MISSING_EVIDENCE };
      this.lastCargoProbe = probe;
      return probe;
    }

    const raw = await this.ctx.exec.evaluateOnLocator<CargoStateEvidence>(
      widget,
      PROBE_CARGO_UI,
      { cargoFieldReSource: CARGO_FIELD_RE.source },
    );
    const probe = buildCargoProbe(raw, isCargoStateReady);
    this.lastCargoProbe = probe;
    return probe;
  }

  async waitCargoInteractive(): Promise<void> {
    const widgetSelector = this.ctx.selectors.resolve(
      "RPA_SELECTOR_QUOTE_WIDGET",
    );
    try {
      await this.ctx.exec.waitForBrowserFunction(
        WAIT_CARGO_INTERACTIVE,
        {
          selector: widgetSelector,
          cargoReadyReSource: CARGO_READY_RE.source,
        },
        CARGO_INTERACTIVE_WAIT_MS,
      );
    } catch {
      const probe = await this.probeCargo();
      throw new RpaError(
        "STRUCT_CHANGE",
        `cargo hydration 未完成：${JSON.stringify(probe.evidence)}`,
        { retryable: true },
      );
    }
  }

  async assertCargoReady(): Promise<CargoStateProbe> {
    await this.waitCargoInteractive();
    const probe = await this.probeCargo();
    if (!probe.ready) {
      throw new RpaError(
        "STRUCT_CHANGE",
        `cargo interactive 未就绪：${JSON.stringify(probe.evidence)}`,
        { retryable: true },
      );
    }
    return probe;
  }

  async fillCargoField(fieldReSource: string, value: string): Promise<boolean> {
    const widget = this.ctx.selectors.widget(this.ctx.page);
    const count = await widget.count().catch(() => 0);
    if (count === 0) {
      return false;
    }
    const raw = await this.ctx.exec.evaluateOnLocator<boolean>(
      widget,
      FILL_CARGO_FIELD,
      {
        fieldRe: fieldReSource,
        val: value,
      },
    );
    return raw === true;
  }
}
