"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 WorkspaceService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkspaceService = void 0;
const common_1 = require("@nestjs/common");
const context_generation_service_1 = require("../context-generation/context-generation.service");
const fs = require("fs/promises");
const path = require("path");
const utils_1 = require("../utils");
const child_process_1 = require("child_process");
const api_1 = require("@opentelemetry/api");
let WorkspaceService = WorkspaceService_1 = class WorkspaceService {
    constructor(contextGenerationService) {
        this.contextGenerationService = contextGenerationService;
        this.logger = new common_1.Logger(WorkspaceService_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
        this.tracer = api_1.trace.getTracer('repoburg-backend');
        this.fileTreeCache = null;
        this.fileTreeCacheTimestamp = null;
        this.CACHE_TTL_MS = 5 * 60 * 1000;
    }
    execAsync(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 getFilesFromRg() {
        const rgCommand = await this.contextGenerationService.getCommandPath('rg');
        const ignoreFlags = await this.contextGenerationService.getRipGrepIgnoreFlags();
        const command = `${rgCommand} ${ignoreFlags} --files . --hidden --glob '!.git'`;
        try {
            this.logger.log(`Executing for file list: ${command}`);
            const { stdout } = await this.execAsync(command, {
                cwd: this.projectRoot,
                maxBuffer: 256 * 1024 * 1024,
                timeout: 10000,
            });
            return stdout.trim().split('\n').filter(Boolean);
        }
        catch (error) {
            if (error.code === 1 && error.stdout === '' && error.stderr === '') {
                this.logger.log('rg --files returned no files.');
                return [];
            }
            this.logger.error(`Failed to get file list from rg: ${error.message}`, error.stack);
            throw new Error(`Could not retrieve file list from RipGrep. Is 'rg' installed and in your PATH?`);
        }
    }
    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 validatePaths(paths) {
        const invalidPaths = [];
        const allPaths = [...(paths.files || []), ...(paths.folders || [])];
        for (const p of allPaths) {
            try {
                const safePath = this.resolveAndValidatePath(p);
                await fs.access(safePath);
            }
            catch (e) {
                invalidPaths.push(p);
            }
        }
        return {
            valid: invalidPaths.length === 0,
            invalidPaths,
        };
    }
    async getFileTree() {
        const now = Date.now();
        if (this.fileTreeCache &&
            this.fileTreeCacheTimestamp &&
            now - this.fileTreeCacheTimestamp < this.CACHE_TTL_MS) {
            this.logger.log('Returning file tree from cache.');
            return this.fileTreeCache;
        }
        this.logger.log(`Building file tree from filesystem with rg: ${this.projectRoot}`);
        try {
            const files = await this.getFilesFromRg();
            const fileTree = {};
            files.forEach((filePath) => {
                filePath.split(path.sep).reduce((level, part) => {
                    if (!level[part]) {
                        level[part] = {};
                    }
                    return level[part];
                }, fileTree);
            });
            const toArboristData = (subtree, pathPrefix = '') => {
                return Object.entries(subtree)
                    .map(([name, children]) => {
                    const id = pathPrefix ? `${pathPrefix}/${name}` : name;
                    const isFile = Object.keys(children).length === 0;
                    if (isFile) {
                        return { id, name };
                    }
                    else {
                        return {
                            id,
                            name,
                            children: toArboristData(children, id),
                        };
                    }
                })
                    .sort((a, b) => {
                    const isAFolder = !!a.children;
                    const isBFolder = !!b.children;
                    if (isAFolder && !isBFolder)
                        return -1;
                    if (!isAFolder && isBFolder)
                        return 1;
                    return a.name.localeCompare(b.name);
                });
            };
            const treeData = toArboristData(fileTree);
            const response = { tree: treeData };
            this.fileTreeCache = response;
            this.fileTreeCacheTimestamp = now;
            this.logger.log(`Built and cached file tree with ${files.length} total entries.`);
            return response;
        }
        catch (error) {
            this.logger.error('Failed to get file tree:', error);
            throw error;
        }
    }
    async invalidateFileTreeCache() {
        this.logger.log('Invalidating file tree cache.');
        this.fileTreeCache = null;
        this.fileTreeCacheTimestamp = null;
    }
    async search(query, type, page, limit) {
        this.logger.log(`Searching for "${query}" (type: ${type})`);
        const { tree } = await this.getFileTree();
        const { files, folders } = this.tracer.startActiveSpan('workspace.search#flattenTree', (span) => {
            const filesList = [];
            const foldersList = [];
            const flatten = (nodes) => {
                for (const node of nodes) {
                    if (node.children) {
                        foldersList.push(node.id);
                        flatten(node.children);
                    }
                    else {
                        filesList.push(node.id);
                    }
                }
            };
            flatten(tree);
            span.setAttribute('files.count', filesList.length);
            span.setAttribute('folders.count', foldersList.length);
            span.end();
            return { files: filesList, folders: foldersList };
        });
        const searchPool = this.tracer.startActiveSpan('workspace.search#prepareSearchPool', (span) => {
            let pool = [];
            if (type === 'all') {
                pool = [
                    ...files.map((p) => ({ path: p, type: 'file' })),
                    ...folders.map((p) => ({ path: p, type: 'folder' })),
                ];
            }
            else if (type === 'files') {
                pool = files.map((p) => ({ path: p, type: 'file' }));
            }
            else if (type === 'folders') {
                pool = folders.map((p) => ({ path: p, type: 'folder' }));
            }
            span.setAttribute('pool.size', pool.length);
            span.setAttribute('search.type', type);
            span.end();
            return pool;
        });
        const matchedPaths = this.tracer.startActiveSpan('workspace.search#fuzzySearch', (span) => {
            const searchPoolStrings = searchPool.map((item) => item.path);
            const results = (0, utils_1.fuzzySearch)(query, searchPoolStrings);
            span.setAttribute('search.query', query);
            span.setAttribute('results.count', results.length);
            span.end();
            return results;
        });
        const { paginatedResults, total } = this.tracer.startActiveSpan('workspace.search#filterAndPaginate', (span) => {
            const searchPoolMap = new Map(searchPool.map((item) => [item.path, item]));
            const results = matchedPaths
                .map((path) => searchPoolMap.get(path))
                .filter((item) => item !== undefined);
            const totalResults = results.length;
            const startIndex = (page - 1) * limit;
            const endIndex = page * limit;
            const paginated = results.slice(startIndex, endIndex);
            span.end();
            return { paginatedResults: paginated, total: totalResults };
        });
        const endIndex = page * limit;
        this.logger.log(`Search for "${query}" (type: ${type}) found ${total} total results. Returning page ${page} with ${paginatedResults.length} items.`);
        return {
            results: paginatedResults,
            total,
            page,
            hasMore: endIndex < total,
        };
    }
    async getFileContent(filePath) {
        this.logger.log(`Reading content for file: ${filePath}`);
        return this.contextGenerationService.fetchFileContent(filePath);
    }
};
exports.WorkspaceService = WorkspaceService;
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "execAsync", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "getFilesFromRg", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "validatePaths", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "getFileTree", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "invalidateFileTreeCache", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Number, Number]),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "search", null);
__decorate([
    (0, utils_1.Trace)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], WorkspaceService.prototype, "getFileContent", null);
exports.WorkspaceService = WorkspaceService = WorkspaceService_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [context_generation_service_1.ContextGenerationService])
], WorkspaceService);
//# sourceMappingURL=workspace.service.js.map