import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { Page } from "playwright";
import {
  PLACE_DIAG_COLLECT_SNAPSHOT,
  PLACE_DIAG_SET_MARK,
} from "@/workers/rpa/fix-place/place-diagnostic-init";

export type PlaceListenerFingerprint = {
  source: string;
  length: number;
  preview: string;
  hasFetch: boolean;
  hasAxelPlace: boolean;
  hasGetPlace: boolean;
  hasXMLHttpRequest: boolean;
};

export type PlaceDiagSnapshot = {
  ok: boolean;
  reason?: string;
  mark?: string;
  topology?: {
    instanceCount: number;
    instances: Array<{
      id: string;
      input: Record<string, unknown> | null;
      listenerCount: number;
      listenerFingerprints: PlaceListenerFingerprint[];
      getPlace: Record<string, unknown> | null;
    }>;
    inputProbes: Array<Record<string, unknown>>;
    monkeyPatchInstances: number;
    capturedHandlers: number;
  };
  listeners?: {
    total: number;
    anyHasFetch: boolean;
    anyHasAxelPlace: boolean;
    rows: Array<{ id: string; instanceId: string; fingerprint: PlaceListenerFingerprint }>;
  };
  events?: Array<Record<string, unknown>>;
  handlerEvents?: Array<Record<string, unknown>>;
  fetchLog?: Array<Record<string, unknown>>;
  activeElement?: Record<string, unknown> | null;
};

export function isPlaceDiagEnabled(): boolean {
  return process.env.RPA_PLACE_DIAG === "true";
}

export async function setPlaceDiagMark(page: Page, mark: string): Promise<void> {
  if (!isPlaceDiagEnabled()) {
    return;
  }
  await page.evaluate(
    ({ fn, m }) => {
      const runner = eval(`(${fn})`) as (m: string) => unknown;
      return runner(m);
    },
    { fn: PLACE_DIAG_SET_MARK, m: mark },
  );
}

export async function collectPlaceDiagSnapshot(
  page: Page,
): Promise<PlaceDiagSnapshot> {
  const raw = await page.evaluate((fn) => {
    const runner = eval(`(${fn})`) as () => unknown;
    return runner();
  }, PLACE_DIAG_COLLECT_SNAPSHOT);
  return (raw ?? { ok: false, reason: "evaluate-undefined" }) as PlaceDiagSnapshot;
}

/** 按用户 P0 决策树输出中文结论 */
export function interpretPlaceDiagnostic(
  snapshot: PlaceDiagSnapshot | undefined | null,
  phase: string,
): string[] {
  const lines: string[] = [`[place-diag:${phase}]`];
  if (!snapshot?.ok) {
    lines.push("P0 未注入或页面无 __rpaPlaceDiag");
    return lines;
  }

  const topo = snapshot.topology;
  const listeners = snapshot.listeners;
  const handlerEvents = snapshot.handlerEvents ?? [];
  const fetchLog = snapshot.fetchLog ?? [];

  lines.push(
    `实例拓扑：${topo?.instanceCount ?? 0} 个 Autocomplete；input 探针 ${topo?.inputProbes?.length ?? 0}；捕获 handler ${topo?.capturedHandlers ?? 0}`,
  );
  lines.push(
    `监听器指纹：共 ${listeners?.total ?? 0}；含 fetch=${listeners?.anyHasFetch ? "是" : "否"}；含 axel/place=${listeners?.anyHasAxelPlace ? "是" : "否"}`,
  );

  if (handlerEvents.length === 0) {
    lines.push("本阶段无 place_changed 触发（handler/trigger 均未记录）");
  } else {
    for (const ev of handlerEvents) {
      const instId = String(ev.instanceId ?? "?");
      const type = String(ev.type ?? "");
      const gp = (ev.getPlaceBefore ?? ev.getPlaceAtTrigger) as Record<string, unknown> | undefined;
      lines.push(
        `触发：${type} instance=${instId} place_id=${gp?.place_id ?? "空"} geometry=${gp?.hasGeometry ? "有" : "无"}`,
      );
    }
  }

  if ((listeners?.total ?? 0) === 0) {
    lines.push(
      "判定：无任何 place_changed 监听器被 P0 捕获 → MotherShip 可能自管实例引用，需用 input[gm_*] 定位真实实例",
    );
    const probes = topo?.inputProbes ?? [];
    const withGm = probes.filter((p) => (p as { hasGmInstance?: boolean }).hasGmInstance);
    if (withGm.length > 0) {
      lines.push(`input 上发现 ${withGm.length} 个 gm 实例（未走 addListener 包装）`);
    }
  } else if (!listeners?.anyHasFetch && !listeners?.anyHasAxelPlace) {
    lines.push(
      "判定：有监听器但指纹无 fetch/axel → commit 可能不在 listener 内发 HTTP，或由其他层发请求",
    );
  }

  const lastHandler = handlerEvents
    .filter((e) => e.type === "place_changed_handler")
    .slice(-1)[0];
  if (lastHandler) {
    const gp = lastHandler.getPlaceBefore as Record<string, unknown> | undefined;
    if (!gp?.place_id) {
      lines.push("判定：监听器已触发但 getPlace() 无 place_id → 数据不完整，检查 mock/trigger 时机");
    } else if (!gp?.hasGeometry) {
      lines.push("判定：place_id 有但无 geometry → getPlace 返回不完整 Place");
    }
  }

  if (handlerEvents.length > 0 && fetchLog.length === 0) {
    lines.push(
      "判定：place_changed 有记录但 P2 无 place fetch → 可能被防抖/条件分支拦截，查 P2 stack 或 widget 状态机",
    );
  } else if (fetchLog.length > 0) {
    lines.push(`P2 观测 place fetch ${fetchLog.length} 次（最近阶段）`);
  }

  return lines;
}

export async function logPlaceDiagnostic(
  page: Page,
  phase: string,
): Promise<PlaceDiagSnapshot> {
  if (!isPlaceDiagEnabled()) {
    return { ok: false, reason: "disabled" };
  }
  const snapshot = await collectPlaceDiagSnapshot(page);
  for (const line of interpretPlaceDiagnostic(snapshot, phase)) {
    console.log(line);
  }
  return snapshot;
}

export function writePlaceDiagnosticArtifact(
  runId: string,
  phase: string,
  snapshot: PlaceDiagSnapshot,
): string {
  const dir = join(process.cwd(), ".rpa", "place-diagnostic");
  mkdirSync(dir, { recursive: true });
  const path = join(dir, `${runId}-${phase}.json`);
  writeFileSync(path, JSON.stringify({ phase, snapshot, at: new Date().toISOString() }, null, 2), "utf8");
  return path;
}
