// PR11.4 capability 三处镜像一致性校验。
//
// 三处来源（事实源 + 两份镜像）：
//   A. packages/agent-protocol/src/host-bridge.ts
//        — `HostCapability` union（事实源）
//        — `DESKTOP_ELECTRON_CAPABILITIES` 数组（事实源）
//   B. desktop/src/main.ts
//        — 局部 `DESKTOP_ELECTRON_CAPABILITIES`（ESM/CJS workaround，不能直接 import A）
//   C. backend/src/modules/agent/tools/host-capability.types.ts
//        — `HostCapability` union 字面复制
//        — `SURFACE_DEFAULT_CAPABILITIES.desktop` 数组
//
// PR15.5 抽 monorepo workspace 后 B/C 切回直接 import A；在那之前用本脚本兜底字节一致。
// 任一漂移 → 非零退出，CI 卡死。

import * as fs from "node:fs";
import * as path from "node:path";

// __dirname 编译后在 desktop/scripts/_compiled/；本脚本要访问 desktop/ 的 sibling 目录
// （packages/、backend/），所以回退三层到仓库根。
const REPO = path.resolve(__dirname, "..", "..", "..");
const SRC_PROTOCOL = path.join(REPO, "packages/agent-protocol/src/host-bridge.ts");
const SRC_MAIN = path.join(REPO, "desktop/src/main.ts");
const SRC_BACKEND_CAPS = path.join(REPO, "backend/src/modules/agent/tools/host-capability.types.ts");
const SRC_DISPATCHER = path.join(REPO, "desktop/src/host-bridge/dispatcher.ts");
const SRC_CLIENT_TOOLS = path.join(REPO, "backend/src/modules/agent/tools/client-executor.tools.ts");

function read(file: string): string {
  if (!fs.existsSync(file)) {
    console.error(`[caps] source file missing: ${file}`);
    process.exit(2);
  }
  return fs.readFileSync(file, "utf8");
}

