/**
 * 文件差异对比工具（1 个）
 *
 * diff_viewer — 对比两个文件或两段文本的差异
 *
 * 生成 unified diff 格式输出，支持行级差异检测。
 * 用于 Agent 在编辑文件前后对比变化，或对比两个配置文件。
 */

import { readFile } from 'fs/promises';
import type { IMetonaTool, ToolExecutionContext } from '../../types/metona-tool';
import type { MetonaToolDef } from '../../../harness/types';
import { MetonaToolCategory, MetonaRiskLevel } from '../../../harness/types';
import {
  safeResolvePath,
  extractErrorMessage,
  decodeBufferWithDetection,
} from './file-guard';

interface DiffLine {
  type: 'context' | 'added' | 'removed';
  oldLineNo: number | null;
  newLineNo: number | null;
  content: string;
}

/** 最长公共子序列（LCS）diff 算法 */
function computeDiff(oldLines: string[], newLines: string[]): DiffLine[] {
  // 构建 LCS 表（限制内存：大文件截断到 5000 行）
  const maxLines = 5000;
  const oldSliced = oldLines.slice(0, maxLines);
  const newSliced = newLines.slice(0, maxLines);
  const sm = oldSliced.length;
  const sn = newSliced.length;

  // F4-1: 使用 Uint16Array 一维数组替代 Uint32Array，节省一半内存
  // LCS 长度最大值为 min(sm, sn) <= 5000，远小于 Uint16Array 上限 65535，安全
  const lcs = new Uint16Array((sm + 1) * (sn + 1));
  const idx = (i: number, j: number): number => i * (sn + 1) + j;

  for (let i = 1; i <= sm; i++) {
    for (let j = 1; j <= sn; j++) {
      if (oldSliced[i - 1] === newSliced[j - 1]) {
        lcs[idx(i, j)] = lcs[idx(i - 1, j - 1)] + 1;
      } else {
        lcs[idx(i, j)] = Math.max(lcs[idx(i - 1, j)], lcs[idx(i, j - 1)]);
      }
    }
  }

  // 回溯生成 diff
  const result: DiffLine[] = [];
  let i = sm, j = sn;

  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && oldSliced[i - 1] === newSliced[j - 1]) {
      result.unshift({ type: 'context', oldLineNo: i, newLineNo: j, content: oldSliced[i - 1] });
      i--; j--;
    } else if (j > 0 && (i === 0 || lcs[idx(i, j - 1)] >= lcs[idx(i - 1, j)])) {
      result.unshift({ type: 'added', oldLineNo: null, newLineNo: j, content: newSliced[j - 1] });
      j--;
    } else {
      result.unshift({ type: 'removed', oldLineNo: i, newLineNo: null, content: oldSliced[i - 1] });
      i--;
    }
  }

  return result;
}

/** 生成 unified diff 格式字符串 */
function formatUnifiedDiff(diffLines: DiffLine[], oldLabel: string, newLabel: string, contextLines: number = 3): string {
  const lines: string[] = [];
  lines.push(`--- ${oldLabel}`);
  lines.push(`+++ ${newLabel}`);

  let oldLine = 1;
  let newLine = 1;
  let hunkLines: string[] = [];
  let hunkStartOld = 1;
  let hunkStartNew = 1;
  let inHunk = false;
  let contextSinceChange = 0;

  const flushHunk = () => {
    if (hunkLines.length > 0) {
      // 移除尾部多余的 context 行
      const trimmed: string[] = [...hunkLines];
      while (trimmed.length > 0 && trimmed[trimmed.length - 1].startsWith(' ') && contextSinceChange > 0) {
        trimmed.pop();
        contextSinceChange--;
      }
      if (trimmed.length > 0) {
        const oldCount = trimmed.filter((l) => l.startsWith('-') || l.startsWith(' ')).length;
        const newCount = trimmed.filter((l) => l.startsWith('+') || l.startsWith(' ')).length;
        lines.push(`@@ -${hunkStartOld},${oldCount} +${hunkStartNew},${newCount} @@`);
        lines.push(...trimmed);
      }
    }
    hunkLines = [];
    inHunk = false;
    contextSinceChange = 0;
  };

  for (const dl of diffLines) {
    if (dl.type === 'context') {
      if (inHunk) {
        hunkLines.push(` ${dl.content}`);
        contextSinceChange++;
        // 超过 contextLines 行连续 context，结束当前 hunk
        if (contextSinceChange > contextLines) {
          flushHunk();
        }
      }
      oldLine++;
      newLine++;
    } else if (dl.type === 'added') {
      if (!inHunk) {
        inHunk = true;
        hunkStartOld = oldLine;
        hunkStartNew = newLine;
      }
      hunkLines.push(`+${dl.content}`);
      contextSinceChange = 0;
      newLine++;
    } else if (dl.type === 'removed') {
      if (!inHunk) {
        inHunk = true;
        hunkStartOld = oldLine;
        hunkStartNew = newLine;
      }
      hunkLines.push(`-${dl.content}`);
      contextSinceChange = 0;
      oldLine++;
    }
  }
  flushHunk();

  return lines.join('\n');
}

