import { readFileSync, writeFileSync } from 'fs';
import type { Tool, ToolResult } from '../types.js';

interface Hunk {
  start_line: number;
  end_line: number;
  content: string;
}

interface FilePatch {
  path: string;
  hunks: Hunk[];
}

interface FilePatchArgs {
  patches: FilePatch[];
}

/**
 * Check whether two hunks overlap.
 * A hunk with end_line < start_line is a pure insertion (zero-width),
 * so it only overlaps if another hunk covers the same insertion point.
 */
function hunksOverlap(a: Hunk, b: Hunk): boolean {
  // For insertion hunks (end < start), treat them as a zero-width point at start_line
  const aStart = a.start_line;
  const aEnd = a.end_line < a.start_line ? a.start_line - 1 : a.end_line;
  const bStart = b.start_line;
  const bEnd = b.end_line < b.start_line ? b.start_line - 1 : b.end_line;

  // Two ranges [aStart, aEnd] and [bStart, bEnd] overlap if aStart <= bEnd && bStart <= aEnd
  return aStart <= bEnd && bStart <= aEnd;
}

export const filePatchTool: Tool = {
  name: 'file.patch',
  description:
    'Apply structured multi-hunk patches to one or more files. Each patch specifies a file path and an array of hunks. Hunks are applied in reverse line order to preserve line numbers.',
  inputSchema: {
    type: 'object',
    properties: {
      patches: {
        type: 'array',
        description: 'Array of file patches to apply',
        items: {
          type: 'object',
          properties: {
            path: { type: 'string', description: 'Absolute path to the file' },
            hunks: {
              type: 'array',
              description: 'Array of hunks to apply to this file',
              items: {
                type: 'object',
                properties: {
                  start_line: { type: 'number', description: 'First line to replace (1-based, inclusive)' },
                  end_line: {
                    type: 'number',
                    description:
                      'Last line to replace (1-based, inclusive). Use start_line - 1 for pure insertion before start_line.',
                  },
                  content: { type: 'string', description: 'Replacement content (empty string to delete lines)' },
                },
                required: ['start_line', 'end_line', 'content'],
              },
            },
          },
          required: ['path', 'hunks'],
        },
      },
    },
    required: ['patches'],
  },

  execute: async (rawArgs: unknown): Promise<ToolResult> => {
    const args = rawArgs as FilePatchArgs;

    try {
      // Validate that patches array is not empty
      if (!args.patches || args.patches.length === 0) {
        return { success: false, output: '', error: 'Patches array must not be empty' };
      }

      let totalHunks = 0;

      for (const patch of args.patches) {
        // Read the file
        const content = readFileSync(patch.path, 'utf-8');
        const lines = content.split('\n');
        const lineCount = lines.length;

        if (!patch.hunks || patch.hunks.length === 0) {
          return { success: false, output: '', error: `No hunks provided for ${patch.path}` };
        }

        // Sort hunks by start_line descending (apply from bottom to top)
        const sortedHunks = [...patch.hunks].sort((a, b) => b.start_line - a.start_line);

        // Validate: check for overlapping hunks
        for (let i = 0; i < sortedHunks.length; i++) {
          for (let j = i + 1; j < sortedHunks.length; j++) {
            if (hunksOverlap(sortedHunks[i], sortedHunks[j])) {
              return {
                success: false,
                output: '',
                error: `Overlapping hunks in ${patch.path}: lines ${sortedHunks[j].start_line}-${sortedHunks[j].end_line} and ${sortedHunks[i].start_line}-${sortedHunks[i].end_line}`,
              };
            }
          }
        }

        // Validate line numbers are within bounds
        for (const hunk of sortedHunks) {
          const isInsertion = hunk.end_line < hunk.start_line;

          if (isInsertion) {
            // For insertions, start_line must be between 1 and lineCount + 1
            // (inserting after the last line is valid)
            if (hunk.start_line < 1 || hunk.start_line > lineCount + 1) {
              return {
                success: false,
                output: '',
                error: `Line number out of bounds in ${patch.path}: start_line ${hunk.start_line} (file has ${lineCount} lines)`,
              };
            }
          } else {
            // For replacement/deletion, both start and end must be within [1, lineCount]
            if (hunk.start_line < 1 || hunk.start_line > lineCount) {
              return {
                success: false,
                output: '',
                error: `Line number out of bounds in ${patch.path}: start_line ${hunk.start_line} (file has ${lineCount} lines)`,
              };
            }
            if (hunk.end_line < 1 || hunk.end_line > lineCount) {
              return {
                success: false,
                output: '',
                error: `Line number out of bounds in ${patch.path}: end_line ${hunk.end_line} (file has ${lineCount} lines)`,
              };
            }
          }
        }

        // Apply hunks in reverse order (bottom to top)
        for (const hunk of sortedHunks) {
          const isInsertion = hunk.end_line < hunk.start_line;
          const isDeletion = !isInsertion && hunk.content === '';

          if (isInsertion) {
            // Insert new content lines at position start_line - 1
            const newLines = hunk.content.split('\n');
            lines.splice(hunk.start_line - 1, 0, ...newLines);
          } else if (isDeletion) {
            // Delete lines from start_line to end_line
            lines.splice(hunk.start_line - 1, hunk.end_line - hunk.start_line + 1);
          } else {
            // Replacement: splice out old lines and insert new content lines
            const newLines = hunk.content.split('\n');
            lines.splice(hunk.start_line - 1, hunk.end_line - hunk.start_line + 1, ...newLines);
          }
        }

        // Write back
        writeFileSync(patch.path, lines.join('\n'), 'utf-8');
        totalHunks += patch.hunks.length;
      }

      return {
        success: true,
        output: `Applied ${totalHunks} hunk(s) to ${args.patches.length} file(s)`,
      };
    } catch (error) {
      return { success: false, output: '', error: error instanceof Error ? error.message : String(error) };
    }
  },
};
