/**
 * EditFile Tool - performs exact string replacements in files
 * Enhanced: atomic write, diff preview, line number reporting
 */
import fs from 'fs/promises';
import path from 'path';
import { randomBytes } from 'crypto';

export function createEditFileTool(config) {
  return {
    description: 'Edit a file by replacing an exact string match. old_string must be unique unless replace_all is true. Uses atomic writes.',
    parameters: {
      type: 'object',
      properties: {
        file_path: {
          type: 'string',
          description: 'Path to the file to edit',
        },
        old_string: {
          type: 'string',
          description: 'The exact text to find and replace',
        },
        new_string: {
          type: 'string',
          description: 'The replacement text',
        },
        replace_all: {
          type: 'boolean',
          description: 'Replace all occurrences (default: false)',
        },
      },
      required: ['file_path', 'old_string', 'new_string'],
    },
    execute: async (input, runtime = {}) => {
      runtime.info?.(`Editing file ${input.file_path}`, {
        file_path: input.file_path,
        replace_all: !!input.replace_all,
      });
      const filePath = path.resolve(config.workDir, input.file_path);

      // Read current content
      let content;
      try {
        content = await fs.readFile(filePath, 'utf-8');
      } catch (e) {
        if (e.code === 'ENOENT') return { error: `File not found: ${input.file_path}` };
        if (e.code === 'EACCES') return { error: `Permission denied: ${input.file_path}` };
        return { error: `Cannot read ${input.file_path}: ${e.message}` };
      }

      if (input.old_string === input.new_string) {
        return { error: 'old_string and new_string are identical.' };
      }

      const count = content.split(input.old_string).length - 1;

      if (count === 0) {
        // Provide helpful hint: show closest match
        const snippet = input.old_string.substring(0, 40);
        return { error: `old_string not found in ${input.file_path}. Searched for: "${snippet}${input.old_string.length > 40 ? '...' : ''}"` };
      }

      if (count > 1 && !input.replace_all) {
        return { error: `old_string found ${count} times. Provide more context to make it unique, or set replace_all: true.` };
      }

      // Find line number of first match
      const beforeMatch = content.indexOf(input.old_string);
      const lineNum = content.substring(0, beforeMatch).split('\n').length;

      // Apply replacement
      let newContent;
      if (input.replace_all) {
        newContent = content.replaceAll(input.old_string, input.new_string);
      } else {
        newContent = content.replace(input.old_string, input.new_string);
      }

      // Generate unified diff for UI display
      const oldLines = content.split('\n');
      const newLines = newContent.split('\n');
      const diffLines = [];
      const contextRadius = 2;
      // Find changed line ranges
      const changedLines = new Set();
      const maxLen = Math.max(oldLines.length, newLines.length);
      for (let i = 0; i < maxLen; i++) {
        if (oldLines[i] !== newLines[i]) changedLines.add(i);
      }
      for (const i of [...changedLines]) {
        for (let j = Math.max(0, i - contextRadius); j <= Math.min(maxLen - 1, i + contextRadius); j++) {
          if (!changedLines.has(j)) changedLines.add(j);
        }
      }
      const sortedLines = [...changedLines].sort((a, b) => a - b);
      for (const i of sortedLines) {
        const ol = oldLines[i], nl = newLines[i];
        if (ol === nl) {
          if (ol !== undefined) diffLines.push(` ${ol}`);
        } else {
          if (ol !== undefined) diffLines.push(`-${ol}`);
          if (nl !== undefined) diffLines.push(`+${nl}`);
        }
      }
      const diff = diffLines.join('\n');

      // Save undo snapshot (stored on config for server access)
      const opId = `edit_${Date.now()}_${randomBytes(3).toString('hex')}`;
      if (config._undoStack) {
        config._undoStack.push({
          id: opId,
          filePath: input.file_path,
          fullPath: filePath,
          oldContent: content,
          timestamp: Date.now(),
        });
        // Keep last 30 undo entries
        if (config._undoStack.length > 30) config._undoStack.shift();
      }

      // Atomic write
      const tmpSuffix = randomBytes(4).toString('hex');
      const tmpPath = `${filePath}.tmp_${tmpSuffix}`;
      try {
        await fs.writeFile(tmpPath, newContent, 'utf-8');
        await fs.rename(tmpPath, filePath);
      } catch (e) {
        await fs.unlink(tmpPath).catch(() => {});
        return { error: `Write failed: ${e.message}` };
      }

      const replacements = input.replace_all ? `${count} replacements` : `1 replacement at line ${lineNum}`;
      runtime.info?.(`Edited ${input.file_path}`, {
        file_path: input.file_path,
        lineNum,
        replacementCount: count,
        replace_all: !!input.replace_all,
      });
      return {
        result: `Edited ${input.file_path} (${replacements})`,
        diff,
        undoId: opId,
        lineNum,
        replacementCount: count,
      };
    },
  };
}
