// HostBridge invoke dispatcher —— main.ts 的 IPC handler 路由到这里。
//
// 设计：
// - 工具名 → capability 映射（whitelist）
// - 首次调用走 ensureCapabilityGranted（OS 弹框，per session 缓存决策）
// - 实际工具调用按 capability 分发到 fs-ops / clipboard / notify / shell 模块
// - 任何错误归一化为 HostInvokeResult { granted, error }
//
// 详见 docs/modules/agent/02-architecture.md §1.3.2。

import { Notification, clipboard, shell } from "electron";
import type { HostCapability, HostInvokeResult } from "@ffai/agent-protocol";
import { ensureCapabilityGranted } from "./permission";
import { readFile, writeFile, listDir } from "./fs-ops";

/** 工具名（agent tool_use.name）→ 必需 capability */
const TOOL_TO_CAPABILITY: Record<string, HostCapability> = {
  "client:fs.read": "fs.read",
  "client:fs.write": "fs.write",
  "client:fs.list": "fs.list",
  "client:clipboard.read": "clipboard.read",
  "client:clipboard.write": "clipboard.write",
  "client:notify": "notify",
  "client:shell.exec": "shell.exec",
  "client:shell.openExternal": "shell.openExternal",
};

export async function dispatchInvoke(tool: string, args: unknown): Promise<HostInvokeResult> {
  const cap = TOOL_TO_CAPABILITY[tool];
  if (!cap) return { granted: false, error: `unknown_tool:${tool}` };

  // 注意：clipboard.write / notify 也走授权，避免恶意 prompt 静默触发
  const ok = await ensureCapabilityGranted(cap, {
    tool,
    argsSummary: summarizeArgs(args),
  });
  if (!ok) return { granted: false, error: "user_denied" };

  try {
    switch (tool) {
      case "client:fs.read": {
        const result = await readFile(args as Parameters<typeof readFile>[0]);
        return { granted: true, result };
      }
      case "client:fs.write": {
        const result = await writeFile(args as Parameters<typeof writeFile>[0]);
        return { granted: true, result };
      }
      case "client:fs.list": {
        const result = await listDir(args as Parameters<typeof listDir>[0]);
        return { granted: true, result };
      }
      case "client:clipboard.read": {
        return { granted: true, result: { text: clipboard.readText() } };
      }
      case "client:clipboard.write": {
        const text = (args as { text?: string })?.text;
        if (typeof text !== "string") return { granted: false, error: "text_required" };
        clipboard.writeText(text);
        return { granted: true };
      }
      case "client:notify": {
        const { title, body } = (args ?? {}) as { title?: unknown; body?: unknown };
        if (typeof title !== "string") return { granted: false, error: "title_required" };
        if (body !== undefined && typeof body !== "string") {
          return { granted: false, error: "body_must_be_string" };
        }
        new Notification({ title, body: body as string | undefined }).show();
        return { granted: true };
      }
      case "client:shell.openExternal": {
        const url = (args as { url?: string })?.url;
        if (typeof url !== "string" || !/^https?:\/\//i.test(url)) {
          return { granted: false, error: "invalid_url" };
        }
        await shell.openExternal(url);
        return { granted: true };
      }
      case "client:shell.exec": {
        // PR13 显式拒绝：OS 沙盒 PoC 未完成 + 红队 25 case 0 逃逸未通过 = release blocker
        // 详见 docs/modules/agent/01-prd-phase2.md §3 PR13 行 + .learnings/2026-05-16-pr13-shell-exec-blockers.md
        return {
          granted: false,
          error: "shell_exec_blocked:sandbox_poc_pending+redteam_pending",
        };
      }
      default:
        return { granted: false, error: `not_implemented:${tool}` };
    }
  } catch (err) {
    return { granted: false, error: (err as Error).message };
  }
}

/** 给 permission 弹窗用的参数摘要（脱敏 + 截断） */
function summarizeArgs(args: unknown): string | undefined {
  if (args === null || args === undefined) return undefined;
  try {
    const json = JSON.stringify(args, (key, value) => {
      if (/password|token|secret|api_?key/i.test(key)) return "<redacted>";
      if (typeof value === "string" && value.length > 100) {
        return value.slice(0, 100) + `…(+${value.length - 100} chars)`;
      }
      return value;
    });
    return json.length > 300 ? json.slice(0, 300) + "…" : json;
  } catch {
    return undefined;
  }
}
