"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 PatchHandler_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PatchHandler = void 0;
const common_1 = require("@nestjs/common");
const fs = require("fs/promises");
const fs_1 = require("fs");
const path = require("path");
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
const patch_args_dto_1 = require("./dto/patch.args.dto");
const tokenpatch_1 = require("../../../packages/tokenpatch");
const utils_1 = require("../../utils");
const parsing_constants_1 = require("../parser/parsing.constants");
const syntax_validation_service_1 = require("../../syntax-validation/syntax-validation.service");
const application_state_service_1 = require("../../application-state/application-state.service");
const ENABLE_TREE_SITTER = false;
const grammarMap = {
    '.js': 'tree-sitter-javascript.wasm',
    '.jsx': 'tree-sitter-javascript.wasm',
    '.ts': 'tree-sitter-typescript.wasm',
    '.tsx': 'tree-sitter-tsx.wasm',
    '.json': 'tree-sitter-json.wasm',
    '.html': 'tree-sitter-html.wasm',
    '.css': 'tree-sitter-css.wasm',
    '.py': 'tree-sitter-python.wasm',
    '.rs': 'tree-sitter-rust.wasm',
    '.toml': 'tree-sitter-toml.wasm',
    '.yaml': 'tree-sitter-yaml.wasm',
    '.yml': 'tree-sitter-yaml.wasm',
    '.sh': 'tree-sitter-bash.wasm',
};
let PatchHandler = PatchHandler_1 = class PatchHandler {
    constructor(syntaxValidator, appStateService) {
        this.syntaxValidator = syntaxValidator;
        this.appStateService = appStateService;
        this.toolName = 'patch';
        this.logger = new common_1.Logger(PatchHandler_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
        this.grammarDir = (() => {
            const prodPath = path.resolve(__dirname, '../../../bin/grammar/');
            if ((0, fs_1.existsSync)(prodPath)) {
                return prodPath;
            }
            const devPath = path.resolve(__dirname, '../../bin/grammar/');
            if ((0, fs_1.existsSync)(devPath)) {
                return devPath;
            }
            console.warn(`[PatchHandler] Grammar directory not found. Looked for ${prodPath} and ${devPath}. The patch tool may not function correctly.`);
            return prodPath;
        })();
    }
    getMetadata() {
        return {
            name: this.toolName,
            description: this.getDefinition(),
            arguments: [
                {
                    name: 'file_path',
                    type: 'string',
                    description: 'The relative path to the file to be patched.',
                    required: true,
                },
                {
                    name: 'patch_code',
                    type: 'string',
                    description: 'A code snippet containing your changes, with enough surrounding context to serve as a unique anchor.',
                    required: true,
                },
            ],
        };
    }
    generateExample(toolCall) {
        return (0, utils_1.generateToolCallJson)(toolCall);
    }
    getDefinition() {
        const genericExample = `\`\`\`typescript
// ... 3-4 lines (or ~20+ tokens) of existing code (Unique Prefix Anchor) ...
// ... THE CODE YOU WANT TO CHANGE OR INSERT ...
// ... 3-4 lines (or ~20+ tokens) of existing code (Unique Suffix Anchor) ...
\`\`\``;
        const goodAnchorExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/modules/auth/auth.service.ts',
            patch_code: `\`\`\`typescript
    async validateUser(username: string, pass: string): Promise<any> {
      const user = await this.usersService.findOne(username);
      if (user && user.password === pass) {
        const { password, ...result } = user;
        return result;
      }
      return null;
    }

    async login(user: any) {
      // MODIFIED: Include roles in JWT payload
      const payload = { 
        username: user.username, 
        sub: user.userId,
        roles: user.roles || ['user']
      };
      
      return {
        access_token: this.jwtService.sign(payload),
      };
    }

    async register(user: CreateUserDto) {
      return this.usersService.create(user);
    }
\`\`\``,
        });
        const badAnchorExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/utils/formatting.ts',
            patch_code: `\`\`\`typescript
  // MODIFIED: Handle non-string inputs gracefully
  if (typeof str !== 'string') return String(str);
  return str.charAt(0).toUpperCase() + str.slice(1);
\`\`\``,
        });
        const appendExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/services/auth.service.ts',
            patch_code: `\`\`\`typescript
  async validateSession(token: string): Promise<boolean> {
    return this.tokenService.verify(token);
  }
}

// Helper for legacy clients
export function isLegacyToken(token: string): boolean {
  return token.startsWith('leg_');
}
// ${parsing_constants_1.SPECIAL_PATCH_END_FILE_MARKER}
\`\`\``,
        });
        const prependExample = this.generateExample({
            tool_name: this.toolName,
            file_path: 'src/main.ts',
            patch_code: `\`\`\`typescript
// ${parsing_constants_1.SPECIAL_PATCH_BEGIN_FILE_MARKER}
import 'dotenv/config';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

// Global types
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      PORT?: string;
    }
  }
}

async function bootstrap() {
\`\`\``,
        });
        const definition = `
-------------
### ${this.toolName}
  Applies an intelligent, context-aware patch to a code file. This is the most robust and preferred method for modifying code.
  
  The algorithm works by treating your 'patch_code' as a snippet of the file's **final desired state**. It finds a unique location in the original file that matches the prefix and suffix of your snippet and replaces the content between them.

  #### Critical Rules
  1.  **The 'patch_code' is a literal replacement**: The entire content of your 'patch_code' will replace the section of the original file that it matches. It is not a diff or a partial change.
  2.  **Provide valid, parseable code**: The snippet in 'patch_code' must be syntactically correct. Do not provide incomplete lines or half-finished code blocks, as the parser will fail.
  3.  **Include unique anchors**: Your 'patch_code' MUST include enough surrounding, unchanged context (a few lines before and after your change) to serve as a unique anchor. If the context is not unique, the patch will fail.
  4.  **Small, atomic patches**: Prefer multiple small patches over one large patch. Large patches are harder to anchor uniquely. You can send multiple patch actions for the same file in a single turn.

  #### Special Markers for Edge Cases
  Standard anchoring requires unique context both above and below the change. This is impossible at the very start or end of a file, OR when the surrounding code is too short (e.g., only a closing brace \`}\` follows your change).
  Use these markers to tell the patcher to skip the missing anchor:
  - \`// ${parsing_constants_1.SPECIAL_PATCH_BEGIN_FILE_MARKER}\`: Use this as the first line of 'patch_code' when adding imports or top-level definitions to the START of a file.
  - \`// ${parsing_constants_1.SPECIAL_PATCH_END_FILE_MARKER}\`: Use this as the last line of 'patch_code' when appending new functions or exports to the END of a file.

  #### Absolutely Forbidden
  - Unified diff style (lines starting with + or -). Providing this will cause the patch to fail.
  - Ellipses (...) or comments like '// ... rest of code' in place of required syntax.

  #### Parameters
  - "file_path": (string) The relative path to the file to be patched.
  - "patch_code": (string) A code snippet containing your changes, with enough surrounding context to serve as a unique anchor.

  #### Conceptual Example
    Explanation: Your patch code must follow this structure. The context anchors are crucial for locating the correct insertion point.
${genericExample}

  #### Example: Good Anchoring
    Explanation: Notice how the patch includes the constructor and 'validateUser' method above, and the start of the 'register' method below as anchors. This guarantees the patch location is unique.
    Original File Content:
    \`\`\`typescript
    @Injectable()
    export class AuthService {
      constructor(
        private readonly usersService: UsersService,
        private readonly jwtService: JwtService,
      ) {}

      async validateUser(username: string, pass: string): Promise<any> {
        const user = await this.usersService.findOne(username);
        if (user && user.password === pass) {
          const { password, ...result } = user;
          return result;
        }
        return null;
      }

      async login(user: any) {
        const payload = { username: user.username, sub: user.userId };
        return {
          access_token: this.jwtService.sign(payload),
        };
      }

      async register(user: CreateUserDto) {
        return this.usersService.create(user);
      }
      
      async forgotPassword(user: CreateUserDto) {
        return this.usersService.forgotPassword(user);
      }
    }
    \`\`\`
${goodAnchorExample}
  
  #### Example: Bad Anchoring
    Explanation: This is a **bad example**. The 'patch_code' contains NO surrounding context. The patcher has no way of knowing *where* in the file this code belongs, or what it is replacing. This will result in an error.
    Original File Content:
    \`\`\`typescript
    export function formatDate(date: Date): string {
      return date.toISOString();
    }

    export function capitalize(str: string): string {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }

    export function formatCurrency(amount: number): string {
      return '$' + amount.toFixed(2);
    }
    \`\`\`
${badAnchorExample}

  #### Example: Append
    Explanation: Use the '// ${parsing_constants_1.SPECIAL_PATCH_END_FILE_MARKER}' marker to add content to the end of a file.
    Original File Content:
    \`\`\`typescript
    export class AuthService {
      // ... other methods ...
      async validateSession(token: string): Promise<boolean> {
        return this.tokenService.verify(token);
      }
    }
    \`\`\`
${appendExample}

  #### Example: Prepend
    Explanation: Use the '// ${parsing_constants_1.SPECIAL_PATCH_BEGIN_FILE_MARKER}' marker to add content to the beginning of a file.
    Original File Content:
    \`\`\`typescript
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';

    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      await app.listen(3000);
    }
    bootstrap();
    \`\`\`
${prependExample}
-------------
`;
        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;
    }
    shouldUseFullReplacementStrategy(patchCode) {
        const beginMarkerRegex = new RegExp(`//\\s*${parsing_constants_1.SPECIAL_PATCH_BEGIN_FILE_MARKER}.*`);
        const endMarkerRegex = new RegExp(`//\\s*${parsing_constants_1.SPECIAL_PATCH_END_FILE_MARKER}.*`);
        return beginMarkerRegex.test(patchCode) && endMarkerRegex.test(patchCode);
    }
    async handleFullFileReplacement(filePath, safePath, patchCode, originalContent, validatedArgs) {
        this.logger.log(`Both begin/end markers detected for ${filePath}. Executing full file replacement strategy.`);
        const beginMarkerRegex = new RegExp(`//\\s*${parsing_constants_1.SPECIAL_PATCH_BEGIN_FILE_MARKER}.*`);
        const endMarkerRegex = new RegExp(`//\\s*${parsing_constants_1.SPECIAL_PATCH_END_FILE_MARKER}.*`);
        const newFileContent = patchCode
            .replace(beginMarkerRegex, '')
            .replace(endMarkerRegex, '')
            .trim();
        await fs.writeFile(safePath, newFileContent, 'utf8');
        return {
            status: 'SUCCESS',
            summary: `File "${filePath}" completely overwritten (detected both start/end markers).`,
            persisted_args: { ...validatedArgs, content: newFileContent },
            original_content_for_revert: originalContent,
            execution_log: {
                output: `${filePath} has been overwritten.`,
                error_message: '',
            },
        };
    }
    async execute(args, _context) {
        const validatedArgs = (0, class_transformer_1.plainToClass)(patch_args_dto_1.PatchArgsDto, 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_code } = validatedArgs;
        try {
            const safePath = this.resolveAndValidatePath(file_path);
            const fileExtension = path.extname(file_path);
            const grammarFile = grammarMap[fileExtension];
            let applyPatchOptions = {};
            if (grammarFile && ENABLE_TREE_SITTER) {
                const grammarPath = path.join(this.grammarDir, grammarFile);
                applyPatchOptions = { grammarPath };
            }
            else {
                this.logger.log(`No Tree-sitter grammar found for ${fileExtension}, using Tiktoken strategy.`);
                applyPatchOptions = { useTiktoken: true };
            }
            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: `Patch on "${file_path}" failed: File not found.`,
                    error_message: errorMessage,
                    persisted_args: validatedArgs,
                    execution_log: { output: '', error_message: errorMessage },
                };
            }
            if (this.shouldUseFullReplacementStrategy(patch_code)) {
                return this.handleFullFileReplacement(file_path, safePath, patch_code, originalContent, validatedArgs);
            }
            const newFileContent = await (0, tokenpatch_1.applySnippetPatch)(originalContent, patch_code, applyPatchOptions);
            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 patched file "${file_path}"`);
                        let errorMessage = this.syntaxValidator.formatErrors(validationError);
                        errorMessage += `\n**CORRECTION:** Your \`patch\` introduced syntax errors. The original file has been restored. `;
                        errorMessage += `Your \`patch_code\` must be syntactically correct and properly formatted. `;
                        errorMessage += `Ensure you include sufficient context (unchanged lines before/after your changes) to anchor the patch uniquely. `;
                        errorMessage += `Use \`request_context\` to get the current file state and try again with corrected \`patch_code\`.`;
                        await fs.writeFile(safePath, originalContent, 'utf8');
                        return {
                            status: 'FAILURE',
                            summary: `File "${file_path}" contains syntax errors after patch and was reverted.`,
                            error_message: errorMessage,
                            persisted_args: validatedArgs,
                            execution_log: { output: '', error_message: errorMessage },
                        };
                    }
                }
            }
            return {
                status: 'SUCCESS',
                summary: `File "${file_path}" successfully modified with patch.`,
                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 patch for ${file_path}: ${error.message}`);
            const errorMessage = `Failed to apply patch for "${file_path}": ${error.message}\n\n**CORRECTION HINT:** The patch could not be applied. This is often because prefix or suffix anchors in patch code could not be uniquely located in the original file. Use \`request_context\` on \`'${file_path}'\` to get the latest version and try again with a more specific patch. If the patch continues to fail, consider using \`overwrite_file\` to replace the entire file content.`;
            return {
                status: 'FAILURE',
                summary: `Patch on "${file_path}" failed: ${error.message}`,
                error_message: errorMessage,
                persisted_args: validatedArgs,
                execution_log: { output: '', error_message: errorMessage },
            };
        }
    }
};
exports.PatchHandler = PatchHandler;
exports.PatchHandler = PatchHandler = PatchHandler_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])
], PatchHandler);
//# sourceMappingURL=patch.handler.js.map