import { Type } from '@sinclair/typebox';
import {
  AgentToolWithMeta,
  textResult,
} from '../common.js';
import {
  applyEditsToContent,
  detectLineEnding,
  normalizeToLf,
  restoreLineEndings,
  stripBom,
} from '../edit_diff.js';
import { withFileMutationQueueOps } from '../file_mutation_queue.js';
import { registerSchema } from '../catalog.js';
import { resolveToolPathFromParams } from '../path_utils.js';
import { getDefaultOperations, type ToolOperations } from '../operations/index.js';

const EditReplacementParams = Type.Object({
  oldText: Type.String({
    description: '原文件中唯一匹配的文本片段。多个 edits 会同时基于原文件匹配，不能重叠。',
  }),
  newText: Type.String({ description: '替换后的文本' }),
});

const EditParams = Type.Object({
  path: Type.String({ description: '文件绝对路径' }),
  edits: Type.Array(EditReplacementParams, {
    minItems: 1,
    description: '一个或多个精确替换。每个 oldText 必须在原文件中唯一匹配，多个替换不能重叠。',
  }),
});

type EditParamsType = {
  path: string;
  edits?: Array<{ oldText?: unknown; newText?: unknown }> | string;
  oldText?: unknown;
  newText?: unknown;
};

export function createEditTool(opts?: { operations?: ToolOperations }): AgentToolWithMeta<typeof EditParams, unknown> {
  const ops = opts?.operations ?? getDefaultOperations();
  return {
    name: 'edit',
    label: '精确编辑',
    description: '对单个文件执行一个或多个精确文本替换，保留 BOM 与原换行风格，并返回 diff 摘要。',
    parameters: EditParams,
    async execute(_id, rawParams) {
      const params = normalizeEditParams(rawParams as EditParamsType);
      const targetPath = resolveToolPathFromParams(params.path, rawParams as Record<string, unknown>);
      return withFileMutationQueueOps(targetPath, ops.file, async () => {
        try {
          const buffer = await ops.file.readFile(targetPath);
          const raw = buffer.toString('utf-8');
          const { bom, text } = stripBom(raw);
          const lineEnding = detectLineEnding(text);
          const normalized = normalizeToLf(text);
          const result = applyEditsToContent(normalized, params.edits, targetPath);
          const finalContent = bom + restoreLineEndings(result.newContent, lineEnding);
          await ops.file.writeFile(targetPath, finalContent);

          return textResult([
            `已编辑 ${params.edits.length} 处: ${targetPath}`,
            result.firstChangedLine ? `首个变更行: ${result.firstChangedLine}` : '',
            result.diff ? `Diff:\n${result.diff}` : '',
          ].filter(Boolean).join('\n'));
        } catch (err: any) {
          return textResult(`edit 失败: ${err?.message || String(err)}`);
        }
      });
    },
  };
}

export const editTool = createEditTool();

function normalizeEditParams(params: EditParamsType): { path: string; edits: Array<{ oldText: string; newText: string }> } {
  const edits = normalizeEditsArray(params.edits);
  if (typeof params.oldText === 'string' && typeof params.newText === 'string') {
    edits.push({ oldText: params.oldText, newText: params.newText });
  }

  if (typeof params.path !== 'string' || !params.path.trim()) {
    throw new Error('path 必须是非空字符串');
  }
  if (edits.length === 0) {
    throw new Error('edits 至少需要提供一个替换项');
  }

  return {
    path: params.path,
    edits: edits.map((edit, index) => {
      if (typeof edit?.oldText !== 'string' || typeof edit?.newText !== 'string') {
        throw new Error(`edits[${index}] requires string oldText and newText`);
      }
      return {
        oldText: edit.oldText,
        newText: edit.newText,
      };
    }),
  };
}

function normalizeEditsArray(input: EditParamsType['edits']): Array<{ oldText?: unknown; newText?: unknown }> {
  if (Array.isArray(input)) {
    return [...input];
  }

  if (typeof input === 'string') {
    try {
      const parsed = JSON.parse(input);
      if (Array.isArray(parsed)) {
        return [...parsed];
      }
      throw new Error('edits JSON 解析后不是数组');
    } catch (error: any) {
      throw new Error(`edits 不是有效的替换数组: ${error?.message || String(error)}`);
    }
  }

  return [];
}

registerSchema({
  name: 'edit',
  toolset: 'base',
  description: editTool.description,
  capability: 'text',
  visibility: 'tool',
  isCore: true,
  canDisable: false,
  supportsPromptHint: true,
});
