"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 AIActionBatchService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIActionBatchService = 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 child_process_1 = require("child_process");
const util_1 = require("util");
const core_entities_1 = require("../core-entities");
const ai_actions_service_1 = require("./ai-actions.service");
const execution_logs_service_1 = require("../execution-logs/execution-logs.service");
const application_state_service_1 = require("../application-state/application-state.service");
const chat_service_1 = require("../interactive-chat/chat.service");
const llm_responses_service_1 = require("../llm-responses/llm-responses.service");
const mcp_service_1 = require("../mcp/mcp.service");
const utils_1 = require("../utils");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
let AIActionBatchService = AIActionBatchService_1 = class AIActionBatchService {
    constructor(aiActionsRepository, sessionInputsRepository, aiActionsService, executionLogsService, applicationStateService, chatService, llmResponsesService, mcpService) {
        this.aiActionsRepository = aiActionsRepository;
        this.sessionInputsRepository = sessionInputsRepository;
        this.aiActionsService = aiActionsService;
        this.executionLogsService = executionLogsService;
        this.applicationStateService = applicationStateService;
        this.chatService = chatService;
        this.llmResponsesService = llmResponsesService;
        this.mcpService = mcpService;
        this.logger = new common_1.Logger(AIActionBatchService_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 runCommand(commandString) {
        this.logger.log(`Executing command: ${commandString}`);
        try {
            const { stdout, stderr } = await execAsync(commandString, {
                cwd: this.projectRoot,
                maxBuffer: 128 * 1024 * 1024,
                timeout: 15000,
            });
            if (stderr) {
                this.logger.warn(`Command "${commandString}" produced stderr: ${stderr}`);
            }
            this.logger.log(`Command "${commandString}" stdout: ${stdout || '(no stdout)'}`);
            return { stdout, stderr };
        }
        catch (error) {
            this.logger.error(`Error executing command "${commandString}": ${error.message}`, error.stack);
            const execError = error;
            const unifiedOutput = `COMMAND FAILED: ${execError.message}\n\nSTDOUT:\n${execError.stdout || 'N/A'}\n\nSTDERR:\n${execError.stderr || 'N/A'}`;
            return { stdout: unifiedOutput, stderr: '' };
        }
    }
    async useMcpTool(serverName, toolName, args) {
        this.logger.log(`Executing MCP tool: ${serverName}.${toolName} with args ${JSON.stringify(args)}`);
        try {
            return await this.mcpService.executeMcpTool(serverName, toolName, args);
        }
        catch (error) {
            this.logger.error(`Error executing MCP tool "${serverName}.${toolName}": ${error.message}`, error.stack);
            const unifiedOutput = `MCP TOOL FAILED: ${error.message}`;
            return { stdout: unifiedOutput, stderr: '' };
        }
    }
    async applyApprovedActions(inputId) {
        const sessionInput = await this.sessionInputsRepository.findOneBy({
            id: inputId,
        });
        if (!sessionInput) {
            throw new common_1.NotFoundException(`SessionInput with ID "${inputId}" not found.`);
        }
        const actionsToApply = await this.aiActionsRepository.find({
            where: {
                input_id: inputId,
                status: ai_actions_service_1.AIActionStatus.APPROVED_FOR_APPLY,
            },
            order: { order_of_execution: 'ASC', created_at: 'ASC' },
        });
        if (actionsToApply.length === 0) {
            this.logger.log(`No actions in "approved_for_apply" state for input ID "${inputId}".`);
            return [];
        }
        const results = [];
        for (const action of actionsToApply) {
            let outcomeMessage = '';
            let errorMessage = undefined;
            let actionSucceeded = false;
            try {
                this.logger.log(`Attempting to apply action ID ${action.id}, type ${action.action_type}`);
                switch (action.action_type) {
                    case 'create_file':
                        if (!action.file_path ||
                            action.content === null ||
                            action.content === undefined) {
                            throw new Error('Missing file_path or content for create_file action.');
                        }
                        await this.createFile(action.file_path, action.content);
                        outcomeMessage = `File "${action.file_path}" created successfully.`;
                        break;
                    case 'overwrite_file':
                        if (!action.file_path ||
                            action.content === null ||
                            action.content === undefined) {
                            throw new Error('Missing file_path or content for overwrite_file action.');
                        }
                        await this.editFile(action.file_path, action.content);
                        outcomeMessage = `File "${action.file_path}" overwritten successfully.`;
                        break;
                    case 'delete_file':
                        if (!action.file_path) {
                            throw new Error('Missing file_path for delete_file action.');
                        }
                        await this.deleteFile(action.file_path);
                        outcomeMessage = `File "${action.file_path}" deleted successfully.`;
                        break;
                    case 'run_command':
                        if (!action.command_string) {
                            throw new Error('Missing command_string for run_command action.');
                        }
                        const cmdResult = await this.runCommand(action.command_string);
                        outcomeMessage = `Command "${action.command_string}" executed. Output: ${cmdResult.stdout || '(no stdout)'}`;
                        if (cmdResult.stderr) {
                            this.logger.warn(`Command stderr for action ${action.id}: ${cmdResult.stderr}`);
                            outcomeMessage += `\nStderr: ${cmdResult.stderr}`;
                        }
                        const message = `The previous 'run_command' action produced this output. Please analyze it and continue with the next logical step based on the original request. Do not just describe the output; use it to proceed.\n\nOUTPUT:\n${cmdResult.stdout}`;
                        this.chatService
                            .sendMessage(sessionInput.session_id, message)
                            .catch((err) => {
                            this.logger.error(`Follow-up LLM call from run_command failed: ${err.message}`, err.stack);
                        });
                        break;
                    default: {
                        const mcpMatch = action.action_type.match(/^([^_]+)__(.+)$/);
                        if (mcpMatch) {
                            const [, serverName, toolName] = mcpMatch;
                            if (!action.arguments) {
                                throw new Error(`Missing arguments for MCP tool action: ${action.action_type}`);
                            }
                            const mcpResult = await this.useMcpTool(serverName, toolName, JSON.parse(action.arguments));
                            outcomeMessage = `MCP Tool "${serverName}.${toolName}" executed. Output: ${mcpResult.stdout || '(no stdout)'}`;
                            if (mcpResult.stderr) {
                                this.logger.warn(`MCP Tool stderr for action ${action.id}: ${mcpResult.stderr}`);
                                outcomeMessage += `\nStderr: ${mcpResult.stderr}`;
                            }
                            const mcpMessage = `The previous MCP tool "${serverName}.${toolName}" produced this output. Please analyze it and continue with the next logical step based on the original request. Do not just describe the output; use it to proceed.\n\nTOOL: ${serverName}.${toolName}\nOUTPUT:\n${mcpResult.stdout}`;
                            this.chatService
                                .sendMessage(sessionInput.session_id, mcpMessage)
                                .catch((err) => {
                                this.logger.error(`Follow-up LLM call from MCP tool failed: ${err.message}`, err.stack);
                            });
                        }
                        else {
                            throw new Error(`Unsupported action_type: "${action.action_type}"`);
                        }
                        break;
                    }
                }
                action.status = ai_actions_service_1.AIActionStatus.CONFIRMED_KEPT;
                actionSucceeded = true;
                this.logger.log(`Action ID ${action.id} applied successfully.`);
            }
            catch (error) {
                this.logger.error(`Failed to apply action ID ${action.id}: ${error.message}`, error.stack);
                action.status = ai_actions_service_1.AIActionStatus.EXECUTION_FAILED;
                errorMessage = error.message;
                outcomeMessage = `Failed to apply action: ${error.message}`;
            }
            const log = await this.executionLogsService.createLog({
                action_id: action.id,
                output: actionSucceeded ? outcomeMessage : undefined,
                error_message: errorMessage,
            });
            await this.aiActionsRepository.save(action);
            const reloadedAction = await this.aiActionsService.findOne(action.id);
            results.push({
                action_id: action.id,
                status: reloadedAction.status,
                logs: reloadedAction.executionLogs || [log],
            });
        }
        return results;
    }
    async discardAllActionsForInput(inputId) {
        const sessionInput = await this.sessionInputsRepository.findOneBy({
            id: inputId,
        });
        if (!sessionInput) {
            throw new common_1.NotFoundException(`SessionInput with ID "${inputId}" not found.`);
        }
        const actionsToDiscard = await this.aiActionsRepository.find({
            where: {
                input_id: inputId,
                status: (0, typeorm_2.In)([
                    ai_actions_service_1.AIActionStatus.PROPOSED,
                    ai_actions_service_1.AIActionStatus.APPROVED_FOR_APPLY,
                ]),
            },
        });
        if (actionsToDiscard.length === 0) {
            this.logger.log(`No actions in "proposed" or "approved_for_apply" state for input ID "${inputId}" to discard.`);
            return [];
        }
        const results = [];
        for (const action of actionsToDiscard) {
            action.status = ai_actions_service_1.AIActionStatus.REJECTED_BEFORE_APPLY;
            await this.aiActionsRepository.save(action);
            results.push({
                action_id: action.id,
                status: action.status,
            });
            this.logger.log(`Action ID ${action.id} for input ${inputId} discarded, status set to ${action.status}.`);
        }
        return results;
    }
    async confirmAllAppliedActionsForInput(inputId) {
        const sessionInput = await this.sessionInputsRepository.findOneBy({
            id: inputId,
        });
        if (!sessionInput) {
            throw new common_1.NotFoundException(`SessionInput with ID "${inputId}" not found.`);
        }
        const actionsToConfirm = await this.aiActionsRepository.find({
            where: {
                input_id: inputId,
                status: ai_actions_service_1.AIActionStatus.APPLIED_PENDING_REVIEW,
            },
        });
        if (actionsToConfirm.length === 0) {
            this.logger.log(`No actions in "${ai_actions_service_1.AIActionStatus.APPLIED_PENDING_REVIEW}" state for input ID "${inputId}" to confirm.`);
            return [];
        }
        const results = [];
        for (const action of actionsToConfirm) {
            action.status = ai_actions_service_1.AIActionStatus.CONFIRMED_KEPT;
            await this.aiActionsRepository.save(action);
            results.push({
                action_id: action.id,
                status: action.status,
            });
            this.logger.log(`Action ID ${action.id} for input ${inputId} confirmed kept, status set to ${action.status}.`);
        }
        return results;
    }
    async revertAllAppliedActionsForInput(inputId) {
        const sessionInput = await this.sessionInputsRepository.findOneBy({
            id: inputId,
        });
        if (!sessionInput) {
            throw new common_1.NotFoundException(`SessionInput with ID "${inputId}" not found. Cannot revert actions.`);
        }
        const actionsToRevert = await this.aiActionsRepository.find({
            where: {
                input_id: inputId,
                status: ai_actions_service_1.AIActionStatus.APPLIED_PENDING_REVIEW,
            },
            order: { order_of_execution: 'DESC', created_at: 'DESC' },
        });
        if (actionsToRevert.length === 0) {
            this.logger.log(`No actions in "${ai_actions_service_1.AIActionStatus.APPLIED_PENDING_REVIEW}" state for input ID "${inputId}" to revert.`);
            return [];
        }
        const results = [];
        for (const action of actionsToRevert) {
            const revertedAction = await this.aiActionsService.revertAction(action.id);
            results.push({
                action_id: revertedAction.id,
                status: revertedAction.status,
                logs: revertedAction.executionLogs || [],
            });
        }
        this.logger.log(`Attempted to revert ${actionsToRevert.length} actions for input ID "${inputId}".`);
        return results;
    }
};
exports.AIActionBatchService = AIActionBatchService;
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], AIActionBatchService.prototype, "createFile", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], AIActionBatchService.prototype, "editFile", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionBatchService.prototype, "deleteFile", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], AIActionBatchService.prototype, "runCommand", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object]),
    __metadata("design:returntype", Promise)
], AIActionBatchService.prototype, "useMcpTool", null);
exports.AIActionBatchService = AIActionBatchService = AIActionBatchService_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)),
    __param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() => chat_service_1.ChatService))),
    __param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() => llm_responses_service_1.LlmResponsesService))),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        ai_actions_service_1.AIActionsService,
        execution_logs_service_1.ExecutionLogsService,
        application_state_service_1.ApplicationStateService,
        chat_service_1.ChatService,
        llm_responses_service_1.LlmResponsesService,
        mcp_service_1.McpService])
], AIActionBatchService);
//# sourceMappingURL=ai-action-batch.service.js.map