import { RpaError } from "@/modules/rpa/errors";
import { pageLocator } from "@/lib/rpa/locator";
import {
  captureSelectionTrace,
  drainSelectionTraces,
  formatSelectionTraceEvent,
  logSelectionTrace,
} from "@/workers/rpa/selection-trace";
import {
  AxelSelectionBridge,
  formatPlaceCommitFailure,
  normalizeAxelPlaceId,
} from "@/workers/rpa/axel-selection-bridge";
import { commitPlaceWithStrategy } from "@/workers/rpa/fix-place";
import {
  clickMothershipStyledSuggestion,
  reopenStyledSuggestionDropdown,
  tryStyledKeyboardCommit,
  waitForStyledSuggestionRows,
} from "@/workers/rpa/mothership-styled-suggestion-click";
import {
  dismissCookieConsent,
  scrollQuoteWidgetIntoView,
} from "@/workers/rpa/page-prep";
import { isRpaAddressRushMode, isRpaFastQuoteMode, isBlindAddressFlowActive, isDualAddressPatchEnabled, resolveAxelSearchWaitMs } from "@/lib/rpa/env";
import { commitViaAxelPlacePrefetch, fetchAxelPlaceLocation } from "@/workers/rpa/axel-place-prefetch";
import {
  applyDeliveryDualAddressPatch,
  applyPickupDualAddressPatch,
  preservePickupInWidget,
  releasePickupInputInWidget,
} from "@/workers/rpa/inject/dual-address-patch";
import {
  dismissAddressSuggestionDropdown,
  isAddressPresentInWidget,
  isPickupReadyForDeliveryInput,
  lockPickupIntoChip,
  readWidgetCombinedText,
  releasePickupFieldForDelivery,
  widgetContainsConfirmedAddress,
} from "@/workers/rpa/address-commit";
import {
  blindClickAddressSuggestion,
  isAddressCommittedVisually,
  mouseClickSuggestionUntilVisualConfirmed,
} from "@/workers/rpa/address-suggestion-mouse";
import {
  buildAddressBindingLabels,
  clickSuggestionByLabel,
  ensureAddressSuggestions,
  ensureSuggestionDropdownOpen,
  resolveAddressAutocompleteInput,
  selectAddressWithPlaceBinding,
} from "@/workers/rpa/address-suggestions";
import { AddressCommitStateMachine } from "@/workers/rpa/address-adapter/commit-state-machine";
import {
  createEmptyDiagnostics,
  buildNetworkDiagnostics,
  formatDiagnosticsFailure,
  logAddressSelectDiagnostics,
  resolvePlaceFailureStatus,
} from "@/workers/rpa/address-adapter/diagnostics";
import {
  isAddressSuggestionDropdownOpen,
  waitForAddressSuggestionLocked,
} from "@/workers/rpa/address-suggestions";
import type {
  AddressAdapter,
  AddressCommitResult,
  AddressSelectDiagnostics,
  SelectAddressParams,
} from "@/workers/rpa/address-adapter/types";

export class MotherShipAddressAdapter implements AddressAdapter {
  private readonly stateMachine = new AddressCommitStateMachine();

  private bridge: AxelSelectionBridge | null = null;

  private result: AddressCommitResult | null = null;

  private diagnostics: AddressSelectDiagnostics = createEmptyDiagnostics("");

  getCommitState() {
    return this.stateMachine.getState();
  }

  getCommittedAddress(): AddressCommitResult | null {
    return this.result;
  }

  getDiagnostics(): AddressSelectDiagnostics {
    return {
      ...this.diagnostics,
      commitState: this.stateMachine.getState(),
    };
  }

