import { mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { z } from 'zod/v4'
import { runApply } from './actions/apply.js'
import { runInspect } from './actions/inspect.js'
import { runPrepare } from './actions/prepare.js'
import { runRead } from './actions/read.js'
import { runRecord } from './actions/record.js'
import { runScript } from './actions/run.js'
import { runSearch } from './actions/search.js'
import { runStatus } from './actions/status.js'
import { configure, requireCodeWorkDir, resetConfigure, getCachedConfigure } from './hooks.js'
import { buildCodeToolSessionAppend } from './sessionAppend.js'
import { initWsSession } from './wsInit.js'

export {
  configure,
  resetConfigure,
  initWsSession,
  buildCodeToolSessionAppend,
  buildCodeToolSessionAppend as buildSessionAppend,
}
export { resolveCodeWorkDir } from './workspace.js'
export type { CodeToolConfigureHooks } from './hooks.js'
export { isEnabledInAgentConfig } from './agentConfig.js'
export type { CodeToolWsInitContext, CodeToolWsInitResult } from './wsInit.js'

export function ensureConfigured(): void {
  const cached = getCachedConfigure()
  if (cached) {
    configure(cached)
  }
}

const inputSchema = z.discriminatedUnion('action', [
  z.object({
    action: z.literal('prepare'),
  }),
  z.object({
    action: z.literal('inspect'),
    brief: z.boolean().optional(),
  }),
  z.object({
    action: z.literal('record'),
    section: z.string().min(1),
    content: z.string().min(1),
    mode: z.enum(['upsert', 'append']).default('upsert'),
  }),
  z.object({
    action: z.literal('read'),
    path: z.string().min(1),
    offset: z.number().int().positive().optional(),
    limit: z.number().int().positive().optional(),
  }),
  z.object({
    action: z.literal('search'),
    pattern: z.string().min(1),
    glob: z.string().optional(),
  }),
  z.object({
    action: z.literal('apply'),
    patches: z
      .array(
        z.object({
          path: z.string().min(1),
          old_string: z.string(),
          new_string: z.string(),
        }),
      )
      .min(1),
  }),
  z.object({
    action: z.literal('run'),
    script: z.string().min(1),
  }),
  z.object({
    action: z.literal('status'),
  }),
])

async function writeResult(
  toolResultsDir: string | undefined,
  payload: unknown,
): Promise<string | undefined> {
  if (!toolResultsDir) return undefined
  await mkdir(toolResultsDir, { recursive: true })
  const file = join(toolResultsDir, `code-tool-${Date.now()}.json`)
  await writeFile(file, JSON.stringify(payload, null, 2), 'utf8')
  return file
}

export const CodeTool = {
  name: 'CodeTool',
  description: async () =>
    '代码开发工作区：加载 code.md、读写仓库、record 沉淀、受控脚本',
  listInterfaces: [
    { name: 'prepare', purpose: '确认 codeWorkDir' },
    { name: 'inspect', purpose: '加载 code.md 与项目摘要' },
    { name: 'read', purpose: '读取代码文件' },
    { name: 'search', purpose: '在仓库内搜索内容' },
    { name: 'apply', purpose: '受控补丁修改业务文件' },
    { name: 'run', purpose: '执行 package.json scripts 白名单' },
    { name: 'record', purpose: '增量更新 code.md' },
    { name: 'status', purpose: '查看 git 简要状态' },
  ],
  prompt: async () => {
    let dir = '(未配置)'
    try {
      dir = requireCodeWorkDir()
    } catch {
      // init 前 prompt 序列化
    }
    return `# CodeTool — 代码开发（唯一入口，codeWorkDir=${dir}）

项目规范来自 **code.md**（本工具加载与维护）。禁止对 codeWorkDir 内业务文件使用裸 Read/Edit/Write/Glob/Grep。

## action 分工
| action | 用途 |
| prepare | 确认 codeWorkDir |
| inspect | 加载 code.md + 项目摘要（涉及仓库的开发/分析前必调） |
| read / search | 读代码、搜内容 |
| apply | 受控补丁改业务文件（禁止改 code.md） |
| run | package.json scripts 白名单 |
| record | 增量更新 code.md |
| status | git 简要状态 |

## 标准流程
1. prepare → inspect
2. 若无 code.md：record 首建 overview、commands（可据 inspect 的 tree/manifest）
3. read/search 理解 → apply 修改 → run 验证
4. 发现非显而易见信息时 record（architecture / commands / patterns / gotchas）
5. 任务完成前 record 收尾；需 clone/commit 等用 GitTool

## record 时机
- 摸清模块/调用链 → section architecture
- 非常规 build/test/环境 → commands、gotchas
- 可复用改法 → patterns
- 删掉后下次会做错才写，不写显而易见内容`
  },
  inputSchema,
  maxResultSizeChars: 120_000,
  isEnabled: () => true,
  isReadOnly: (input: z.infer<typeof inputSchema>) =>
    input.action === 'inspect' ||
    input.action === 'read' ||
    input.action === 'search' ||
    input.action === 'status' ||
    input.action === 'prepare',
  isConcurrencySafe: (input: z.infer<typeof inputSchema>) =>
    input.action === 'inspect' || input.action === 'read' || input.action === 'search',
  isDestructive: (input: z.infer<typeof inputSchema>) =>
    input.action === 'apply' || input.action === 'record' || input.action === 'run',
  checkPermissions: async () => ({ behavior: 'ask' as const, updatedInput: {} }),
  validateInput: async (input: unknown) => {
    const parsed = inputSchema.safeParse(input)
    if (!parsed.success) {
      return {
        result: false as const,
        message: parsed.error.message,
        errorCode: 0,
      }
    }
    try {
      requireCodeWorkDir()
      return { result: true as const }
    } catch (e) {
      return {
        result: false as const,
        message: e instanceof Error ? e.message : String(e),
        errorCode: 0,
      }
    }
  },
  call: async (input: z.infer<typeof inputSchema>, ctx: { toolResultsDir?: string }) => {
    let data: Record<string, unknown>
    switch (input.action) {
      case 'prepare':
        data = await runPrepare()
        break
      case 'inspect':
        data = await runInspect(Boolean(input.brief))
        break
      case 'record':
        data = await runRecord({
          section: input.section,
          content: input.content,
          mode: input.mode,
        })
        break
      case 'read':
        data = await runRead(input.path, input.offset, input.limit)
        break
      case 'search':
        data = await runSearch(input.pattern, input.glob)
        break
      case 'apply':
        data = await runApply(input.patches)
        break
      case 'run':
        data = await runScript(input.script)
        break
      case 'status':
        data = await runStatus()
        break
      default:
        throw new Error('unknown action')
    }
    const resultFile = await writeResult(ctx.toolResultsDir, data)
    return { data: { ...data, resultFile } }
  },
  renderToolUseMessage: (input: { action?: string }) =>
    `CodeTool: ${input.action ?? '…'}`,
  renderToolResultMessage: () => 'CodeTool 完成',
  mapToolResultToToolResultBlockParam: (output: { data?: Record<string, unknown> }) => ({
    type: 'text' as const,
    text: JSON.stringify(output.data ?? output, null, 2),
  }),
}

export default CodeTool
