"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextOptimizer = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const glob_1 = require("glob");
const operation_context_1 = require("@myycontext/core/src/types/operation-context");
/**
 * ContextOptimizer Service
 * Provides minimal, targeted context for operations
 */
class ContextOptimizer {
    constructor(baseDir) {
        this.baseDir = baseDir || process.cwd();
        this.importCache = new Map();
        this.dependentCache = new Map();
    }
    /**
     * Get optimized context for an operation
     */
    async getContext(op, strategy = "balanced") {
        const requirement = this.getRequirementForOperation(op);
        const files = await this.gatherFiles(op, requirement, strategy);
        const metadata = this.buildMetadata(files, requirement, strategy);
        return {
            operation: {
                type: op.type,
                id: op.id,
            },
            requirement,
            files,
            metadata,
            timestamp: new Date().toISOString(),
        };
    }
    /**
     * Get context diff report
     */
    async getContextDiff(op, strategy = "balanced") {
        const requirement = this.getRequirementForOperation(op);
        const allPossibleFiles = await this.getAllPossibleFiles(op, requirement);
        const optimizedFiles = await this.gatherFiles(op, requirement, strategy);
        const included = optimizedFiles.map((f) => f.path);
        const excluded = allPossibleFiles.filter((f) => !included.includes(f));
        const compressed = optimizedFiles
            .filter((f) => f.compressed)
            .map((f) => f.path);
        const totalSizeKB = (0, operation_context_1.calculateContextSize)(optimizedFiles);
        const budgetKB = requirement.contextBudgetKB || 100;
        const budgetUtilization = budgetKB > 0 ? Math.round((totalSizeKB / budgetKB) * 100) : 0;
        return {
            included,
            excluded,
            pruned: excluded,
            compressed,
            totalSizeKB,
            budgetKB,
            budgetUtilization,
        };
    }
    /**
     * Compress file content
     */
    compressContent(content, filePath, options = {}) {
        let compressed = content;
        // Remove comments
        if (options.removeComments !== false) {
            const ext = path.extname(filePath);
            if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
                // Remove single-line comments
                compressed = compressed.replace(/\/\/.*$/gm, "");
                // Remove multi-line comments (simple approach)
                compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, "");
            }
        }
        // Collapse whitespace
        if (options.removeWhitespace !== false) {
            // Remove empty lines
            compressed = compressed.replace(/^\s*[\r\n]/gm, "");
            // Collapse multiple spaces to single space (preserve indentation)
            compressed = compressed.replace(/[ \t]+/g, " ");
        }
        // Truncate if too long
        const maxLines = options.maxLinesPerFile || 500;
        const lines = compressed.split("\n");
        if (lines.length > maxLines) {
            compressed =
                lines.slice(0, maxLines).join("\n") +
                    `\n... (${lines.length - maxLines} lines truncated)`;
        }
        return compressed;
    }
    /**
     * Clear caches
     */
    clearCaches() {
        this.importCache.clear();
        this.dependentCache.clear();
    }
    /**
     * Get requirement for operation (with param-specific overrides)
     */
    getRequirementForOperation(op) {
        const baseRequirement = (0, operation_context_1.getContextRequirement)(op.type);
        // Override with operation-specific files
        const files = [...(baseRequirement.files || [])];
        switch (op.type) {
            case "patch-file":
            case "delete-file":
                if (op.params.path)
                    files.push(op.params.path);
                break;
            case "rename-file":
                if (op.params.from)
                    files.push(op.params.from);
                break;
            case "add-import":
            case "add-export":
            case "remove-import":
            case "add-classname":
            case "add-server-action":
            case "add-test":
                if (op.params.file)
                    files.push(op.params.file);
                break;
        }
        return {
            ...baseRequirement,
            files,
        };
    }
    /**
     * Gather files based on requirement and strategy
     */
    async gatherFiles(op, requirement, strategy) {
        const files = [];
        const seenPaths = new Set();
        // Step 1: Add explicit files
        for (const filePath of requirement.files || []) {
            const absPath = path.resolve(this.baseDir, filePath);
            if (await fs.pathExists(absPath)) {
                const fileContext = await this.buildFileContext(absPath, strategy, requirement);
                if (fileContext) {
                    files.push(fileContext);
                    seenPaths.add(absPath);
                }
            }
        }
        // Step 2: Add files matching patterns
        for (const pattern of requirement.patterns || []) {
            const matches = await this.globFiles(pattern);
            for (const match of matches.slice(0, 20)) {
                // Limit to 20 files per pattern
                if (seenPaths.has(match))
                    continue;
                const fileContext = await this.buildFileContext(match, strategy, requirement);
                if (fileContext) {
                    files.push(fileContext);
                    seenPaths.add(match);
                }
            }
        }
        // Step 3: Add imports (if requested)
        if (requirement.includeImports) {
            const importFiles = await this.gatherImports(files.map((f) => f.path), requirement.maxDepth || 0);
            for (const importPath of importFiles) {
                if (seenPaths.has(importPath))
                    continue;
                const fileContext = await this.buildFileContext(importPath, strategy, requirement);
                if (fileContext) {
                    files.push(fileContext);
                    seenPaths.add(importPath);
                }
            }
        }
        // Step 4: Add dependents (if requested)
        if (requirement.includeDependents) {
            const dependentFiles = await this.gatherDependents(files.map((f) => f.path));
            for (const depPath of dependentFiles) {
                if (seenPaths.has(depPath))
                    continue;
                const fileContext = await this.buildFileContext(depPath, strategy, requirement);
                if (fileContext) {
                    files.push(fileContext);
                    seenPaths.add(depPath);
                }
            }
        }
        // Step 5: Enforce budget
        const budgetKB = requirement.contextBudgetKB || 100;
        if ((0, operation_context_1.exceedsBudget)(files, budgetKB)) {
            return this.pruneToFitBudget(files, budgetKB, strategy);
        }
        return files;
    }
    /**
     * Build file context
     */
    async buildFileContext(absPath, strategy, requirement) {
        if (!(await fs.pathExists(absPath)))
            return null;
        // Check exclusions
        const relativePath = path.relative(this.baseDir, absPath);
        if (this.isExcluded(relativePath, requirement.excludePatterns || [])) {
            return null;
        }
        const content = await fs.readFile(absPath, "utf-8");
        const stat = await fs.stat(absPath);
        const sizeKB = Math.round(stat.size / 1024);
        // Apply compression based on strategy
        const compressionOptions = this.getCompressionOptions(strategy);
        const finalContent = strategy === "none"
            ? content
            : this.compressContent(content, absPath, compressionOptions);
        const compressed = finalContent !== content;
        const imports = await this.extractImports(absPath, content);
        const exports = await this.extractExports(content);
        const dependents = await this.findDependents(absPath);
        const testFiles = await this.findTestFiles(absPath);
        return {
            path: relativePath,
            content: finalContent,
            sizeKB: Math.round(Buffer.byteLength(finalContent, "utf-8") / 1024),
            imports,
            exports,
            dependents: dependents.map((d) => path.relative(this.baseDir, d)),
            hasTests: testFiles.length > 0,
            testFiles: testFiles.map((t) => path.relative(this.baseDir, t)),
            compressed,
        };
    }
    /**
     * Get all possible files (for diff calculation)
     */
    async getAllPossibleFiles(op, requirement) {
        const files = [];
        // Add explicit files
        for (const filePath of requirement.files || []) {
            const absPath = path.resolve(this.baseDir, filePath);
            if (await fs.pathExists(absPath)) {
                files.push(path.relative(this.baseDir, absPath));
            }
        }
        // Add pattern matches
        for (const pattern of requirement.patterns || []) {
            const matches = await this.globFiles(pattern);
            files.push(...matches.map((m) => path.relative(this.baseDir, m)));
        }
        return [...new Set(files)];
    }
    /**
     * Prune files to fit budget
     */
    pruneToFitBudget(files, budgetKB, strategy) {
        // Sort by priority (explicit files first, then by size)
        const sorted = [...files].sort((a, b) => {
            // Prioritize smaller files
            return a.sizeKB - b.sizeKB;
        });
        const kept = [];
        let currentSize = 0;
        for (const file of sorted) {
            if (currentSize + file.sizeKB <= budgetKB) {
                kept.push(file);
                currentSize += file.sizeKB;
            }
        }
        return kept;
    }
    /**
     * Glob files
     */
    async globFiles(pattern) {
        return await (0, glob_1.glob)(pattern, { cwd: this.baseDir, absolute: true });
    }
    /**
     * Extract imports from file
     */
    async extractImports(absPath, content) {
        if (this.importCache.has(absPath)) {
            return this.importCache.get(absPath);
        }
        const imports = [];
        const ext = path.extname(absPath);
        if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
            // Match import statements
            const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
            let match;
            while ((match = importRegex.exec(content)) !== null) {
                if (match[1]) {
                    imports.push(match[1]);
                }
            }
        }
        this.importCache.set(absPath, imports);
        return imports;
    }
    /**
     * Extract exports from file
     */
    async extractExports(content) {
        const exports = [];
        const exportRegex = /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g;
        let match;
        while ((match = exportRegex.exec(content)) !== null) {
            if (match[1]) {
                exports.push(match[1]);
            }
        }
        return exports;
    }
    /**
     * Find dependents (files that import this file)
     */
    async findDependents(absPath) {
        if (this.dependentCache.has(absPath)) {
            return this.dependentCache.get(absPath);
        }
        // This is expensive - would normally use a pre-built import graph
        // For now, return empty array
        const dependents = [];
        this.dependentCache.set(absPath, dependents);
        return dependents;
    }
    /**
     * Find test files for a source file
     */
    async findTestFiles(absPath) {
        const baseName = path.basename(absPath, path.extname(absPath));
        const dir = path.dirname(absPath);
        const testPatterns = [
            `${baseName}.test.ts`,
            `${baseName}.test.tsx`,
            `${baseName}.spec.ts`,
            `${baseName}.spec.tsx`,
            `__tests__/${baseName}.test.ts`,
        ];
        const testFiles = [];
        for (const pattern of testPatterns) {
            const testPath = path.join(dir, pattern);
            if (await fs.pathExists(testPath)) {
                testFiles.push(testPath);
            }
        }
        return testFiles;
    }
    /**
     * Gather imports recursively
     */
    async gatherImports(files, maxDepth) {
        const imports = new Set();
        const queue = files.map((f) => ({
            path: path.resolve(this.baseDir, f),
            depth: 0,
        }));
        while (queue.length > 0) {
            const { path: currentPath, depth } = queue.shift();
            if (depth >= maxDepth)
                continue;
            const content = await fs.readFile(currentPath, "utf-8").catch(() => "");
            const fileImports = await this.extractImports(currentPath, content);
            for (const imp of fileImports) {
                if (imp.startsWith(".") || imp.startsWith("/")) {
                    const absImport = path.resolve(path.dirname(currentPath), imp);
                    if (!imports.has(absImport)) {
                        imports.add(absImport);
                        queue.push({ path: absImport, depth: depth + 1 });
                    }
                }
            }
        }
        return Array.from(imports);
    }
    /**
     * Gather dependents
     */
    async gatherDependents(files) {
        const dependents = new Set();
        for (const file of files) {
            const absPath = path.resolve(this.baseDir, file);
            const fileDependents = await this.findDependents(absPath);
            fileDependents.forEach((d) => dependents.add(d));
        }
        return Array.from(dependents);
    }
    /**
     * Check if file is excluded
     */
    isExcluded(filePath, patterns) {
        for (const pattern of patterns) {
            if (filePath.includes(pattern.replace("/**", ""))) {
                return true;
            }
        }
        return false;
    }
    /**
     * Get compression options for strategy
     */
    getCompressionOptions(strategy) {
        switch (strategy) {
            case "none":
                return {
                    removeComments: false,
                    removeWhitespace: false,
                    summarizeContent: false,
                    maxLinesPerFile: undefined,
                };
            case "minimal":
                return {
                    removeComments: false,
                    removeWhitespace: false,
                    summarizeContent: false,
                    maxLinesPerFile: 1000,
                };
            case "balanced":
                return {
                    removeComments: true,
                    removeWhitespace: false,
                    summarizeContent: false,
                    maxLinesPerFile: 500,
                };
            case "aggressive":
                return {
                    removeComments: true,
                    removeWhitespace: true,
                    summarizeContent: true,
                    maxLinesPerFile: 200,
                };
            default:
                return {};
        }
    }
    /**
     * Build metadata
     */
    buildMetadata(files, requirement, strategy) {
        return {
            totalFiles: files.length,
            totalSizeKB: (0, operation_context_1.calculateContextSize)(files),
            includedFiles: files.map((f) => f.path),
            excludedFiles: [],
            pruningStrategy: strategy,
            compressionApplied: files.some((f) => f.compressed),
        };
    }
}
exports.ContextOptimizer = ContextOptimizer;
//# sourceMappingURL=ContextOptimizer.js.map