/** 从 `type HostCapability = ... ;` 抽出引号包裹的能力名集合。 */
function extractUnion(src: string, label: string): string[] {
  const m = src.match(/type\s+HostCapability\s*=([^;]+);/);
  if (!m) {
    console.error(`[caps] ${label}: HostCapability union not found`);
    process.exit(2);
  }
  return Array.from(m[1].matchAll(/['"]([\w.]+)['"]/g)).map((x) => x[1]);
}

/** 从命名常量数组里抽字符串字面值。 */
function extractArray(src: string, varName: string, label: string): string[] {
  const re = new RegExp(`${varName}[^=]*=\\s*\\[([\\s\\S]*?)\\]`);
  const m = src.match(re);
  if (!m) {
    console.error(`[caps] ${label}: ${varName} array not found`);
    process.exit(2);
  }
  return Array.from(m[1].matchAll(/['"]([\w.]+)['"]/g)).map((x) => x[1]);
}

function equalArray(a: readonly string[], b: readonly string[]): boolean {
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
  return true;
}

/** 从 desktop dispatcher 抽 TOOL_TO_CAPABILITY 的 key 集合（如 'client:fs.read'）。 */
function extractDispatcherKeys(src: string): string[] {
  const m = src.match(/TOOL_TO_CAPABILITY[^=]*=\s*\{([\s\S]*?)\}/);
  if (!m) {
    console.error("[caps] dispatcher: TOOL_TO_CAPABILITY object not found");
    process.exit(2);
  }
  return Array.from(m[1].matchAll(/['"](client:[\w.]+)['"]\s*:/g)).map((x) => x[1]);
}

/** 从 backend client-executor.tools.ts 抽全部 dispatchKey 值。 */
function extractBackendDispatchKeys(src: string): string[] {
  return Array.from(src.matchAll(/dispatchKey:\s*['"](client:[\w.]+)['"]/g)).map((x) => x[1]);
}

function main(): void {
  const protocol = read(SRC_PROTOCOL);
  const main = read(SRC_MAIN);
  const backend = read(SRC_BACKEND_CAPS);
  const dispatcher = read(SRC_DISPATCHER);
  const clientTools = read(SRC_CLIENT_TOOLS);

  const unionA = extractUnion(protocol, "protocol");
  const unionC = extractUnion(backend, "backend");

  const desktopA = extractArray(protocol, "DESKTOP_ELECTRON_CAPABILITIES", "protocol");
  const desktopB = extractArray(main, "DESKTOP_ELECTRON_CAPABILITIES", "desktop/main");
  // backend 的 desktop 数组在 SURFACE_DEFAULT_CAPABILITIES.desktop 里，抽取方式略不同
  const desktopCMatch = backend.match(/desktop:\s*\[([\s\S]*?)\]/);
  if (!desktopCMatch) {
    console.error("[caps] backend: SURFACE_DEFAULT_CAPABILITIES.desktop not found");
    process.exit(2);
  }
  const desktopC = Array.from(desktopCMatch[1].matchAll(/['"]([\w.]+)['"]/g)).map((x) => x[1]);

  let failed = 0;

  if (!equalArray(unionA, unionC)) {
    console.error("[caps] HostCapability union 不一致：");
    console.error(`  protocol: ${JSON.stringify(unionA)}`);
    console.error(`  backend : ${JSON.stringify(unionC)}`);
    failed++;
  }

  // 三处 desktop 数组必须字节级一致（顺序敏感——constants/permission UI 也按这个顺序）
  if (!equalArray(desktopA, desktopB)) {
    console.error("[caps] DESKTOP_ELECTRON_CAPABILITIES 在 protocol vs desktop/main 不一致：");
    console.error(`  protocol     : ${JSON.stringify(desktopA)}`);
    console.error(`  desktop/main : ${JSON.stringify(desktopB)}`);
    failed++;
  }
  if (!equalArray(desktopA, desktopC)) {
    console.error("[caps] DESKTOP_ELECTRON_CAPABILITIES 在 protocol vs backend 不一致：");
    console.error(`  protocol: ${JSON.stringify(desktopA)}`);
    console.error(`  backend : ${JSON.stringify(desktopC)}`);
    failed++;
  }

  // union ⊇ desktop（desktop 能力不能超 union 范围）
  for (const c of desktopA) {
    if (!unionA.includes(c)) {
      console.error(`[caps] DESKTOP_ELECTRON_CAPABILITIES 含 union 外的成员: ${c}`);
      failed++;
    }
  }

  // dispatcher TOOL_TO_CAPABILITY keys ↔ backend dispatchKey 双向相等
  // （后端给 LLM 用 ToolDescriptor.name；客户端按 dispatchKey 路由——两侧必须 1:1）
  const dispatcherKeys = extractDispatcherKeys(dispatcher);
  const backendKeys = extractBackendDispatchKeys(clientTools);
  const dispatcherSet = new Set(dispatcherKeys);
  const backendSet = new Set(backendKeys);
  const missingInBackend = dispatcherKeys.filter((k) => !backendSet.has(k));
  const missingInDispatcher = backendKeys.filter((k) => !dispatcherSet.has(k));
  if (missingInBackend.length > 0) {
    console.error(
      `[caps] dispatcher TOOL_TO_CAPABILITY 有 key 但 backend 无对应 ToolDescriptor.dispatchKey: ${JSON.stringify(missingInBackend)}`,
    );
    failed++;
  }
  if (missingInDispatcher.length > 0) {
    console.error(
      `[caps] backend ToolDescriptor.dispatchKey 有值但 dispatcher TOOL_TO_CAPABILITY 无路由: ${JSON.stringify(missingInDispatcher)}`,
    );
    failed++;
  }

  if (failed > 0) {
    console.error(`\n[caps] FAILED: ${failed} mirror inconsistencies`);
    process.exit(1);
  }
  console.log(
    `[caps] OK: union=${unionA.length} caps, desktop=${desktopA.length} caps, dispatchKeys=${backendKeys.length}, 3 mirrors + dispatcher↔dispatchKey aligned`,
  );
}

main();
