"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 AIActionsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIActionsService = exports.AIActionType = exports.AIActionStatus = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const fs = require("fs/promises");
const path = require("path");
const core_entities_1 = require("../core-entities");
const execution_logs_service_1 = require("../execution-logs/execution-logs.service");
const utils_1 = require("../utils");
const child_process_1 = require("child_process");
const util_1 = require("util");
const crypto = require("crypto");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
var AIActionStatus;
(function (AIActionStatus) {
    AIActionStatus["PROPOSED"] = "proposed";
    AIActionStatus["AWAITING_CONTEXT"] = "awaiting_context";
    AIActionStatus["AWAITING_HANDOVER"] = "awaiting_handover";
    AIActionStatus["APPROVED_FOR_APPLY"] = "approved_for_apply";
    AIActionStatus["REJECTED_BEFORE_APPLY"] = "rejected_before_apply";
    AIActionStatus["APPLIED_PENDING_REVIEW"] = "applied_pending_review";
    AIActionStatus["CONFIRMED_KEPT"] = "confirmed_kept";
    AIActionStatus["CONFIRMED_REVERTED"] = "confirmed_reverted";
    AIActionStatus["EXECUTION_FAILED"] = "execution_failed";
    AIActionStatus["REVERT_FAILED"] = "revert_failed";
})(AIActionStatus || (exports.AIActionStatus = AIActionStatus = {}));
var AIActionType;
(function (AIActionType) {
    AIActionType["CREATE_FILE"] = "create_file";
    AIActionType["OVERWRITE_FILE"] = "overwrite_file";
    AIActionType["DELETE_FILE"] = "delete_file";
    AIActionType["RUN_COMMAND"] = "run_command";
    AIActionType["REQUEST_CONTEXT"] = "request_context";
    AIActionType["FINAL"] = "final";
    AIActionType["QUICK_EDIT"] = "quick_edit";
    AIActionType["APPLY_DIFF"] = "apply_diff";
    AIActionType["PATCH"] = "patch";
    AIActionType["NEW_SESSION"] = "new_session";
    AIActionType["WRITE_TODO"] = "write_todo";
    AIActionType["EXECUTE_CODE"] = "execute_code";
})(AIActionType || (exports.AIActionType = AIActionType = {}));
let AIActionsService = AIActionsService_1 = class AIActionsService {
    constructor(aiActionsRepository, sessionInputsRepository, executionLogsService) {
        this.aiActionsRepository = aiActionsRepository;
        this.sessionInputsRepository = sessionInputsRepository;
        this.executionLogsService = executionLogsService;
        this.logger = new common_1.Logger(AIActionsService_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
    }
    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)) {
            this.logger.warn(`Path traversal attempt detected: ${unsafePath}`);
            throw new common_1.BadRequestException(`Path traversal is not allowed. Access denied for path: ${unsafePath}`);
        }
        return resolvedPath;
    }
    async createFile(filePath, content) {
        const safePath = this.resolveAndValidatePath(filePath);
        const dir = path.dirname(safePath);
        try {
            await fs.mkdir(dir, { recursive: true });
            await fs.writeFile(safePath, content, 'utf8');
            this.logger.log(`Created file: ${safePath}`);
        }
        catch (error) {
            this.logger.error(`Error creating file ${safePath}: ${error.message}`);
            throw new common_1.InternalServerErrorException(`Failed to create file "${filePath}": ${error.message}`);
        }
    }
    async editFile(filePath, newContent) {
        const safePath = this.resolveAndValidatePath(filePath);
        try {
            const dir = path.dirname(safePath);
            await fs.mkdir(dir, { recursive: true });
            await fs.writeFile(safePath, newContent, 'utf8');
            this.logger.log(`Edited file: ${safePath}`);
        }
        catch (error) {
            this.logger.error(`Error editing file ${safePath}: ${error.message}`);
            throw new common_1.InternalServerErrorException(`Failed to edit file "${filePath}": ${error.message}`);
        }
    }
    async deleteFile(filePath) {
        const safePath = this.resolveAndValidatePath(filePath);
        try {
            await fs.access(safePath);
            await fs.unlink(safePath);
            this.logger.log(`Deleted file: ${safePath}`);
        }
        catch (error) {
            this.logger.error(`Error deleting file ${safePath}: ${error.message}`);
            if (error.code === 'ENOENT') {
                throw new common_1.NotFoundException(`File not found, cannot delete: "${filePath}"`);
            }
            throw new common_1.InternalServerErrorException(`Failed to delete file "${filePath}": ${error.message}`);
        }
    }
    async findAllByInputId(inputId) {
        const sessionInput = await this.sessionInputsRepository.findOneBy({
            id: inputId,
        });
        if (!sessionInput) {
            throw new common_1.NotFoundException(`SessionInput with ID "${inputId}" not found. Cannot retrieve actions.`);
        }
        return this.aiActionsRepository.find({
            where: { input_id: inputId },
            order: { order_of_execution: 'ASC', created_at: 'ASC' },
            relations: ['executionLogs'],
        });
    }
    async findOne(actionId) {
        const action = await this.aiActionsRepository.findOne({
            where: { id: actionId },
            relations: ['executionLogs'],
        });
        if (!action) {
            throw new common_1.NotFoundException(`AIAction with ID "${actionId}" not found`);
        }
        return action;
    }
    async approveAction(actionId) {
        const action = await this.findOne(actionId);
        if (action.status !== AIActionStatus.PROPOSED) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" cannot be approved. Status is "${action.status}", but must be "${AIActionStatus.PROPOSED}".`);
        }
        action.status = AIActionStatus.APPROVED_FOR_APPLY;
        return this.aiActionsRepository.save(action);
    }
    async discardAction(actionId) {
        const action = await this.findOne(actionId);
        if (action.status !== AIActionStatus.PROPOSED &&
            action.status !== AIActionStatus.APPROVED_FOR_APPLY) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" cannot be discarded. Status is "${action.status}", but must be "${AIActionStatus.PROPOSED}" or "${AIActionStatus.APPROVED_FOR_APPLY}".`);
        }
        action.status = AIActionStatus.REJECTED_BEFORE_APPLY;
        return this.aiActionsRepository.save(action);
    }
    async confirmKeepAction(actionId) {
        const action = await this.findOne(actionId);
        if (action.status !== AIActionStatus.APPLIED_PENDING_REVIEW) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" cannot be confirmed. Status is "${action.status}", but must be "${AIActionStatus.APPLIED_PENDING_REVIEW}".`);
        }
        action.status = AIActionStatus.CONFIRMED_KEPT;
        this.logger.log(`Action ID ${actionId} confirmed kept. Status: ${action.status}`);
        return this.aiActionsRepository.save(action);
    }
    async revertAction(actionId) {
        const action = await this.findOne(actionId);
        if (action.status !== AIActionStatus.APPLIED_PENDING_REVIEW) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" cannot be reverted. Status is "${action.status}", but must be "${AIActionStatus.APPLIED_PENDING_REVIEW}".`);
        }
        let revertSucceeded = false;
        let revertOutcomeMessage = `Reverting action type: ${action.action_type}`;
        let errorDetails = undefined;
        try {
            this.logger.log(`Attempting to revert action ID ${action.id}, type ${action.action_type}`);
            switch (action.action_type) {
                case 'quick_edit':
                    if (!action.file_path) {
                        throw new Error('Missing file_path for quick_edit revert.');
                    }
                    if (action.original_content_for_revert === null) {
                        throw new Error('Invalid state: quick_edit action is missing original content for revert.');
                    }
                    await this.editFile(action.file_path, action.original_content_for_revert);
                    revertOutcomeMessage = `File "${action.file_path}" content reverted from quick_edit.`;
                    revertSucceeded = true;
                    break;
                case 'overwrite_file':
                case 'apply_diff':
                case 'patch':
                case 'write_todo':
                    if (!action.file_path)
                        throw new Error(`Missing file_path for ${action.action_type} revert.`);
                    if (action.original_content_for_revert !== null) {
                        await this.editFile(action.file_path, action.original_content_for_revert);
                        revertOutcomeMessage = `File "${action.file_path}" content reverted to original.`;
                    }
                    else {
                        await this.deleteFile(action.file_path);
                        revertOutcomeMessage = `File "${action.file_path}" (created by ${action.action_type}) was deleted.`;
                    }
                    revertSucceeded = true;
                    break;
                case 'delete_file':
                    if (!action.file_path)
                        throw new Error('Missing file_path for delete_file revert.');
                    if (action.original_content_for_revert !== null) {
                        await this.createFile(action.file_path, action.original_content_for_revert);
                        revertOutcomeMessage = `Deleted file "${action.file_path}" restored with original content.`;
                    }
                    else {
                        try {
                            await this.deleteFile(action.file_path);
                            revertOutcomeMessage = `Ensured file "${action.file_path}" (originally non-existent or empty) remains deleted.`;
                        }
                        catch (e) {
                            if (e instanceof common_1.NotFoundException) {
                                revertOutcomeMessage = `File "${action.file_path}" was already non-existent as expected for revert.`;
                            }
                            else {
                                throw e;
                            }
                        }
                    }
                    revertSucceeded = true;
                    break;
                case 'create_file':
                    if (!action.file_path)
                        throw new Error('Missing file_path for create_file revert.');
                    try {
                        await this.deleteFile(action.file_path);
                        revertOutcomeMessage = `Created file "${action.file_path}" deleted.`;
                    }
                    catch (e) {
                        if (e instanceof common_1.NotFoundException) {
                            revertOutcomeMessage = `Created file "${action.file_path}" was already deleted/not found.`;
                        }
                        else {
                            throw e;
                        }
                    }
                    revertSucceeded = true;
                    break;
                case 'run_command':
                    revertOutcomeMessage = `Command "${action.command_string}" cannot be automatically reverted.`;
                    revertSucceeded = false;
                    break;
                default:
                    throw new Error(`Unsupported action_type for revert: "${action.action_type}"`);
            }
            this.logger.log(`Revert attempt for action ID ${action.id} resulted in success: ${revertSucceeded}. Message: ${revertOutcomeMessage}`);
        }
        catch (error) {
            this.logger.error(`Failed to revert action ID ${action.id}: ${error.message}`, error.stack);
            revertOutcomeMessage = `Failed to revert action: ${error.message}`;
            errorDetails = error.message;
            revertSucceeded = false;
        }
        action.status = revertSucceeded
            ? AIActionStatus.CONFIRMED_REVERTED
            : AIActionStatus.REVERT_FAILED;
        const log = await this.executionLogsService.createLog({
            action_id: action.id,
            output: revertSucceeded ? revertOutcomeMessage : undefined,
            error_message: !revertSucceeded
                ? errorDetails || revertOutcomeMessage
                : undefined,
        });
        action.executionLogs = [...(action.executionLogs || []), log];
        await this.aiActionsRepository.save(action);
        return action;
    }
    async deleteAllByInputId(inputId) {
        const actions = await this.aiActionsRepository.find({
            where: { input_id: inputId },
        });
        if (actions.length > 0) {
            this.logger.log(`Deleting ${actions.length} actions for input ID ${inputId}`);
            await this.aiActionsRepository.remove(actions);
        }
    }
    async reExecuteAction(actionId) {
        const action = await this.findOne(actionId);
        if (action.action_type !== AIActionType.EXECUTE_CODE) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" is not of type EXECUTE_CODE. Only code execution actions can be re-run.`);
        }
        if (!action.content) {
            throw new common_1.BadRequestException(`Action with ID "${actionId}" has no content (code) to execute.`);
        }
        const tempDir = path.join(this.projectRoot, '.repoburg', 'temp');
        const randomId = crypto.randomBytes(8).toString('hex');
        const tempFilePath = path.join(tempDir, `exec-rerun-${randomId}.ts`);
        let unifiedOutput;
        let errorDetails;
        try {
            await fs.mkdir(tempDir, { recursive: true });
            await fs.writeFile(tempFilePath, action.content, 'utf-8');
            const { stdout, stderr } = await execAsync(`npx tsx "${tempFilePath}"`, {
                cwd: this.projectRoot,
                maxBuffer: 10 * 1024 * 1024,
                timeout: 30000,
            });
            unifiedOutput = stdout || stderr || '(Script produced no output)';
        }
        catch (error) {
            const execError = error;
            unifiedOutput = `EXECUTION FAILED: ${execError.message}\n\nSTDOUT:\n${execError.stdout || 'N/A'}\n\nSTDERR:\n${execError.stderr || 'N/A'}`;
            errorDetails = execError.message;
            this.logger.error(`Error re-executing code action ${actionId}: ${unifiedOutput}`);
        }
        finally {
            try {
                await fs.unlink(tempFilePath);
            }
            catch (cleanupErr) {
                this.logger.warn(`Failed to delete temp file ${tempFilePath}: ${cleanupErr}`);
            }
        }
        const log = await this.executionLogsService.createLog({
            action_id: action.id,
            output: unifiedOutput,
            error_message: errorDetails,
        });
        action.executionLogs = [...(action.executionLogs || []), log];
        return action;
    }
};
exports.AIActionsService = AIActionsService;
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "findAllByInputId", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "findOne", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "approveAction", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "discardAction", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "confirmKeepAction", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "revertAction", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "deleteAllByInputId", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionsService.prototype, "reExecuteAction", null);
exports.AIActionsService = AIActionsService = AIActionsService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(core_entities_1.AIAction)),
    __param(1, (0, typeorm_1.InjectRepository)(core_entities_1.SessionInput)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        execution_logs_service_1.ExecutionLogsService])
], AIActionsService);
//# sourceMappingURL=ai-actions.service.js.map