/**
 * PR12 Client Executor 工具描述符 —— `client:*` 系列。
 *
 * 这些工具的**执行端在前端壳层（Desktop Electron / Mobile / CLI）**，通过 HostBridge.invoke
 * 调用本机原生 API。后端**仅注册描述符**让 LLM 能在 tools list 看到；当 LLM 决策调用时，
 * messages.service 的流式 pipeline 把 tool_use 发到前端 SSE 流，前端 dispatcher 执行后回
 * 写 tool_result。
 *
 * **不要在后端 invoke() 真执行任何 client: 工具** —— 后端 fs.write 不等于"用户本机 fs.write"。
 * 强制 throw 见各工具 invoke() 实现。
 *
 * 详见 docs/modules/agent/02-architecture.md §1.3.2。
 */

import { Injectable, BadRequestException } from '@nestjs/common';
import type { AgentTool, ToolDescriptor, ToolInvocation, ToolResult } from './tool.types';

/** 任何 client: 工具在后端被直接 invoke = 路由 bug；统一抛错 */
function refuseServerInvoke(toolName: string): never {
  throw new BadRequestException(
    `${toolName} is a client-executed tool and must be dispatched via SSE tool_use → frontend HostBridge.invoke, never invoked server-side.`,
  );
}

const SCOPE_DOC =
  ' 路径相对 ~/FF AI Workspace/，禁止 ../ 越界；首次调用 OS 弹框授权（per session 缓存）。';

@Injectable()
export class FileReadTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'File.read',
    description: '读取用户本机文件内容（仅 Desktop / CLI）。' + SCOPE_DOC,
    inputSchema: {
      path: { type: 'string', required: true, description: '相对路径，相对 ~/FF AI Workspace/' },
      encoding: { type: 'string', description: '"utf8"（默认）或 "base64"' },
    },
    availability: {
      surface: ['desktop', 'cli'],
      requiredCapabilities: ['fs.read'],
    },
    writeAction: false,
    dispatchKey: 'client:fs.read',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class FileWriteTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'File.write',
    description: '写入用户本机文件（仅 Desktop / CLI）。' + SCOPE_DOC + ' 默认拒绝覆盖，须显式 overwrite=true。',
    inputSchema: {
      path: { type: 'string', required: true, description: '相对路径，相对 ~/FF AI Workspace/' },
      content: { type: 'string', required: true, description: '文件内容' },
      encoding: { type: 'string', description: '"utf8"（默认）或 "base64"' },
      overwrite: { type: 'boolean', description: '文件存在时是否覆盖；默认 false' },
    },
    availability: {
      surface: ['desktop', 'cli'],
      requiredCapabilities: ['fs.write'],
    },
    writeAction: true,
    dispatchKey: 'client:fs.write',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class FileListTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'File.list',
    description: '列用户本机目录内容（仅 Desktop / CLI）。' + SCOPE_DOC + ' 单次最多 1000 条。',
    inputSchema: {
      path: { type: 'string', required: true, description: '目录相对路径' },
      limit: { type: 'number', description: '最多返回多少条，默认 100，上限 1000' },
    },
    availability: {
      surface: ['desktop', 'cli'],
      requiredCapabilities: ['fs.list'],
    },
    writeAction: false,
    dispatchKey: 'client:fs.list',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class ClipboardReadTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'Clipboard.read',
    description: '读取用户系统剪贴板文本（仅 Desktop / Mobile）。首次调用 OS 弹框授权。',
    inputSchema: {},
    availability: {
      surface: ['desktop', 'mobile'],
      requiredCapabilities: ['clipboard.read'],
    },
    writeAction: false,
    dispatchKey: 'client:clipboard.read',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class ClipboardWriteTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'Clipboard.write',
    description: '写入用户系统剪贴板文本（仅 Desktop / Mobile）。首次调用 OS 弹框授权。',
    inputSchema: {
      text: { type: 'string', required: true, description: '要写入剪贴板的文本' },
    },
    availability: {
      surface: ['desktop', 'mobile'],
      requiredCapabilities: ['clipboard.write'],
    },
    writeAction: true,
    dispatchKey: 'client:clipboard.write',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class NotifyPushTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'Notify.push',
    description: '在用户本机弹系统通知（长任务完成等场景）。Desktop / Mobile。',
    inputSchema: {
      title: { type: 'string', required: true, description: '通知标题' },
      body: { type: 'string', description: '通知正文（可选）' },
    },
    availability: {
      surface: ['desktop', 'mobile'],
      requiredCapabilities: ['notify'],
    },
    writeAction: false,
    dispatchKey: 'client:notify',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class ShellOpenExternalTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'Shell.openExternal',
    description:
      '用默认浏览器打开外部 URL（仅 http/https）。Web 端走 window.open / Desktop 走 shell.openExternal。',
    inputSchema: {
      url: { type: 'string', required: true, description: 'http:// 或 https:// URL' },
    },
    availability: {
      // mobile / teams 不列入：当前 SURFACE_DEFAULT_CAPABILITIES.mobile=[]，
      // 即使 surface 列入也会被 capability 过滤掉，避免 descriptor 字段误导。
      // mobile 真实化时（PR Phase 3）一并补默认 cap + surface。
      surface: ['web', 'desktop', 'cli'],
      requiredCapabilities: ['shell.openExternal'],
    },
    writeAction: false,
    dispatchKey: 'client:shell.openExternal',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}

@Injectable()
export class ShellExecTool implements AgentTool {
  readonly descriptor: ToolDescriptor = {
    name: 'Shell.exec',
    description:
      '在用户本机执行命令（仅 Desktop / CLI，默认 admin 关停）。解释器白名单 python/node/bash；网络 egress 默认禁。需要 IT 审批开启。',
    inputSchema: {
      cmd: { type: 'string', required: true, description: '执行命令（在白名单内）' },
      args: { type: 'string', description: 'JSON-stringified 参数数组' },
      cwd: { type: 'string', description: '工作目录（默认 ~/FF AI Workspace/）' },
      timeoutMs: { type: 'number', description: '超时毫秒，默认 30000' },
    },
    availability: {
      surface: ['desktop', 'cli'],
      requiredCapabilities: ['shell.exec'],
    },
    writeAction: true,
    dispatchKey: 'client:shell.exec',
  };
  invoke(_invocation: ToolInvocation): Promise<ToolResult> {
    refuseServerInvoke(this.descriptor.name);
  }
}
