"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 ContextGenerationService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextGenerationService = void 0;
const common_1 = require("@nestjs/common");
const eta_1 = require("eta");
const fs = require("fs/promises");
const glob_1 = require("glob");
const path = require("path");
const child_process_1 = require("child_process");
const typeorm_1 = require("@nestjs/typeorm");
const core_entities_1 = require("../core-entities");
const typeorm_2 = require("typeorm");
const context_template_entity_1 = require("../core-entities/context-template.entity");
const utils_1 = require("../utils");
let ContextGenerationService = ContextGenerationService_1 = class ContextGenerationService {
    constructor(contextTemplatesRepository) {
        this.contextTemplatesRepository = contextTemplatesRepository;
        this.logger = new common_1.Logger(ContextGenerationService_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
        this.partialCache = new Map();
        this.eta = new eta_1.Eta({
            views: this.projectRoot,
            useWith: true,
        });
        this.templateHelpers = {
            readFile: this.secureReadFile.bind(this),
            readDir: this.secureReadDir.bind(this),
            glob: this.secureGlob.bind(this),
            tree: this.secureTree.bind(this),
            ripGrep: this.secureRipGrep.bind(this),
            gitDiff: this.secureGitDiff.bind(this),
            runCommand: this.runCommand.bind(this),
            include: this.include.bind(this),
            fetchFileContent: this.fetchFileContent.bind(this),
        };
    }
    execPromise(command, options) {
        return new Promise((resolve, reject) => {
            (0, child_process_1.exec)(command, options, (error, stdout, stderr) => {
                const stdoutStr = stdout.toString();
                const stderrStr = stderr.toString();
                if (error) {
                    error['stdout'] = stdoutStr;
                    error['stderr'] = stderrStr;
                    reject(error);
                    return;
                }
                resolve({ stdout: stdoutStr, stderr: stderrStr });
            });
        });
    }
    async getRipGrepIgnoreFlags() {
        const rgignorePath = path.join(this.projectRoot, '.rgignore');
        try {
            await fs.access(rgignorePath, fs.constants.R_OK);
            this.logger.log('.rgignore found, using it instead of .gitignore');
            return '--ignore-file .rgignore --no-ignore-vcs';
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                this.logger.log('.rgignore not found, using default .gitignore behavior');
                return '';
            }
            this.logger.error(`Error checking for .rgignore: ${error.message}`);
            return '';
        }
    }
    async getCommandPath(commandName) {
        const inDist = __dirname.includes(path.join('backend', 'dist'));
        const relativePath = inDist
            ? path.join(__dirname, '..', 'bin', commandName)
            : path.join(__dirname, '..', '..', 'src', 'bin', commandName);
        const bundledPath = path.resolve(relativePath);
        try {
            await fs.access(bundledPath, fs.constants.X_OK);
            this.logger.log(`Using bundled executable for '${commandName}' at: ${bundledPath}`);
            return `'${bundledPath}'`;
        }
        catch (error) {
            this.logger.warn(`Bundled executable for '${commandName}' not found or not executable at '${bundledPath}'. Falling back to system PATH.`);
            return commandName;
        }
    }
    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 secureReadFile(filePath) {
        const safePath = this.resolveAndValidatePath(filePath);
        try {
            const stats = await fs.lstat(safePath);
            if (!stats.isFile()) {
                this.logger.warn(`Path is not a file: ${safePath}`);
                return `Error: '${filePath}' is not a file`;
            }
            const content = await fs.readFile(safePath, 'utf8');
            const lines = content.split('\n');
            const contentWithLineNumbers = lines
                .map((line, index) => `${index + 1}:${line}`)
                .join('\n');
            return `// File: ${filePath}\n\`\`\`\n${contentWithLineNumbers}\n\`\`\``;
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                this.logger.warn(`File not found in template render: ${safePath}`);
                return `Error: File not found at '${filePath}'`;
            }
            this.logger.error(`Error reading file ${safePath}: ${error.message}`);
            throw error;
        }
    }
    async fetchFileContent(filePath) {
        const safePath = this.resolveAndValidatePath(filePath);
        try {
            const stats = await fs.lstat(safePath);
            if (!stats.isFile()) {
                this.logger.warn(`Path is not a file: ${safePath}`);
                throw new common_1.NotFoundException(`'${filePath}' is not a file`);
            }
            const content = await fs.readFile(safePath, 'utf8');
            return content;
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                this.logger.warn(`File not found when fetching raw content: ${safePath}`);
                throw new common_1.NotFoundException(`File not found: ${filePath}`);
            }
            this.logger.error(`Error fetching raw file content from ${safePath}: ${error.message}`);
            throw error;
        }
    }
    async secureReadDir(dirPath) {
        const safePath = this.resolveAndValidatePath(dirPath);
        try {
            const entries = await fs.readdir(safePath, { withFileTypes: true });
            return entries.filter((e) => e.isFile()).map((entry) => entry.name);
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                this.logger.warn(`Directory not found in template render: ${safePath}`);
                return [`Error: Directory not found at '${dirPath}'`];
            }
            this.logger.error(`Error reading directory ${safePath}: ${error.message}`);
            throw error;
        }
    }
    async secureGlob(pattern) {
        if (path.isAbsolute(pattern)) {
            throw new common_1.BadRequestException(`Absolute paths are not allowed in glob patterns: ${pattern}`);
        }
        try {
            const files = await (0, glob_1.glob)(pattern, {
                cwd: this.projectRoot,
                nodir: true,
                absolute: false,
            });
            return files;
        }
        catch (error) {
            this.logger.error(`Error resolving glob pattern "${pattern}": ${error.message}`);
            throw error;
        }
    }
    async secureTree(args) {
        const treeCommand = await this.getCommandPath('tree');
        const command = `${treeCommand} ${args || ''}`;
        try {
            this.logger.log(`Executing command: ${command}`);
            const { stdout } = await this.execPromise(command, {
                cwd: this.projectRoot,
                maxBuffer: 32 * 1024 * 1024,
                timeout: 3000,
            });
            return stdout;
        }
        catch (error) {
            if (error.signal === 'SIGTERM') {
                this.logger.warn(`'tree' command timed out after 3 seconds.`);
                return `'tree' command timed out after 3 seconds.`;
            }
            if (error.message.includes('command not found')) {
                this.logger.warn(`'tree' command not found. Please ensure it is installed and in your PATH or bundled in 'backend/bin'.`);
                return `'tree' command not found. Please ensure it is installed and in your PATH or bundled in 'backend/bin'.`;
            }
            this.logger.error(`Error executing tree command: ${error.message}`, error);
            return `Error executing tree command: ${error.stderr || error.message}`;
        }
    }
    async secureRipGrep(args) {
        const rgCommand = await this.getCommandPath('rg');
        const ignoreFlags = await this.getRipGrepIgnoreFlags();
        const command = `${rgCommand} ${ignoreFlags} ${args || ''}`;
        try {
            this.logger.log(`Executing command: ${command}`);
            const { stdout } = await this.execPromise(command, {
                cwd: this.projectRoot,
                maxBuffer: 256 * 1024 * 1024,
                timeout: 3000,
            });
            return stdout;
        }
        catch (error) {
            if (error.signal === 'SIGTERM') {
                this.logger.warn(`'rg' command timed out after 3 seconds.`);
                return `'rg' command timed out after 3 seconds.`;
            }
            if (error.code === 1) {
                return error.stdout;
            }
            if (error.message.includes('command not found')) {
                this.logger.warn(`'rg' (ripgrep) command not found. Please ensure it is installed and in your PATH or bundled in 'backend/bin'.`);
                return `'rg' (ripgrep) command not found. Please ensure it is installed and in your PATH or bundled in 'backend/bin'.`;
            }
            this.logger.error(`Error executing ripgrep command: ${error.message}`, error);
            return `Error executing ripgrep command: ${error.stderr || error.message}`;
        }
    }
    async secureGitDiff(args) {
        if (args && /[`$&|;()<>*]/.test(args)) {
            this.logger.warn(`Potential command injection attempt in gitDiff args: ${args}`);
            return `Error: Invalid characters in gitDiff arguments.`;
        }
        const command = `git --no-pager diff ${args || ''}`;
        try {
            this.logger.log(`Executing command: ${command}`);
            const { stdout } = await this.execPromise(command, {
                cwd: this.projectRoot,
                maxBuffer: 256 * 1024 * 1024,
                timeout: 30000,
            });
            return stdout;
        }
        catch (error) {
            if (error.signal === 'SIGTERM') {
                this.logger.warn(`'git diff' command timed out after 30 seconds.`);
                return `'git diff' command timed out after 30 seconds.`;
            }
            if (error.message.includes('command not found')) {
                this.logger.warn(`'git' command not found. Please ensure it is installed and in your PATH.`);
                return `'git' command not found. Please ensure it is installed and in your PATH.`;
            }
            this.logger.error(`Error executing git diff command: ${error.message}`, error);
            return `Error executing git diff command: ${error.stderr || error.message}`;
        }
    }
    async runCommand(commandString) {
        if (!commandString) {
            return 'Error: command cannot be empty.';
        }
        try {
            this.logger.log(`Executing command from template: ${commandString}`);
            const { stdout, stderr } = await this.execPromise(commandString, {
                cwd: this.projectRoot,
                maxBuffer: 256 * 1024 * 1024,
                timeout: 30000,
            });
            if (stderr) {
                this.logger.warn(`Command "${commandString}" produced stderr: ${stderr}`);
                return `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
            }
            return stdout;
        }
        catch (error) {
            if (error.signal === 'SIGTERM') {
                this.logger.warn(`Command "${commandString}" timed out after 30 seconds.`);
                return `Error: Command "${commandString}" timed out after 30 seconds.`;
            }
            this.logger.error(`Error executing command "${commandString}": ${error.message}`, error);
            return `Error executing command "${commandString}": ${error.stderr || error.message}`;
        }
    }
    async include(templateName, data) {
        if (data.__included_partials.has(templateName)) {
            this.logger.error(`Recursive inclusion of partial "${templateName}" detected.`);
            return `[Error: Recursive inclusion of partial "${templateName}" detected]`;
        }
        let partial = this.partialCache.get(templateName);
        if (partial === undefined) {
            this.logger.log(`Cache miss for partial: "${templateName}". Fetching from DB.`);
            partial = await this.contextTemplatesRepository.findOneBy({
                template_name: templateName,
                template_type: context_template_entity_1.TemplateType.PARTIAL,
            });
            this.partialCache.set(templateName, partial || null);
        }
        else {
            this.logger.log(`Cache hit for partial: "${templateName}".`);
        }
        if (!partial) {
            this.logger.warn(`Partial template "${templateName}" not found or is not of type 'partial'.`);
            return `[Error: Partial template "${templateName}" not found]`;
        }
        data.__included_partials.add(templateName);
        const result = await this.eta.renderStringAsync(partial.template_content, data);
        data.__included_partials.delete(templateName);
        return result;
    }
    async render(templateContent, data = {}) {
        try {
            const renderData = {
                ...data,
                ...this.templateHelpers,
                __included_partials: new Set(),
            };
            renderData['include'] = (templateName) => this.include(templateName, renderData);
            const result = await this.eta.renderStringAsync(templateContent, renderData);
            return result;
        }
        catch (error) {
            this.logger.error(`Error rendering Eta template: ${error.message}`, error.stack);
            throw new Error(`Template rendering failed: ${error.message}`);
        }
    }
};
exports.ContextGenerationService = ContextGenerationService;
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "getRipGrepIgnoreFlags", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "getCommandPath", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "secureReadFile", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "fetchFileContent", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "secureGlob", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "secureTree", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "secureRipGrep", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "secureGitDiff", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "runCommand", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], ContextGenerationService.prototype, "render", null);
exports.ContextGenerationService = ContextGenerationService = ContextGenerationService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(core_entities_1.ContextTemplate)),
    __metadata("design:paramtypes", [typeorm_2.Repository])
], ContextGenerationService);
//# sourceMappingURL=context-generation.service.js.map