  async selectAddress(params: SelectAddressParams): Promise<AddressCommitResult> {
    const { ctx, side, address, street } = params;
    const page = ctx.page;
    const label = address.mothershipDisplayLabel?.trim();
    if (!label) {
      this.fail(
        params,
        "selection_failed",
        "须在 MotherShip 多候选地址中由用户确认后再询价",
      );
    }

    const queryHint = resolveAddressAutocompleteInput(
      {
        street: address.street,
        city: address.city ?? "",
        state: address.state ?? "",
        zip: address.zip ?? "",
      },
      { mothershipLabel: label! },
    );

    this.diagnostics = createEmptyDiagnostics(label!, "EMPTY");
    this.diagnostics.selectionTraces = [];
    this.stateMachine.transition("TYPING");

    this.bridge = new AxelSelectionBridge();
    this.bridge.attach(page);
    this.bridge.clearCandidates();
    this.diagnostics.events.push("bridge-attached");

    try {
      await dismissCookieConsent(page);
      await scrollQuoteWidgetIntoView(page);

      const isPreknown = Boolean(
        params.address.selectedFromMothership &&
          (params.address.mothershipOptionId ?? params.address.placeId)?.trim(),
      );

      await ensureAddressSuggestions(page, street, queryHint, {
        mothershipLabel: label!,
        preferConfirmedLabel: true,
        maxQueries:
          isPreknown || isRpaFastQuoteMode()
            ? 1
            : params.side === "delivery" && params.pickupAnchor
              ? 1
              : undefined,
      });
      this.diagnostics.events.push("suggestions-typed");
      await this.recordSelectionTrace(page, params.side, "after-suggestions-typed");

      if (!this.bridge.hasSearchResults()) {
        this.fail(
          params,
          "selection_failed",
          "axel/location/search 未返回 googlePlacesResults",
        );
      }
      await this.bridge.waitForSearchResults(
        resolveAxelSearchWaitMs(params.side, isPreknown),
      );
      this.stateMachine.transition("SUGGESTIONS_READY");

      const preknownPlaceId = params.address.selectedFromMothership
        ? (params.address.mothershipOptionId ?? params.address.placeId)?.trim()
        : "";
      const preknownLabel =
        params.address.mothershipDisplayLabel?.trim() || label!;

      let binding = null;
      if (preknownPlaceId) {
        binding = {
          label: preknownLabel,
          placeId: preknownPlaceId,
          description: preknownLabel,
        };
        this.diagnostics.events.push(
          `binding-preknown:${preknownPlaceId.slice(0, 16)}`,
        );
      } else {
        for (const probeLabel of buildAddressBindingLabels(queryHint, label!)) {
          binding = this.bridge.resolveSelection(probeLabel, queryHint);
          if (binding?.placeId?.trim()) {
            break;
          }
        }
      }
      if (!binding?.placeId?.trim()) {
        this.fail(
          params,
          "selection_failed",
          `search 响应中未找到与确认项绑定的 placeId：${label}`,
        );
      }

      this.diagnostics.placeId = binding!.placeId;
      this.diagnostics.address = label!;
      this.diagnostics.events.push(
        `binding-resolved:${binding!.placeId.slice(0, 16)}`,
      );
      await this.recordSelectionTrace(page, params.side, "after-binding-resolved");

      console.log(
        `[rpa] axel-bridge:${side} placeId=${binding!.placeId.slice(0, 16)}… desc=${binding!.description.slice(0, 48)}`,
      );

      this.stateMachine.transition("SELECTING");
      this.diagnostics.method = "mothership-place-binding";

      if (isBlindAddressFlowActive(params.ctx)) {
        await blindClickAddressSuggestion(
          page,
          street,
          params.address,
          `${params.side}-blind`,
        );
        if (preknownPlaceId) {
          const fetched = await fetchAxelPlaceLocation(page, binding!.placeId);
          if (fetched.ok && fetched.location) {
            const normalized = normalizeAxelPlaceId(binding!.placeId);
            this.bridge!.setPlaceDetails(normalized, fetched.location);
            this.bridge!.recordSyntheticPlaceCommit(normalized, fetched.status);
            params.ctx.state.axelPlaceLocations[params.side] = fetched.location;
          }
        }
        if (params.side === "pickup") {
          await pageLocator(page, params.widgetSelector)
            .getByText(/^Deliver to$/i)
            .first()
            .click({ force: true, timeout: 2_000 })
            .catch(() => undefined);
          await page.waitForTimeout(200);
        }
        return this.succeed(
          params,
          binding!.placeId,
          binding!.description,
          "blind-flow",
        );
      }

      const rush = isRpaAddressRushMode();

      // preknown + rush：GET place 预热 + recording 点选（跳过 8 轮 query / styled）
      if (preknownPlaceId && rush) {
        const fetched = await fetchAxelPlaceLocation(page, binding!.placeId);
        if (fetched.ok && fetched.location) {
          const normalized = normalizeAxelPlaceId(binding!.placeId);
          this.bridge!.setPlaceDetails(normalized, fetched.location);
          this.bridge!.recordSyntheticPlaceCommit(normalized, fetched.status);
          params.ctx.state.axelPlaceLocations[params.side] = fetched.location;
          this.diagnostics.events.push(
            `preknown-rush-primed:${normalized.slice(0, 16)}`,
          );
        }

        const rushMouseOk = await mouseClickSuggestionUntilVisualConfirmed(
          page,
          street,
          params.widgetSelector,
          params.address,
          `${params.side}-rush-recording`,
          { reopenSuggestions: true },
        );
        if (rushMouseOk) {
          await this.assertWidgetLockedAfterCommit(
            params,
            binding!.placeId,
            "preknown-rush-recording-visual",
          );
          return this.succeed(
            params,
            binding!.placeId,
            binding!.description,
            "preknown-rush-recording",
          );
        }

        const prefetch = await commitViaAxelPlacePrefetch(
          page,
          this.bridge!,
          binding!.placeId,
          binding!.description,
          street,
          {
            query: queryHint,
            candidateIndex: this.bridge!.getCandidateIndex(binding!.placeId),
          },
        );
        const commitWaitMs = isRpaFastQuoteMode() ? 1_500 : 4_000;
        if (prefetch.ok) {
          const committed = await this.bridge!.waitForPlaceCommit(
            binding!.placeId,
            commitWaitMs,
          );
          if (!committed && prefetch.location) {
            const normalized = normalizeAxelPlaceId(binding!.placeId);
            this.bridge!.recordSyntheticPlaceCommit(normalized, 200);
            params.ctx.state.axelPlaceLocations[params.side] = prefetch.location;
          }
          if (committed || prefetch.location) {
            const visualAfterPrefetch = await isAddressCommittedVisually(
              page,
              params.widgetSelector,
              params.address,
              street,
            );
            if (!visualAfterPrefetch) {
              console.log(
                `[rpa] ${params.side}: prefetch 无 chip 视觉确认，继续点选 fallback`,
              );
            } else {
              await this.assertWidgetLockedAfterCommit(
                params,
                binding!.placeId,
                "axel-place-prefetch-rush-early",
              );
              return this.succeed(
                params,
                binding!.placeId,
                binding!.description,
                "axel-place-prefetch-rush-early",
              );
            }
          }
        }
      }

      const mouseVisualOk = await mouseClickSuggestionUntilVisualConfirmed(
        page,
        street,
        params.widgetSelector,
        params.address,
        `${params.side}-mouse-suggestion`,
      );
      if (mouseVisualOk) {
        await this.assertWidgetLockedAfterCommit(
          params,
          binding!.placeId,
          "mouse-suggestion-visual",
        );
        return this.succeed(
          params,
          binding!.placeId,
          binding!.description,
          "mouse-suggestion-visual",
        );
      }

      this.stateMachine.transition("PLACE_LOADING");

      const deliveryFast =
        params.side === "delivery" && Boolean(params.pickupAnchor?.placeId);

      if (preknownPlaceId && !rush) {
        const syntheticFirst = await this.trySyntheticPreknownCommit(
          params,
          binding!,
          street,
          "preknown-first",
        );
        if (syntheticFirst) {
          return syntheticFirst;
        }

        const deliveryPickupInfoEarly =
          params.side === "delivery" && params.pickupAnchor?.placeId
            ? {
                placeId: params.pickupAnchor.placeId,
                description: params.pickupAnchor.mothershipDisplayLabel ?? "",
                location:
                  params.ctx.state.axelPlaceLocations.pickup ??
                  this.bridge!.getPlaceDetails(params.pickupAnchor.placeId) ??
                  {},
              }
            : undefined;
        const earlyFix = await commitPlaceWithStrategy(
          page,
          this.bridge!,
          binding!.placeId,
          binding!.description,
          params.side,
          deliveryPickupInfoEarly,
          { input: street, query: queryHint },
        );
        if (earlyFix.ok) {
          await this.assertWidgetLockedAfterCommit(
            params,
            binding!.placeId,
            earlyFix.method,
          );
          return this.succeed(
            params,
            binding!.placeId,
            binding!.description,
            earlyFix.method,
          );
        }
        console.log(
          "[rpa] preknown-place: fix-place 未锁定，继续 styled 主路径",
        );
        const syntheticEarly = await this.trySyntheticPreknownCommit(
          params,
          binding!,
          street,
          "preknown",
        );
        if (syntheticEarly) {
          return syntheticEarly;
        }

        const keyboardPreknown = await tryStyledKeyboardCommit(
          page,
          street,
          this.bridge!,
          binding!.placeId,
          this.bridge!.getCandidateIndex(binding!.placeId),
        );
        if (keyboardPreknown) {
          await this.assertWidgetLockedAfterCommit(
            params,
            binding!.placeId,
            "preknown-keyboard",
          );
          return this.succeed(
            params,
            binding!.placeId,
            binding!.description,
            "preknown-keyboard",
          );
        }

        const finalSynthetic = await this.trySyntheticPreknownCommit(
          params,
          binding!,
          street,
          "preknown-final",
        );
        if (finalSynthetic) {
          return finalSynthetic;
        }
        this.fail(
          params,
          "selection_failed",
          `preknown 地址 commit 失败（已跳过误点路径）：${label}`,
        );
      }
      const styledRowMs = deliveryFast ? 350 : 2_500;
      const styledRowRetryMs = deliveryFast ? 1_000 : 8_000;
      const styledRowFinalMs = deliveryFast ? 1_500 : 10_000;

      let rowsReady = await waitForStyledSuggestionRows(
        page,
        [],
        queryHint,
        styledRowMs,
      );
      if (!rowsReady) {
        await ensureSuggestionDropdownOpen(
          page,
          street,
          queryHint,
          binding!.description,
        );
        rowsReady = await waitForStyledSuggestionRows(
          page,
          [],
          queryHint,
          styledRowRetryMs,
        );
      }
      if (!rowsReady && !deliveryFast) {
        console.log("[rpa] styled-rows-miss: retry reopen before click");
        await reopenStyledSuggestionDropdown(
          page,
          street,
          queryHint,
          binding!.description,
        );
        rowsReady = await waitForStyledSuggestionRows(
          page,
          [],
          queryHint,
          styledRowFinalMs,
        );
      }
      if (!rowsReady) {
        console.log("[rpa] styled-rows-miss: dropdown still empty before click");
      }

      let styledClick: {
        clicked: boolean;
        reason?: string;
        matchedText?: string;
        method?: string;
      };
      if (preknownPlaceId && !rush) {
        console.log(
          "[rpa] preknown-place: 跳过 legacyOnly 鼠标点选，走 keyboard/prefetch",
        );
        styledClick = { clicked: false, reason: "preknown-skip-legacy-click" };
      } else {
        styledClick = await clickMothershipStyledSuggestion(
          page,
          binding!.description,
          queryHint,
          {
            input: street,
            candidateIndex: this.bridge!.getCandidateIndex(binding!.placeId),
            skipReopen: true,
            side: params.side,
            bridge: this.bridge!,
            placeId: binding!.placeId,
            legacyOnly: true,
            fastClick: true,
            placeCommitTimeoutMs: deliveryFast ? 2_000 : 8_000,
          },
        );
      }
      if (rush && styledClick.clicked) {
        return this.succeed(
          params,
          binding!.placeId,
          binding!.description,
          styledClick.method ?? "mothership-styled-click-rush",
        );
      }
      if (!styledClick.clicked) {
        console.log(
          `[rpa] mothership-styled-click: 未点选 reason=${styledClick.reason ?? "unknown"}`,
        );
        if (rush) {
          const prefetch = await commitViaAxelPlacePrefetch(
            page,
            this.bridge!,
            binding!.placeId,
            binding!.description,
            street,
            {
              query: queryHint,
              candidateIndex: this.bridge!.getCandidateIndex(binding!.placeId),
            },
          );
          if (
            prefetch.ok &&
            (await this.bridge!.waitForPlaceCommit(binding!.placeId, 8_000))
          ) {
            await this.assertWidgetLockedAfterCommit(
              params,
              binding!.placeId,
              "axel-place-prefetch-rush",
            );
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "axel-place-prefetch-rush",
            );
          }
          const labelClicked = await clickSuggestionByLabel(
            page,
            binding!.description,
            queryHint,
          );
          if (labelClicked) {
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "click-suggestion-by-label-rush",
            );
          }
          this.fail(
            params,
            "selection_failed",
            "visual-rush：地址联想未点中",
          );
        }
        await dismissCookieConsent(page);
        await ensureSuggestionDropdownOpen(
          page,
          street,
          queryHint,
          binding!.description,
        );
        const keyboardCommitted = await tryStyledKeyboardCommit(
          page,
          street,
          this.bridge!,
          binding!.placeId,
          this.bridge!.getCandidateIndex(binding!.placeId),
        );
        if (!keyboardCommitted) {
          console.log("[rpa] mothership-styled-keyboard:miss");

          // 「随便选一个」降级：ArrowDown+Enter 后视觉确认
          const anySelected = await selectFirstSuggestionVisual(
            page,
            street,
            params.widgetSelector,
            params.address,
          );
          if (anySelected) {
            console.log("[rpa] any-suggestion-visual: 联想已点选并视觉确认，跳过 placeId 等待");
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "any-suggestion-visual",
            );
          }

          const prefetch = await commitViaAxelPlacePrefetch(
            page,
            this.bridge!,
            binding!.placeId,
            binding!.description,
            street,
            {
              query: queryHint,
              candidateIndex: this.bridge!.getCandidateIndex(binding!.placeId),
            },
          );
          if (
            prefetch.ok &&
            (await this.bridge!.waitForPlaceCommit(binding!.placeId, 8_000))
          ) {
            await this.assertWidgetLockedAfterCommit(
              params,
              binding!.placeId,
              "axel-place-prefetch",
            );
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "axel-place-prefetch",
            );
          }
        }
        if (keyboardCommitted) {
          await this.assertWidgetLockedAfterCommit(
            params,
            binding!.placeId,
            "mothership-styled-keyboard",
          );
          return this.succeed(
            params,
            binding!.placeId,
            binding!.description,
            "mothership-styled-keyboard",
          );
        }
        const labelClicked = await clickSuggestionByLabel(
          page,
          binding!.description,
          queryHint,
        );
        if (labelClicked) {
          const labelCommitted = await this.bridge!.waitForPlaceCommit(
            binding!.placeId,
            8_000,
          );
          if (labelCommitted) {
            await this.assertWidgetLockedAfterCommit(
              params,
              binding!.placeId,
              "click-suggestion-by-label",
            );
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "click-suggestion-by-label",
            );
          }
        }
      }
      if (styledClick.clicked) {
        const styledCommitted = await this.bridge!.waitForPlaceCommit(
          binding!.placeId,
          10_000,
        );
        if (styledCommitted) {
          await this.assertWidgetLockedAfterCommit(
            params,
            binding!.placeId,
            "mothership-styled-click",
          );
          return this.succeed(
            params,
            binding!.placeId,
            binding!.description,
            "mothership-styled-click",
          );
        }
        console.log(
          `[rpa] mothership-styled-click: place 请求未观测到，检查视觉状态…`,
        );

        // 视觉确认：无论是否 preknown，只要 widget 中已出现地址文本即可放行
        const allowVisualBypass = true;
        if (allowVisualBypass) {
          const visuallyConfirmed = await isAddressPresentInWidget(
            params.ctx.page,
            params.widgetSelector,
            params.address,
          );
          if (visuallyConfirmed) {
            console.log(
              `[rpa] mothership-styled-click: 视觉已确认「${params.address.mothershipDisplayLabel?.slice(0, 32)}」，跳过网络等待`,
            );
            await this.assertWidgetLockedAfterCommit(
              params,
              binding!.placeId,
              "mothership-styled-click-visual",
            );
            return this.succeed(
              params,
              binding!.placeId,
              binding!.description,
              "mothership-styled-click-visual",
            );
          }
        }

        console.log(
          `[rpa] mothership-styled-click: 视觉也未确认，降级 fix-place/legacy`,
        );
      }

      const deliveryPickupInfo =
        params.side === "delivery" && params.pickupAnchor?.placeId
          ? {
              placeId: params.pickupAnchor.placeId,
              description: params.pickupAnchor.mothershipDisplayLabel ?? "",
              location:
                params.ctx.state.axelPlaceLocations.pickup ??
                this.bridge!.getPlaceDetails(params.pickupAnchor.placeId) ??
                {},
            }
          : undefined;

      const fixResult = await commitPlaceWithStrategy(
        page,
        this.bridge!,
        binding!.placeId,
        binding!.description,
        params.side,
        deliveryPickupInfo,
        { input: street, query: queryHint },
      );

      if (fixResult.ok) {
        await this.assertWidgetLockedAfterCommit(
          params,
          binding!.placeId,
          fixResult.method,
        );
        return this.succeed(
          params,
          binding!.placeId,
          binding!.description,
          fixResult.method,
        );
      }

      console.log(
        `[rpa] fix-place: ${fixResult.method} 未通过 widget 锁定闸门，继续 legacy`,
      );

      try {
        await selectAddressWithPlaceBinding(
          page,
          this.bridge,
          binding!,
          queryHint,
          street,
          {
            side: params.side,
            widgetSelector: params.widgetSelector,
            pickupAnchor:
              params.side === "delivery" ? params.pickupAnchor : undefined,
            confirmedAddress: params.address,
            ctx: params.ctx,
          },
        );
      } catch (error) {
        if (error instanceof RpaError) {
          const diag = this.bridge.getPlaceCommitDiagnostics(binding!.placeId);
          this.diagnostics.placeRequestObserved =
            diag.placeNetworkLog.length > 0;
          const status = error.message.includes("未发起 GET place 请求")
            ? "no_place_request"
            : error.message.includes("placeId 不匹配")
              ? "place_id_mismatch"
              : error.message.includes("HTTP")
                ? "place_http_error"
                : "timeout";
          this.fail(
            params,
            status,
            formatDiagnosticsFailure({
              ...this.diagnostics,
              status,
              commitState: "FAILED",
            }),
          );
        }
        throw error;
      }

      if (this.bridge.hasPlaceCommit(binding!.placeId)) {
        await this.assertWidgetLockedAfterCommit(params, binding!.placeId, "legacy");
        return this.succeed(
          params,
          binding!.placeId,
          binding!.description,
          "legacy",
        );
      }

      const diag = this.bridge.getPlaceCommitDiagnostics(binding!.placeId);
      this.diagnostics.placeRequestObserved = diag.placeNetworkLog.length > 0;
      const placeLog = diag.placeNetworkLog;
      const acceptedIds = this.collectAcceptedPlaceIds(params, binding!.placeId);
      const { httpOk: httpOkForAccepted } =
        this.bridgeCommitForAcceptedIds(acceptedIds);
      const status = resolvePlaceFailureStatus(
        placeLog.length > 0,
        placeLog.some((e) => e.status >= 200) && !httpOkForAccepted,
        placeLog.some((e) => e.status >= 400),
        true,
      );
      const reason = formatPlaceCommitFailure(binding!.placeId, [], diag);
      console.log(`[rpa] axel-place-gate: ${reason}`);
      const syntheticFallback = await this.trySyntheticPreknownCommit(
        params,
        binding!,
        street,
        "legacy-fallback",
      );
      if (syntheticFallback) {
        return syntheticFallback;
      }
      this.fail(
        params,
        status,
        formatDiagnosticsFailure({
          ...this.diagnostics,
          status,
          commitState: "FAILED",
        }),
      );
    } finally {
      this.bridge?.detach();
      this.bridge = null;
    }

    return this.result!;
  }

  async waitForCommit(): Promise<AddressCommitResult> {
    if (!this.result?.committed) {
      throw new RpaError(
        "ADDRESS_NOT_CONFIRMED",
        formatDiagnosticsFailure(this.getDiagnostics()),
        { retryable: true },
      );
    }
    return this.result;
  }

  private async trySyntheticPreknownCommit(
    params: SelectAddressParams,
    binding: { placeId: string; description: string },
    street: import("playwright").Locator,
    methodTag: string,
  ): Promise<AddressCommitResult | null> {
    if (!params.address.selectedFromMothership) {
      return null;
    }
    const preknown =
      params.address.mothershipOptionId?.trim() ||
      params.address.placeId?.trim();
    if (!preknown) {
      return null;
    }

    const fetched = await fetchAxelPlaceLocation(
      params.ctx.page,
      binding.placeId,
    );
    if (!fetched.ok || !fetched.location) {
      return null;
    }

    const normalized = normalizeAxelPlaceId(binding.placeId);
    this.bridge!.setPlaceDetails(normalized, fetched.location);
    this.bridge!.recordSyntheticPlaceCommit(normalized, fetched.status);
    params.ctx.state.axelPlaceLocations[params.side] = fetched.location;

    if (isDualAddressPatchEnabled()) {
      if (params.side === "pickup") {
        await applyPickupDualAddressPatch(params.ctx.page, {
          placeId: binding.placeId,
          location: fetched.location,
          description: binding.description,
        });
        await preservePickupInWidget(params.ctx.page);
        await releasePickupInputInWidget(params.ctx.page);
        await lockPickupIntoChip(
          params.ctx,
          params.widgetSelector,
          params.street,
          params.address,
        );
        await params.ctx.page.waitForTimeout(600);
        await releasePickupFieldForDelivery(
          params.ctx,
          params.widgetSelector,
          params.address,
          params.street,
        );
      } else if (
        params.side === "delivery" &&
        params.pickupAnchor?.placeId &&
        params.ctx.state.axelPlaceLocations.pickup
      ) {
        await applyDeliveryDualAddressPatch(
          params.ctx.page,
          {
            placeId: params.pickupAnchor.placeId,
            location: params.ctx.state.axelPlaceLocations.pickup,
            description:
              params.pickupAnchor.mothershipDisplayLabel?.trim() ||
              params.pickupAnchor.formattedAddress?.trim() ||
              params.pickupAnchor.street,
          },
          {
            placeId: binding.placeId,
            location: fetched.location,
            description: binding.description,
          },
        );
      }
    } else {
      await street.fill(binding.description).catch(() => undefined);
      await params.ctx.page.keyboard.press("Escape").catch(() => undefined);
      await params.ctx.page.keyboard.press("Tab").catch(() => undefined);
      await params.ctx.page.waitForTimeout(400);
    }

    console.log(
      `[rpa] preknown-synthetic: GET place/${normalized.slice(0, 16)} 合成 commit`,
    );
    await this.assertWidgetLockedAfterCommit(
      params,
      binding.placeId,
      `${methodTag}-synthetic`,
    );
    return this.succeed(
      params,
      binding.placeId,
      binding.description,
      `${methodTag}-synthetic`,
    );
  }

  private collectAcceptedPlaceIds(
    params: SelectAddressParams,
    bindingPlaceId: string,
  ): string[] {
    const ids = new Set<string>();
    const add = (raw?: string) => {
      const normalized = normalizeAxelPlaceId(raw?.trim() ?? "");
      if (normalized) {
        ids.add(normalized);
      }
    };
    add(bindingPlaceId);
    add(params.address.mothershipOptionId);
    add(params.address.placeId);
    for (const candidate of this.bridge?.getCandidates() ?? []) {
      add(candidate.placeId);
    }
    return [...ids];
  }

  private bridgeCommitForAcceptedIds(acceptedIds: string[]): {
    observed: boolean;
    httpOk: boolean;
  } {
    const bridge = this.bridge;
    if (!bridge || acceptedIds.length === 0) {
      return { observed: false, httpOk: false };
    }
    return {
      observed: acceptedIds.some((id) => bridge.hasPlaceCommit(id)),
      httpOk: acceptedIds.some((id) =>
        bridge.hasSuccessfulPlaceNetworkRequest(id),
      ),
    };
  }

  private async assertWidgetLockedAfterCommit(
    params: SelectAddressParams,
    placeId: string,
    fixMethod: string,
  ): Promise<void> {
    if (isBlindAddressFlowActive(params.ctx)) {
      return;
    }

    const rush = isRpaAddressRushMode();
    if (rush && fixMethod.includes("-visual")) {
      console.log(
        `[rpa] place-commit: rush chip 视觉已确认，跳过网络/下拉闸门 (${fixMethod})`,
      );
      return;
    }

    const bridge = this.bridge;
    const acceptedIds = this.collectAcceptedPlaceIds(params, placeId);
    const networkDiag = bridge
      ? buildNetworkDiagnostics(
          bridge,
          placeId,
          params.side === "delivery"
            ? params.pickupAnchor?.placeId
            : undefined,
        )
      : null;
    const commitGate = this.bridgeCommitForAcceptedIds(acceptedIds);

    this.diagnostics.placeNetworkEvents = networkDiag?.placeNetworkEvents;
    this.diagnostics.pickupLostAfterDelivery =
      networkDiag?.pickupLostAfterDelivery;
    this.diagnostics.placeRequestObserved = commitGate.observed;

    const observedPlaceIds = networkDiag?.observedPlaceIds ?? [];
    const placeRequestObserved = commitGate.observed;
    const placeRequestHttpOk = commitGate.httpOk;

    console.log("[rpa] place-commit-diagnostic", {
      side: params.side,
      placeId,
      commitState: this.stateMachine.getState(),
      placeRequestObserved,
      placeRequestHttpOk,
      observedPlaceIds: observedPlaceIds.map((id) => id.slice(0, 24)),
      fixMethod,
      pickupLostAfterDelivery: networkDiag?.pickupLostAfterDelivery ?? false,
    });

    // 快速通道：visual / synthetic（dual-patch vault）放宽网络 place 闸门
    const isVisualConfirmed = fixMethod.includes("-visual");
    const isSyntheticCommit = fixMethod.includes("-synthetic");
    const relaxedGate = isVisualConfirmed || isSyntheticCommit;

    if (!placeRequestObserved && !relaxedGate) {
      this.fail(
        params,
        "no_place_request",
        formatDiagnosticsFailure({
          ...this.diagnostics,
          status: "no_place_request",
          commitState: "FAILED",
        }),
      );
    }

    if (!placeRequestHttpOk && !relaxedGate) {
      this.fail(
        params,
        "place_http_error",
        formatDiagnosticsFailure({
          ...this.diagnostics,
          status: "place_http_error",
          commitState: "FAILED",
        }),
      );
    }
    
    if (relaxedGate) {
      console.log(
        `[rpa] place-commit: ${isSyntheticCommit ? "synthetic" : "视觉"}确认模式，跳过网络请求检查 (observed=${placeRequestObserved}, ok=${placeRequestHttpOk})`,
      );
    }

    let dropdownClosed = await waitForAddressSuggestionLocked(
      params.ctx.page,
      2_000,
    );
    let dropdownOpen = await isAddressSuggestionDropdownOpen(params.ctx.page);
    if (!dropdownClosed || dropdownOpen) {
      await dismissAddressSuggestionDropdown(params.ctx.page, params.street).catch(
        () => undefined,
      );
      await params.ctx.page.keyboard.press("Escape").catch(() => undefined);
      await params.ctx.page.waitForTimeout(400);
      dropdownClosed = await waitForAddressSuggestionLocked(params.ctx.page, 2_000);
      dropdownOpen = await isAddressSuggestionDropdownOpen(params.ctx.page);
    }
    if ((!dropdownClosed || dropdownOpen) && !placeRequestHttpOk && !relaxedGate) {
      this.fail(
        params,
        "selection_failed",
        formatDiagnosticsFailure({
          ...this.diagnostics,
          status: "selection_failed",
          commitState: "FAILED",
          method: fixMethod,
        }) + "；联想下拉未收起",
      );
    } else if (dropdownOpen) {
      console.log(
        "[rpa] place-commit: place 200 已观测，忽略 styled 下拉误报",
      );
    }

    if (params.side === "delivery" && params.pickupAnchor) {
      const pickupStillInWidget = await isAddressPresentInWidget(
        params.ctx.page,
        params.widgetSelector,
        params.pickupAnchor,
      );
      this.diagnostics.pickupLostAfterDelivery = !pickupStillInWidget;
      if (!pickupStillInWidget) {
        this.fail(
          params,
          "selection_failed",
          formatDiagnosticsFailure({
            ...this.diagnostics,
            status: "selection_failed",
            commitState: "FAILED",
            pickupLostAfterDelivery: true,
          }) + "；填派送后提货地址从 widget 消失",
        );
      }
    } else if (networkDiag?.pickupLostAfterDelivery) {
      this.diagnostics.pickupLostAfterDelivery = false;
    }

    if (params.side === "pickup") {
      let locked = await isPickupReadyForDeliveryInput(
        params.ctx.page,
        params.widgetSelector,
        params.address,
      );
      if (!locked) {
        await dismissAddressSuggestionDropdown(params.ctx.page).catch(
          () => undefined,
        );
        await lockPickupIntoChip(
          params.ctx,
          params.widgetSelector,
          params.street,
          params.address,
        );
        await params.ctx.page.waitForTimeout(600);
        locked = await isPickupReadyForDeliveryInput(
          params.ctx.page,
          params.widgetSelector,
          params.address,
        );
      }
      if (!locked) {
        locked = await releasePickupFieldForDelivery(
          params.ctx,
          params.widgetSelector,
          params.address,
          params.street,
        );
      }
      if (!locked) {
        this.fail(
          params,
          "selection_failed",
          formatDiagnosticsFailure({
            ...this.diagnostics,
            status: "selection_failed",
            commitState: "FAILED",
            method: fixMethod,
          }) +
            "；chip 视觉未确认（须门牌街名行 + City, ST 行，且 Deliver to 可见）",
        );
      }
      return;
    }

    if (
      !(await isAddressPresentInWidget(
        params.ctx.page,
        params.widgetSelector,
        params.address,
      ))
    ) {
      if (placeRequestHttpOk) {
        await dismissAddressSuggestionDropdown(params.ctx.page).catch(
          () => undefined,
        );
        await params.ctx.page.keyboard.press("Escape").catch(() => undefined);
        await params.ctx.page.keyboard.press("Tab").catch(() => undefined);
        const deadline = Date.now() + 4_000;
        while (Date.now() < deadline) {
          if (
            await isAddressPresentInWidget(
              params.ctx.page,
              params.widgetSelector,
              params.address,
            )
          ) {
            return;
          }
          await params.ctx.page.waitForTimeout(200);
        }
        if (placeRequestHttpOk) {
          console.log(
            "[rpa] place-commit: delivery place 200 已观测，widget 文案未同步仍接受网络 commit",
          );
          return;
        }
      }
      this.fail(
        params,
        "selection_failed",
        formatDiagnosticsFailure({
          ...this.diagnostics,
          status: "selection_failed",
          commitState: "FAILED",
        }) + "；派送地址未写入 widget",
      );
    }
  }

  private succeed(
    params: SelectAddressParams,
    placeId: string,
    formattedAddress: string,
    method?: string,
  ): AddressCommitResult {
    this.mergeBufferedSelectionTraces();
    this.stateMachine.transition("COMMITTED");
    this.diagnostics.status = "ok";
    this.diagnostics.method = method ?? this.diagnostics.method;
    this.diagnostics.placeRequestObserved =
      this.bridge?.hasSuccessfulPlaceNetworkRequest(placeId) ?? false;
    this.diagnostics.commitState = "COMMITTED";
    const details = this.bridge?.getPlaceDetails(placeId);
    params.ctx.state.axelPlaceLocations[params.side] = details ?? {
      placeId,
      formattedAddress,
    };
    this.result = {
      success: true,
      placeId,
      formattedAddress,
      committed: true,
    };
    logAddressSelectDiagnostics(params.side, this.getDiagnostics());
    return this.result;
  }

  private async recordSelectionTrace(
    page: import("playwright").Page,
    side: "pickup" | "delivery",
    label: string,
  ): Promise<void> {
    const snapshot = await captureSelectionTrace(page, label);
    this.diagnostics.selectionTraces = [
      ...(this.diagnostics.selectionTraces ?? []),
      snapshot,
    ];
    this.diagnostics.events.push(formatSelectionTraceEvent(snapshot));
    logSelectionTrace(side, snapshot);
  }

  private mergeBufferedSelectionTraces(): void {
    const drained = drainSelectionTraces();
    if (!drained.length) {
      return;
    }
    this.diagnostics.selectionTraces = [
      ...(this.diagnostics.selectionTraces ?? []),
      ...drained,
    ];
    for (const snapshot of drained) {
      this.diagnostics.events.push(formatSelectionTraceEvent(snapshot));
    }
  }

  private fail(
    params: SelectAddressParams,
    status: AddressSelectDiagnostics["status"],
    detail: string,
  ): never {
    this.mergeBufferedSelectionTraces();
    this.stateMachine.transition("FAILED");
    this.diagnostics.status = status;
    this.diagnostics.commitState = "FAILED";
    this.diagnostics.events.push(`failed:${status}`);
    logAddressSelectDiagnostics(params.side, this.getDiagnostics());
    throw new RpaError("ADDRESS_NOT_CONFIRMED", detail, {
      retryable: status !== "selection_failed",
    });
  }
}

/**
 * 「随便选一个」降级：先尝试按文字点击，再 ArrowDown→Enter 后视觉确认
 * 不依赖 placeId API 回调，只做视觉确认
 */
async function selectFirstSuggestionVisual(
  page: import("playwright").Page,
  input: import("playwright").Locator,
  widgetSelector: string,
  address: Parameters<typeof isAddressPresentInWidget>[2],
): Promise<boolean> {
  return mouseClickSuggestionUntilVisualConfirmed(
    page,
    input,
    widgetSelector,
    address,
    "any-suggestion-visual",
  );
}
