"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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var QuickEditHandler_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuickEditHandler = void 0;
const common_1 = require("@nestjs/common");
const fs = require("fs/promises");
const fsSync = require("fs");
const path = require("path");
const os = require("os");
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
const quick_edit_args_dto_1 = require("./dto/quick-edit.args.dto");
const utils_1 = require("../../utils");
const hybrid_matcher_1 = require("./matchers/hybrid.matcher");
const syntax_validation_service_1 = require("../../syntax-validation/syntax-validation.service");
const application_state_service_1 = require("../../application-state/application-state.service");
let QuickEditHandler = QuickEditHandler_1 = class QuickEditHandler {
    constructor(syntaxValidator, appStateService) {
        this.syntaxValidator = syntaxValidator;
        this.appStateService = appStateService;
        this.toolName = 'quick_edit';
        this.logger = new common_1.Logger(QuickEditHandler_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
        this.hybridMatcher = new hybrid_matcher_1.HybridMatcher();
        this.logDir = process.env.QUICK_EDIT_LOG_DIR ||
            path.join(os.homedir(), '.repoburg/quick-edit-logs');
    }
    getMetadata() {
        return {
            name: this.toolName,
            description: this.getDefinition(),
            arguments: [
                {
                    name: 'file_path',
                    type: 'string',
                    description: 'The relative path to file to be modified.',
                    required: true,
                },
                {
                    name: 'action',
                    type: 'string',
                    description: "The operation to perform. Allowed values: 'insert_after', 'insert_before', 'replace', 'delete_block'. Defaults to 'replace'.",
                    required: false,
                },
                {
                    name: 'search_block',
                    type: 'string',
                    description: 'A unique and exact block of code to locate the position for the edit.',
                    required: true,
                },
                {
                    name: 'new_content',
                    type: 'string',
                    description: "The new code to be added. Required for 'insert_after', 'insert_before', and 'replace'.",
                    required: false,
                },
            ],
        };
    }
    generateExample(toolCall) {
        return (0, utils_1.generateToolCallJson)(toolCall);
    }
    getDefinition() {
        const insertAfterExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/components/Header.tsx',
            action: 'insert_after',
            search_block: `import React from 'react';`,
            new_content: `import { useTheme } from '@emotion/react';`,
        });
        const replaceExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/config.ts',
            action: 'replace',
            search_block: `export const API_TIMEOUT = 5000;`,
            new_content: `export const API_TIMEOUT = 10000; // Increased timeout`,
        });
        const deleteExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/utils/legacy.js',
            action: 'delete_block',
            search_block: `
function oldUnusedFunction() {
  return 'hello';
}
      `.trim(),
        });
        const definition = `
-------------
### ${this.toolName}
  Makes small, targeted changes to a file by finding a unique block of text and inserting, replacing, or deleting it.

  **Best Practices:**
  - Include 3-5 lines surrounding code you want to edit
  - Use unique identifiers (variable names, function names) in your search block
  - Avoid selecting large blocks (>10 lines) - smaller is more reliable
  - Verify your search block matches the file exactly character-for-character
  - When code appears multiple times, include more surrounding context to disambiguate

  **Common Pitfalls:**
  - Search block with trailing characters the LLM added (e.g., "value;xyz")
  - Extra whitespace or indentation not present in file
  - Selecting only common patterns (imports, exports) that appear multiple times
  - Including code that was already edited in a previous action

  **When to use quick_edit vs overwrite_file:**
  - Use quick_edit for small, targeted changes (1-10 lines)
  - Use overwrite_file for large replacements, complete file rewrites, or when search block cannot be unique

  #### Parameters
  - "file_path": (string) The relative path to file to be modified.
  - "action": (enum, optional) The operation to perform. Allowed values: 'insert_after', 'insert_before', 'replace', 'delete_block'. Defaults to 'replace'.
  - "search_block": (string) A unique and exact block of code to locate the position for the edit.
  - "new_content": (string, optional) The new code to be added. Required for 'insert_after', 'insert_before', and 'replace'.

  #### Example: Insert After
    Explanation: Add an import statement after the React import.
${insertAfterExample}

  #### Example: Replace
    Explanation: Change the API timeout constant value.
${replaceExample}

  #### Example: Delete
    Explanation: Remove the old unused function.
${deleteExample}
-------------
`;
        return `\n${definition.trim()}\n`;
    }
    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;
    }
    normalizeArgs(args) {
        const normalized = { ...args };
        if (normalized.action === undefined || normalized.action === null) {
            normalized.action = 'replace';
        }
        const mappings = {
            old_content: 'search_block',
            search: 'search_block',
            target: 'search_block',
            replacement: 'new_content',
            content: 'new_content',
        };
        for (const [incorrect, correct] of Object.entries(mappings)) {
            if (incorrect in normalized && !(correct in normalized)) {
                normalized[correct] = normalized[incorrect];
                delete normalized[incorrect];
            }
        }
        return normalized;
    }
    formatNotFoundMessage(filePath, searchBlock) {
        return `File \`${filePath}\` not found. Use \`request_context\` to verify the file path and content.`;
    }
    formatErrorWithCandidates(filePath, searchBlock, matchResult) {
        if (!matchResult.candidates || matchResult.candidates.length === 0) {
            return `Search block not found in \`${filePath}\`. Use \`request_context\` to read the file and verify content.`;
        }
        const count = matchResult.candidates.length;
        return `Found **${count} possible matches** for your search block in \`${filePath}\`. Include more surrounding lines or use \`request_context\` to uniquely identify the correct location.`;
    }
    async writeMatchFailureLog(action, searchBlock, filePath, fileContent) {
        try {
            await fsSync.promises.mkdir(this.logDir, { recursive: true });
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const logFileName = `quick-edit-failure-${timestamp}.txt`;
            const logFilePath = path.join(this.logDir, logFileName);
            const logContent = `Timestamp: ${new Date().toISOString()}
Action: ${action}
FilePath: ${filePath}

Search Block:
${searchBlock}

File Content Before Modification:
${fileContent}`;
            await fsSync.promises.writeFile(logFilePath, logContent, 'utf8');
        }
        catch (error) {
            this.logger.error(`Failed to write match failure log: ${error.message}`);
        }
    }
    async execute(args, _context) {
        const normalizedArgs = this.normalizeArgs(args);
        const validatedArgs = (0, class_transformer_1.plainToClass)(quick_edit_args_dto_1.QuickEditArgsDto, normalizedArgs);
        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, action, search_block, new_content } = 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 = this.formatNotFoundMessage(file_path, search_block);
                return {
                    status: 'FAILURE',
                    summary: `Quick edit on "${file_path}" failed: File not found.`,
                    error_message: errorMessage,
                    persisted_args: validatedArgs,
                    execution_log: { output: '', error_message: errorMessage },
                };
            }
            const matchResult = this.hybridMatcher.findBestMatch(search_block, originalContent);
            if (!matchResult.found || !matchResult.unique) {
                await this.writeMatchFailureLog(action, search_block, file_path, originalContent);
                const errorMessage = this.formatErrorWithCandidates(file_path, search_block, matchResult);
                return {
                    status: 'FAILURE',
                    summary: `Quick edit on "${file_path}" failed: Match issue.`,
                    error_message: errorMessage,
                    persisted_args: validatedArgs,
                    execution_log: { output: '', error_message: errorMessage },
                    original_content_for_revert: originalContent,
                };
            }
            let newFileContent;
            const { before, after } = matchResult;
            switch (action) {
                case 'replace':
                    newFileContent = `${before}${new_content}${after}`;
                    break;
                case 'insert_after':
                    newFileContent = `${before}${search_block}\n${new_content}${after}`;
                    break;
                case 'insert_before':
                    newFileContent = `${before}${new_content}\n${search_block}${after}`;
                    break;
                case 'delete_block':
                    newFileContent = `${before}${after}`;
                    break;
            }
            if (newFileContent === originalContent) {
                const noChangeMessage = `The quick_edit operation on "${file_path}" resulted in no changes. The new content is identical to the original content. This typically means your edit didn't change anything (e.g., replacing with the same content, or inserting duplicate content). Please review your edit and ensure you're making the intended changes.`;
                return {
                    status: 'FAILURE',
                    summary: `Quick edit on "${file_path}" produced no changes.`,
                    error_message: noChangeMessage,
                    persisted_args: validatedArgs,
                    execution_log: { output: '', error_message: noChangeMessage },
                    original_content_for_revert: originalContent,
                };
            }
            await fs.writeFile(safePath, newFileContent, 'utf8');
            if (this.syntaxValidator && this.appStateService) {
                const isValidationEnabled = await this.appStateService.getSyntaxValidationEnabled();
                if (isValidationEnabled) {
                    const validationError = await this.syntaxValidator.validate(file_path, newFileContent);
                    if (validationError) {
                        this.logger.warn(`Syntax validation failed for quick edit on "${file_path}"`);
                        let errorMessage = this.syntaxValidator.formatErrors(validationError);
                        errorMessage += `\n**CORRECTION:** Your \`quick_edit\` introduced syntax errors. The original file content has been restored. `;
                        errorMessage += `Please ensure your edit maintains syntactically valid code. `;
                        errorMessage += `Common issues: missing closing braces in inserted code, mismatched quotes, or broken imports. `;
                        errorMessage += `Use \`request_context\` to verify the file state and try again with a corrected \`search_block\` or \`new_content\`.`;
                        if (originalContent !== null) {
                            await fs.writeFile(safePath, originalContent, 'utf8');
                        }
                        else {
                            await fs.unlink(safePath).catch(() => { });
                        }
                        return {
                            status: 'FAILURE',
                            summary: `File "${file_path}" contains syntax errors after quick_edit and was reverted.`,
                            error_message: errorMessage,
                            persisted_args: validatedArgs,
                            execution_log: { output: '', error_message: errorMessage },
                            original_content_for_revert: originalContent,
                        };
                    }
                }
            }
            return {
                status: 'SUCCESS',
                summary: `File "${file_path}" successfully modified with quick_edit.`,
                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 write file for quick edit at ${file_path}: ${error.message}`);
            return {
                status: 'FAILURE',
                summary: `Failed to quick_edit file "${file_path}": ${error.message}`,
                error_message: error.message,
                persisted_args: validatedArgs,
                execution_log: { output: '', error_message: error.message },
            };
        }
    }
};
exports.QuickEditHandler = QuickEditHandler;
exports.QuickEditHandler = QuickEditHandler = QuickEditHandler_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Optional)()),
    __param(1, (0, common_1.Optional)()),
    __metadata("design:paramtypes", [syntax_validation_service_1.SyntaxValidationService,
        application_state_service_1.ApplicationStateService])
], QuickEditHandler);
//# sourceMappingURL=quick-edit.handler.js.map