"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var ApplyDiffHandler_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApplyDiffHandler = void 0;
const common_1 = require("@nestjs/common");
const fs = require("fs/promises");
const path = require("path");
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
const apply_diff_args_dto_1 = require("./dto/apply-diff.args.dto");
const utils_1 = require("../../utils");
let ApplyDiffHandler = ApplyDiffHandler_1 = class ApplyDiffHandler {
    constructor() {
        this.toolName = 'apply_diff';
        this.logger = new common_1.Logger(ApplyDiffHandler_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
    }
    getMetadata() {
        return {
            name: this.toolName,
            description: this.getDefinition(),
            arguments: [
                {
                    name: 'file_path',
                    type: 'string',
                    description: 'The relative path to the file to be modified.',
                    required: true,
                },
                {
                    name: 'patch',
                    type: 'string',
                    description: 'A string containing one or more diff commands with anchors.',
                    required: true,
                },
            ],
        };
    }
    generateExample(toolCall) {
        return (0, utils_1.generateToolCallJson)(toolCall);
    }
    getDefinition() {
        const examplePatch = `
@2,2c|ANCHOR=console.log('Hello, world!');
  console.log('Hello, Repoburg!');
@5,7d|ANCHOR=function farewell() {
`.trim();
        const example = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/example.js',
            patch: examplePatch,
        });
        const definition = `
::<${this.toolName}>
  Applies a patch to a file using a line-based diff format with anchor validation.
  NOTE: This is a legacy tool. Prefer using the more robust 'patch' tool for all modifications.

  The patch format uses commands starting with '@' to specify changes.
  Command syntax: \`@<start_line>,<end_line><operation>|ANCHOR=<substring>\`
  - <start_line>,<end_line>: 1-based line numbers.
  - <operation>: 'a' (add after), 'c' (change), 'd' (delete).
  - |ANCHOR=<substring>: A mandatory substring that must be present on the <start_line> to validate the change.

  Parameters:
  - "file_path": (string) The relative path to the file to be modified.
  - "patch": (string) A string containing one or more diff commands with anchors.

  <example>
    Explanation: This patch changes line 2 and deletes lines 5-7 from 'src/example.js'.
:${example}
  </example>
::</${this.toolName}>
`;
        return definition.trim();
    }
    resolveAndValidatePath(unsafePath) {
        const normalizedPath = path.normalize(unsafePath);
        if (path.isAbsolute(normalizedPath)) {
            throw new common_1.BadRequestException(`Absolute paths are not allowed: ${unsafePath}`);
        }
        const resolvedPath = path.resolve(this.projectRoot, normalizedPath);
        if (!resolvedPath.startsWith(this.projectRoot)) {
            throw new common_1.BadRequestException(`Path traversal is not allowed: ${unsafePath}`);
        }
        return resolvedPath;
    }
    parsePatch(patch) {
        const changes = [];
        const lines = patch.split('\n');
        const commandRegex = /^@(\d+),(\d+)([acd])\|ANCHOR=(.*)$/;
        let i = 0;
        while (i < lines.length) {
            const line = lines[i];
            if (!line.startsWith('@')) {
                if (line.trim()) {
                    this.logger.warn(`Skipping invalid line in patch: "${line}"`);
                }
                i++;
                continue;
            }
            const match = line.match(commandRegex);
            if (match) {
                const startLine = parseInt(match[1], 10);
                const endLine = parseInt(match[2], 10);
                const operation = match[3];
                const anchor = match[4];
                const contentLines = [];
                i++;
                if (operation === 'a' || operation === 'c') {
                    while (i < lines.length && !lines[i].startsWith('@')) {
                        contentLines.push(lines[i]);
                        i++;
                    }
                }
                changes.push({
                    startLine,
                    endLine,
                    operation,
                    content: contentLines,
                    anchor,
                });
            }
            else {
                this.logger.warn(`Skipping malformed command in patch: "${line}"`);
                i++;
            }
        }
        return changes;
    }
    applyChanges(originalContent, changes) {
        const lines = originalContent ? originalContent.split('\n') : [];
        changes.sort((a, b) => b.startLine - a.startLine);
        for (const change of changes) {
            if (change.operation === 'a' && change.startLine !== change.endLine) {
                throw new common_1.BadRequestException(`For 'add' operations, start_line must equal end_line. Got ${change.startLine},${change.endLine}.`);
            }
            const startIndex = change.startLine - 1;
            if (change.operation === 'a') {
                if (change.startLine < 0 || change.startLine > lines.length) {
                    throw new common_1.BadRequestException(`Invalid line number ${change.startLine} in patch for file with ${lines.length} lines.`);
                }
            }
            else {
                if (startIndex < 0 ||
                    startIndex >= lines.length ||
                    change.endLine > lines.length) {
                    throw new common_1.BadRequestException(`Invalid line number range ${change.startLine},${change.endLine} in patch for file with ${lines.length} lines.`);
                }
            }
            if (change.operation === 'a' && change.startLine === 0) {
                if (lines.length > 0 && !(lines.length === 1 && lines[0] === '')) {
                    throw new common_1.BadRequestException(`Invalid 'add at line 0' for a non-empty file.`);
                }
                if (change.anchor !== '') {
                    throw new common_1.BadRequestException(`Anchor for 'add at line 0' must be empty, but got '${change.anchor}'.`);
                }
            }
            else {
                const anchorLineIndex = startIndex;
                if (anchorLineIndex < 0 || anchorLineIndex >= lines.length) {
                    throw new common_1.BadRequestException(`Anchor validation failed: line number ${anchorLineIndex + 1} is out of bounds for anchor '${change.anchor}'.`);
                }
                const anchorLine = lines[anchorLineIndex];
                const isValidAnchor = change.anchor === ''
                    ? anchorLine === ''
                    : anchorLine.includes(change.anchor);
                if (!isValidAnchor) {
                    throw new common_1.BadRequestException(`Anchor validation failed at line ${anchorLineIndex + 1}: expected to find '${change.anchor}', but found '${anchorLine}'.`);
                }
            }
            switch (change.operation) {
                case 'a':
                    lines.splice(change.startLine, 0, ...change.content);
                    break;
                case 'c':
                    const deleteCountC = change.endLine - change.startLine + 1;
                    lines.splice(startIndex, deleteCountC, ...change.content);
                    break;
                case 'd':
                    const deleteCountD = change.endLine - change.startLine + 1;
                    lines.splice(startIndex, deleteCountD);
                    break;
            }
        }
        return lines.join('\n');
    }
    async execute(args, _context) {
        const validatedArgs = (0, class_transformer_1.plainToClass)(apply_diff_args_dto_1.ApplyDiffArgsDto, args);
        const errors = await (0, class_validator_1.validate)(validatedArgs);
        if (errors.length > 0) {
            const errorMessages = errors
                .map((err) => Object.values(err.constraints || {}).join(', '))
                .join('; ');
            return {
                status: 'FAILURE',
                summary: `Invalid arguments for ${this.toolName}.`,
                error_message: errorMessages,
                persisted_args: args,
                execution_log: { output: '', error_message: errorMessages },
            };
        }
        const { file_path, patch } = validatedArgs;
        try {
            const safePath = this.resolveAndValidatePath(file_path);
            const originalContent = await fs
                .readFile(safePath, 'utf8')
                .catch((error) => {
                if (error.code === 'ENOENT') {
                    return null;
                }
                throw error;
            });
            if (originalContent === null) {
                const errorMessage = `File "${file_path}" not found.`;
                return {
                    status: 'FAILURE',
                    summary: `Apply diff on "${file_path}" failed: File not found.`,
                    error_message: errorMessage,
                    persisted_args: validatedArgs,
                    execution_log: { output: '', error_message: errorMessage },
                };
            }
            const changes = this.parsePatch(patch);
            const newFileContent = this.applyChanges(originalContent, changes);
            await fs.writeFile(safePath, newFileContent, 'utf8');
            return {
                status: 'SUCCESS',
                summary: `File "${file_path}" successfully modified with apply_diff.`,
                persisted_args: { ...validatedArgs, content: newFileContent },
                original_content_for_revert: originalContent,
                execution_log: {
                    output: `${file_path} has been modified.`,
                    error_message: '',
                },
            };
        }
        catch (error) {
            this.logger.error(`Failed to apply diff for ${file_path}: ${error.message}`);
            let errorMessage = `Failed to apply diff for "${file_path}": ${error.message}`;
            if (error instanceof common_1.BadRequestException &&
                error.message.startsWith('Anchor validation failed')) {
                errorMessage += `\n\n**CORRECTION HINT:** The file content has changed. Use \`request_context\` on \`'${file_path}'\` to get the latest version.`;
            }
            return {
                status: 'FAILURE',
                summary: `Apply diff on "${file_path}" failed: ${error.message}`,
                error_message: errorMessage,
                persisted_args: validatedArgs,
                execution_log: { output: '', error_message: errorMessage },
            };
        }
    }
};
exports.ApplyDiffHandler = ApplyDiffHandler;
exports.ApplyDiffHandler = ApplyDiffHandler = ApplyDiffHandler_1 = __decorate([
    (0, common_1.Injectable)()
], ApplyDiffHandler);
//# sourceMappingURL=apply-diff.handler.js.map