export class DiffViewerTool implements IMetonaTool {
  readonly definition: MetonaToolDef = {
    name: 'diff_viewer',
    description: 'Compare two files or two text snippets and show differences. Generates unified diff format output. Useful for reviewing changes before applying or comparing configurations.',
    parameters: {
      type: 'object',
      properties: {
        mode: {
          type: 'string',
          description: 'Comparison mode',
          enum: ['files', 'text'],
        },
        file_a: { type: 'string', description: 'First file path (for files mode)' },
        file_b: { type: 'string', description: 'Second file path (for files mode)' },
        text_a: { type: 'string', description: 'First text content (for text mode)' },
        text_b: { type: 'string', description: 'Second text content (for text mode)' },
        context_lines: { type: 'number', description: 'Context lines around changes (default 3, max 10)' },
      },
      required: ['mode'],
    },
    category: MetonaToolCategory.FILESYSTEM,
    riskLevel: MetonaRiskLevel.SAFE,
    requiresPermission: false,
    timeoutMs: 15_000,
  };

  async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
    try {
      const mode = args.mode as 'files' | 'text';
      const contextLines = Math.min(10, Math.max(0, (args.context_lines as number) ?? 3));

      // D4.5: 校验 mode
      if (mode !== 'files' && mode !== 'text') {
        return { success: false, error: `Invalid mode: ${mode}. Must be 'files' or 'text'` };
      }

      let contentA: string;
      let contentB: string;
      let labelA: string;
      let labelB: string;

      if (mode === 'files') {
        const fileA = args.file_a as string;
        const fileB = args.file_b as string;
        if (!fileA || !fileB) {
          return { success: false, error: 'file_a and file_b are required for files mode' };
        }

        // F4-1: 用 safeResolvePath 统一路径校验（含 isPathWithinWorkspace + MEMORY.md 拦截）
        let pathA: string;
        let pathB: string;
        try {
          pathA = safeResolvePath(fileA, context.workspacePath);
          pathB = safeResolvePath(fileB, context.workspacePath);
        } catch (error) {
          return { success: false, error: extractErrorMessage(error) };
        }

        try {
          // F4-1: 用智能编码检测读取文件（支持 GBK/UTF-16 等非 UTF-8 编码）
          const bufferA = await readFile(pathA);
          const bufferB = await readFile(pathB);
          contentA = decodeBufferWithDetection(bufferA).content;
          contentB = decodeBufferWithDetection(bufferB).content;
        } catch (err) {
          return { success: false, error: `Failed to read files: ${extractErrorMessage(err)}` };
        }
        labelA = fileA;
        labelB = fileB;
      } else {
        contentA = (args.text_a as string) ?? '';
        contentB = (args.text_b as string) ?? '';
        labelA = 'text_a';
        labelB = 'text_b';
      }

      const linesA = contentA.split('\n');
      const linesB = contentB.split('\n');

      const diff = computeDiff(linesA, linesB);
      const unifiedDiff = formatUnifiedDiff(diff, labelA, labelB, contextLines);

      const addedCount = diff.filter((d) => d.type === 'added').length;
      const removedCount = diff.filter((d) => d.type === 'removed').length;
      const contextCount = diff.filter((d) => d.type === 'context').length;

      // D4.3: similarity 计算使用截断后的长度作为分母
      const maxLines = 5000;
      const oldSlicedLen = Math.min(linesA.length, maxLines);
      const newSlicedLen = Math.min(linesB.length, maxLines);
      const oldTruncated = linesA.length > maxLines;
      const newTruncated = linesB.length > maxLines;
      const truncated = oldTruncated || newTruncated;

      // 生成摘要
      const summary = {
        files_compared: mode === 'files' ? 2 : 0,
        lines_added: addedCount,
        lines_removed: removedCount,
        lines_unchanged: contextCount,
        total_changes: addedCount + removedCount,
        similarity: (oldSlicedLen + newSlicedLen) > 0
          ? Math.round((contextCount * 2 / (oldSlicedLen + newSlicedLen)) * 100) / 100
          : 1,
        truncated,
      };

      // D4.6: unifiedDiff 大小限制
      const MAX_DIFF_CHARS = 50_000;
      const truncatedDiff = unifiedDiff.length > MAX_DIFF_CHARS
        ? unifiedDiff.slice(0, MAX_DIFF_CHARS) + '\n... (diff truncated)'
        : unifiedDiff;

      return {
        success: true,
        summary,
        diff: truncatedDiff,
        diff_lines: diff.slice(0, 500),
      };
    } catch (err) {
      // F4-2: 统一错误处理，避免 non-Error 抛出值被 String 强转丢失信息
      return { success: false, error: extractErrorMessage(err) };
    }
  }
}
