/**
 * Unified Cache Manager
 *
 * Central orchestrator for all caching subsystems.
 * Provides:
 * - Unified API for all cache operations
 * - Global statistics and monitoring
 * - Coordinated invalidation across caches
 * - Performance monitoring and optimization
 * - Memory pressure handling
 *
 * Based on research showing 60-70% reduction in expensive operations
 * through intelligent caching.
 */
import { EventEmitter } from 'events';
import { getLLMResponseCache } from './llm-response-cache.js';
import { getFileContentCache } from './file-content-cache.js';
import { getEmbeddingCache } from './embedding-cache.js';
import { getSearchResultsCache } from './search-results-cache.js';
import { getCacheConfig } from './cache-config.js';
import { logger } from '../utils/logger.js';
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_MANAGER_CONFIG = {
    enabled: true,
    enableMemoryPressureHandling: true,
    memoryThresholdMB: 512, // Start evicting when heap uses > 512MB
    memoryCheckIntervalMs: 30 * 1000, // Check memory every 30 seconds
    memoryPressureEvictionRatio: 0.25, // Evict 25% of entries under memory pressure
    criticalMemoryRatio: 0.9, // 90% of threshold triggers aggressive eviction
    enableMetrics: true,
    metricsFlushIntervalMs: 60 * 1000, // Log metrics every minute
    enableFileWatching: false, // Disabled by default (can be expensive)
    fileWatchDebounceMs: 300,
    watchPatterns: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.json'],
    ignorePatterns: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
    enableDependencyTracking: true,
    autoPersist: true,
};
// ============================================================================
// Cache Manager
// ============================================================================
export class CacheManager extends EventEmitter {
    config;
    cacheConfig;
    // Cache instances
    llmCache;
    fileCache;
    embeddingCache;
    searchCache;
    // Metrics
    metricsInterval = null;
    memoryCheckInterval = null;
    initialized = false;
    // Memory pressure tracking
    lastMemoryCheck = null;
    memoryPressureHistory = [];
    maxMemoryPressureHistory = 100;
    // File dependency tracking
    fileDependencies = new Map(); // file -> dependent cache keys
    reverseDependencies = new Map(); // cache key -> files it depends on
    // Pending invalidations (for debouncing)
    pendingInvalidations = new Set();
    invalidationTimeout = null;
    // Metrics history for trending
    metricsHistory = [];
    maxMetricsHistory = 60; // Keep 60 samples
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_MANAGER_CONFIG, ...config };
        this.cacheConfig = getCacheConfig();
        // Initialize caches with config
        this.llmCache = getLLMResponseCache({
            enabled: this.cacheConfig.llmResponse.enabled,
            ttlMs: this.cacheConfig.llmResponse.ttlMs,
            maxEntries: this.cacheConfig.llmResponse.maxEntries,
            similarityThreshold: this.cacheConfig.llmResponse.similarityThreshold,
            minTokensToCache: this.cacheConfig.llmResponse.minTokensToCache,
            costPerMillion: this.cacheConfig.llmResponse.costPerMillion,
            persistToDisk: this.cacheConfig.llmResponse.persistToDisk,
        });
        this.fileCache = getFileContentCache({
            enabled: this.cacheConfig.fileContent.enabled,
            ttlMs: this.cacheConfig.fileContent.ttlMs,
            maxEntries: this.cacheConfig.fileContent.maxEntries,
            maxFileSizeBytes: this.cacheConfig.fileContent.maxFileSizeBytes,
        });
        this.embeddingCache = getEmbeddingCache({
            enabled: this.cacheConfig.embedding.enabled,
            ttlMs: this.cacheConfig.embedding.ttlMs,
            maxEntries: this.cacheConfig.embedding.maxEntries,
            dimension: this.cacheConfig.embedding.dimension,
            modelName: this.cacheConfig.embedding.modelName,
            persistToDisk: this.cacheConfig.embedding.persistToDisk,
        });
        this.searchCache = getSearchResultsCache({
            enabled: this.cacheConfig.searchResults.enabled,
            ttlMs: this.cacheConfig.searchResults.ttlMs,
            maxEntries: this.cacheConfig.searchResults.maxEntries,
            invalidateOnFileChange: this.cacheConfig.searchResults.invalidateOnFileChange,
        });
        // Setup event forwarding
        this.setupEventForwarding();
    }
    /**
     * Initialize the cache manager
     */
    async initialize() {
        if (this.initialized)
            return;
        if (this.config.enableMetrics && this.config.metricsFlushIntervalMs > 0) {
            this.metricsInterval = setInterval(() => {
                this.logMetrics();
            }, this.config.metricsFlushIntervalMs);
        }
        // Start memory pressure monitoring
        if (this.config.enableMemoryPressureHandling && this.config.memoryCheckIntervalMs > 0) {
            this.startMemoryMonitoring();
        }
        this.initialized = true;
        this.emit('initialized');
        logger.debug('Cache manager initialized');
    }
    /**
     * Start periodic memory pressure monitoring
     */
    startMemoryMonitoring() {
        if (this.memoryCheckInterval) {
            clearInterval(this.memoryCheckInterval);
        }
        // Initial check
        this.checkMemoryPressure();
        // Periodic checks
        this.memoryCheckInterval = setInterval(() => {
            this.checkMemoryPressure();
        }, this.config.memoryCheckIntervalMs);
        logger.debug(`Memory pressure monitoring started (interval: ${this.config.memoryCheckIntervalMs}ms, threshold: ${this.config.memoryThresholdMB}MB)`);
    }
    /**
     * Stop memory pressure monitoring
     */
    stopMemoryMonitoring() {
        if (this.memoryCheckInterval) {
            clearInterval(this.memoryCheckInterval);
            this.memoryCheckInterval = null;
        }
    }
    // ===========================================================================
    // LLM Response Cache API
    // ===========================================================================
    /**
     * Get cached LLM response
     */
    async getLLMResponse(messages, model, options) {
        if (!this.config.enabled)
            return null;
        return this.llmCache.get(messages, model, options);
    }
    /**
     * Cache LLM response
     */
    setLLMResponse(messages, response, model, options) {
        if (!this.config.enabled)
            return;
        this.llmCache.set(messages, response, model, options);
    }
    // ===========================================================================
    // File Content Cache API
    // ===========================================================================
    /**
     * Read file with caching
     */
    async readFile(filePath, encoding = 'utf-8') {
        if (!this.config.enabled) {
            const fs = await import('fs/promises');
            const content = await fs.readFile(filePath, encoding);
            const crypto = await import('crypto');
            const hash = crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
            return { content, cached: false, hash };
        }
        return this.fileCache.read(filePath, encoding);
    }
    /**
     * Invalidate file cache
     */
    invalidateFile(filePath) {
        this.fileCache.invalidate(filePath);
        // Also invalidate search results that may reference this file
        this.searchCache.invalidateForFile(filePath);
    }
    /**
     * Check if file has changed
     */
    async hasFileChanged(filePath) {
        return this.fileCache.hasChanged(filePath);
    }
    /**
     * Get file hash without reading content
     */
    getFileHash(filePath) {
        return this.fileCache.getHash(filePath);
    }
    // ===========================================================================
    // Embedding Cache API
    // ===========================================================================
    /**
     * Get cached embedding
     */
    getEmbedding(content, model) {
        if (!this.config.enabled)
            return null;
        return this.embeddingCache.get(content, model);
    }
    /**
     * Cache embedding
     */
    setEmbedding(content, embedding, metadata) {
        if (!this.config.enabled)
            return;
        this.embeddingCache.set(content, embedding, metadata);
    }
    /**
     * Get or compute embedding
     */
    async getOrComputeEmbedding(content, computeFn, metadata) {
        if (!this.config.enabled) {
            const embedding = await computeFn();
            return { embedding, cached: false };
        }
        return this.embeddingCache.getOrCompute(content, computeFn, metadata);
    }
    // ===========================================================================
    // Search Results Cache API
    // ===========================================================================
    /**
     * Get cached search results
     */
    getSearchResults(query, searchType, options) {
        if (!this.config.enabled)
            return null;
        return this.searchCache.get(query, searchType, options);
    }
    /**
     * Cache search results
     */
    setSearchResults(query, searchType, results, options) {
        if (!this.config.enabled)
            return;
        this.searchCache.set(query, searchType, results, options);
    }
    /**
     * Get or compute search results
     */
    async getOrComputeSearchResults(query, searchType, computeFn, options) {
        if (!this.config.enabled) {
            const results = await computeFn();
            return { results, cached: false };
        }
        return this.searchCache.getOrCompute(query, searchType, computeFn, options);
    }
    // ===========================================================================
    // Global Operations
    // ===========================================================================
    /**
     * Invalidate all caches for a file
     */
    invalidateForFile(filePath) {
        this.fileCache.invalidate(filePath);
        this.searchCache.invalidateForFile(filePath);
        this.embeddingCache.invalidateSource(filePath);
        // Invalidate dependent cache entries
        if (this.config.enableDependencyTracking) {
            this.invalidateDependents(filePath);
        }
        this.emit('invalidate:file', { path: filePath });
    }
    /**
     * Invalidate all caches for a directory
     */
    invalidateForDirectory(dirPath) {
        this.fileCache.invalidateDirectory(dirPath);
        // Invalidate all files in directory that have dependencies
        if (this.config.enableDependencyTracking) {
            const normalizedDir = dirPath.endsWith('/') ? dirPath : dirPath + '/';
            for (const [filePath] of this.fileDependencies) {
                if (filePath.startsWith(normalizedDir)) {
                    this.invalidateDependents(filePath);
                }
            }
        }
        this.emit('invalidate:directory', { path: dirPath });
    }
    // ===========================================================================
    // Dependency Tracking
    // ===========================================================================
    /**
     * Register a cache entry's dependency on files
     */
    registerDependency(cacheKey, filePaths) {
        if (!this.config.enableDependencyTracking)
            return;
        // Store reverse dependencies (cache key -> files)
        if (!this.reverseDependencies.has(cacheKey)) {
            this.reverseDependencies.set(cacheKey, new Set());
        }
        const keyDeps = this.reverseDependencies.get(cacheKey);
        for (const filePath of filePaths) {
            // Store file -> cache keys mapping
            if (!this.fileDependencies.has(filePath)) {
                this.fileDependencies.set(filePath, new Set());
            }
            this.fileDependencies.get(filePath).add(cacheKey);
            keyDeps.add(filePath);
        }
    }
    /**
     * Unregister a cache entry's dependencies
     */
    unregisterDependency(cacheKey) {
        if (!this.config.enableDependencyTracking)
            return;
        const fileDeps = this.reverseDependencies.get(cacheKey);
        if (fileDeps) {
            for (const filePath of fileDeps) {
                const keys = this.fileDependencies.get(filePath);
                if (keys) {
                    keys.delete(cacheKey);
                    if (keys.size === 0) {
                        this.fileDependencies.delete(filePath);
                    }
                }
            }
            this.reverseDependencies.delete(cacheKey);
        }
    }
    /**
     * Invalidate all cache entries that depend on a file
     */
    invalidateDependents(filePath) {
        const dependentKeys = this.fileDependencies.get(filePath);
        if (!dependentKeys || dependentKeys.size === 0)
            return;
        logger.debug(`Invalidating ${dependentKeys.size} cache entries dependent on ${filePath}`);
        for (const cacheKey of dependentKeys) {
            // Emit event for each invalidated entry
            this.emit('invalidate:dependent', { cacheKey, filePath });
        }
        // Clear the dependency mapping
        this.fileDependencies.delete(filePath);
    }
    /**
     * Queue a file for invalidation (debounced)
     */
    queueInvalidation(filePath) {
        this.pendingInvalidations.add(filePath);
        // Clear existing timeout
        if (this.invalidationTimeout) {
            clearTimeout(this.invalidationTimeout);
        }
        // Set new debounced timeout
        this.invalidationTimeout = setTimeout(() => {
            this.processPendingInvalidations();
        }, this.config.fileWatchDebounceMs);
    }
    /**
     * Process all pending invalidations
     */
    processPendingInvalidations() {
        if (this.pendingInvalidations.size === 0)
            return;
        const filePaths = Array.from(this.pendingInvalidations);
        this.pendingInvalidations.clear();
        logger.debug(`Processing ${filePaths.length} pending invalidations`);
        for (const filePath of filePaths) {
            this.invalidateForFile(filePath);
        }
        this.emit('invalidations:processed', { count: filePaths.length, files: filePaths });
    }
    /**
     * Get dependency statistics
     */
    getDependencyStats() {
        let totalDependencies = 0;
        for (const deps of this.fileDependencies.values()) {
            totalDependencies += deps.size;
        }
        return {
            trackedFiles: this.fileDependencies.size,
            trackedCacheKeys: this.reverseDependencies.size,
            totalDependencies,
        };
    }
    /**
     * Clear all caches
     */
    clearAll() {
        this.llmCache.clear();
        this.fileCache.clear();
        this.embeddingCache.clear();
        this.searchCache.clear();
        this.emit('clear:all');
        logger.debug('All caches cleared');
    }
    /**
     * Get unified statistics
     */
    getStats() {
        const llmStats = this.llmCache.getStats();
        const fileStats = this.fileCache.getStats();
        const embeddingStats = this.embeddingCache.getStats();
        const searchStats = this.searchCache.getStats();
        const totalEntries = llmStats.totalEntries +
            fileStats.totalEntries +
            embeddingStats.totalEntries +
            searchStats.totalEntries;
        const totalHits = llmStats.hits + fileStats.hits + embeddingStats.hits + searchStats.hits;
        const totalMisses = llmStats.misses + fileStats.misses + embeddingStats.misses + searchStats.misses;
        const total = totalHits + totalMisses;
        // Estimate time saved (ms)
        const estimatedTimeSaved = llmStats.hits * 2000 + // LLM calls ~2s
            fileStats.hits * 10 + // File reads ~10ms
            embeddingStats.computationsSaved * 50 + // Embeddings ~50ms
            (searchStats.hits + searchStats.deduplicatedRequests) * 500; // Searches ~500ms
        // Estimate memory usage (very rough)
        const memoryUsageEstimate = llmStats.totalEntries * 5000 + // ~5KB per LLM response
            fileStats.totalCacheSize + // Actual bytes
            embeddingStats.totalEntries * 1536 + // 384 floats * 4 bytes
            searchStats.totalEntries * 2000; // ~2KB per search result
        return {
            llmResponse: llmStats,
            fileContent: fileStats,
            embedding: embeddingStats,
            searchResults: searchStats,
            overall: {
                totalEntries,
                totalHits,
                totalMisses,
                hitRate: total > 0 ? totalHits / total : 0,
                estimatedCostSaved: llmStats.estimatedCostSaved,
                estimatedTimeSaved,
                memoryUsageEstimate,
            },
        };
    }
    /**
     * Format statistics for display
     */
    formatStats() {
        const stats = this.getStats();
        const memMB = (stats.overall.memoryUsageEstimate / (1024 * 1024)).toFixed(2);
        const timeSec = (stats.overall.estimatedTimeSaved / 1000).toFixed(1);
        const lines = [
            '='.repeat(50),
            'CACHE MANAGER STATISTICS',
            '='.repeat(50),
            '',
            `Overall Hit Rate: ${(stats.overall.hitRate * 100).toFixed(1)}%`,
            `Total Entries: ${stats.overall.totalEntries}`,
            `Est. Cost Saved: $${stats.overall.estimatedCostSaved.toFixed(4)}`,
            `Est. Time Saved: ${timeSec}s`,
            `Memory Usage: ~${memMB}MB`,
            '',
            '-'.repeat(50),
            this.llmCache.formatStats(),
            '',
            '-'.repeat(50),
            this.fileCache.formatStats(),
            '',
            '-'.repeat(50),
            this.embeddingCache.formatStats(),
            '',
            '-'.repeat(50),
            this.searchCache.formatStats(),
            '='.repeat(50),
        ];
        return lines.join('\n');
    }
    /**
     * Check memory pressure and evict if needed
     * Uses actual process heap memory (process.memoryUsage().heapUsed)
     */
    checkMemoryPressure() {
        if (!this.config.enableMemoryPressureHandling)
            return;
        const memoryUsage = process.memoryUsage();
        const heapUsedMB = memoryUsage.heapUsed / (1024 * 1024);
        const heapTotalMB = memoryUsage.heapTotal / (1024 * 1024);
        const thresholdMB = this.config.memoryThresholdMB;
        const criticalThresholdMB = thresholdMB * this.config.criticalMemoryRatio;
        // Store last check for trending
        this.lastMemoryCheck = {
            heapUsedMB,
            timestamp: Date.now(),
        };
        // Check if we're approaching the threshold
        if (heapUsedMB >= criticalThresholdMB) {
            // Critical memory pressure - aggressive eviction
            logger.debug(`Critical memory pressure detected: ${heapUsedMB.toFixed(2)}MB / ${heapTotalMB.toFixed(2)}MB (threshold: ${thresholdMB}MB)`);
            this.recordMemoryPressureEvent(heapUsedMB, 'critical_eviction');
            this.evictLowPriorityEntries('critical');
        }
        else if (heapUsedMB >= thresholdMB * 0.75) {
            // Warning level - moderate eviction
            logger.debug(`Memory pressure warning: ${heapUsedMB.toFixed(2)}MB / ${heapTotalMB.toFixed(2)}MB (threshold: ${thresholdMB}MB)`);
            this.recordMemoryPressureEvent(heapUsedMB, 'warning_eviction');
            this.evictLowPriorityEntries('warning');
        }
    }
    /**
     * Record a memory pressure event for history tracking
     */
    recordMemoryPressureEvent(heapUsedMB, action) {
        this.memoryPressureHistory.push({
            timestamp: Date.now(),
            heapUsedMB,
            action,
        });
        // Trim history
        if (this.memoryPressureHistory.length > this.maxMemoryPressureHistory) {
            this.memoryPressureHistory.shift();
        }
    }
    /**
     * Get memory pressure history for diagnostics
     */
    getMemoryPressureHistory() {
        return [...this.memoryPressureHistory];
    }
    /**
     * Get current memory status
     */
    getMemoryStatus() {
        const memoryUsage = process.memoryUsage();
        const heapUsedMB = memoryUsage.heapUsed / (1024 * 1024);
        const heapTotalMB = memoryUsage.heapTotal / (1024 * 1024);
        const thresholdMB = this.config.memoryThresholdMB;
        const usagePercent = (heapUsedMB / thresholdMB) * 100;
        let status = 'normal';
        if (heapUsedMB >= thresholdMB * this.config.criticalMemoryRatio) {
            status = 'critical';
        }
        else if (heapUsedMB >= thresholdMB * 0.75) {
            status = 'warning';
        }
        return {
            heapUsedMB,
            heapTotalMB,
            thresholdMB,
            usagePercent,
            status,
        };
    }
    /**
     * Evict low-priority entries to reduce memory
     * Uses LRU strategy across all caches
     */
    evictLowPriorityEntries(level) {
        const evictionRatio = level === 'critical'
            ? this.config.memoryPressureEvictionRatio * 2 // Double eviction for critical
            : this.config.memoryPressureEvictionRatio;
        const stats = this.getStats();
        const totalEntries = stats.overall.totalEntries;
        const entriesToEvict = Math.ceil(totalEntries * evictionRatio);
        if (entriesToEvict === 0) {
            logger.debug('No entries to evict');
            return;
        }
        logger.debug(`Memory pressure eviction: evicting ~${entriesToEvict} entries (${(evictionRatio * 100).toFixed(0)}% of ${totalEntries})`);
        // Calculate how many to evict from each cache proportionally
        const llmEntries = stats.llmResponse.totalEntries;
        const fileEntries = stats.fileContent.totalEntries;
        const embeddingEntries = stats.embedding.totalEntries;
        const searchEntries = stats.searchResults.totalEntries;
        const llmToEvict = Math.ceil(entriesToEvict * (llmEntries / Math.max(1, totalEntries)));
        const fileToEvict = Math.ceil(entriesToEvict * (fileEntries / Math.max(1, totalEntries)));
        const embeddingToEvict = Math.ceil(entriesToEvict * (embeddingEntries / Math.max(1, totalEntries)));
        const searchToEvict = Math.ceil(entriesToEvict * (searchEntries / Math.max(1, totalEntries)));
        // Emit memory pressure event with details for other components to react
        // The individual caches have internal LRU eviction mechanisms that will be
        // triggered when new items are added. Here we emit the event for:
        // 1. External components to react (e.g., reduce caching aggressiveness)
        // 2. Logging and monitoring
        this.emit('memory:pressure', {
            level,
            heapUsedMB: this.lastMemoryCheck?.heapUsedMB || 0,
            thresholdMB: this.config.memoryThresholdMB,
            entriesToEvict,
            evictionRatio,
            breakdown: {
                llm: llmToEvict,
                file: fileToEvict,
                embedding: embeddingToEvict,
                search: searchToEvict,
            },
        });
        // For critical pressure, clear less frequently accessed caches
        if (level === 'critical') {
            // Clear search cache first (most volatile, least critical)
            this.searchCache.clear();
            logger.debug('Cleared search cache due to critical memory pressure');
            // If still critical after clearing search, clear embeddings
            const currentUsage = process.memoryUsage();
            const currentMB = currentUsage.heapUsed / (1024 * 1024);
            if (currentMB >= this.config.memoryThresholdMB * this.config.criticalMemoryRatio) {
                this.embeddingCache.clear();
                logger.debug('Cleared embedding cache due to critical memory pressure');
            }
        }
        // Force garbage collection hint (if available via --expose-gc flag)
        if (typeof global.gc === 'function') {
            try {
                global.gc();
                logger.debug('Triggered garbage collection after cache eviction');
            }
            catch {
                // gc not exposed or failed
            }
        }
        logger.debug('Memory pressure eviction complete');
    }
    /**
     * Force immediate memory pressure check and eviction if needed
     */
    forceMemoryCheck() {
        const statusBefore = this.getMemoryStatus();
        this.checkMemoryPressure();
        const statusAfter = this.getMemoryStatus();
        return {
            evicted: statusBefore.status !== 'normal',
            status: statusAfter,
        };
    }
    /**
     * Update memory threshold at runtime
     */
    setMemoryThreshold(thresholdMB) {
        if (thresholdMB < 64) {
            logger.debug('Memory threshold too low, setting to minimum of 64MB');
            thresholdMB = 64;
        }
        this.config.memoryThresholdMB = thresholdMB;
        logger.debug(`Memory threshold updated to ${thresholdMB}MB`);
        this.emit('config:update', { memoryThresholdMB: thresholdMB });
    }
    /**
     * Update memory check interval at runtime
     */
    setMemoryCheckInterval(intervalMs) {
        if (intervalMs < 1000) {
            logger.debug('Memory check interval too short, setting to minimum of 1000ms');
            intervalMs = 1000;
        }
        this.config.memoryCheckIntervalMs = intervalMs;
        // Restart monitoring with new interval
        if (this.config.enableMemoryPressureHandling && this.initialized) {
            this.stopMemoryMonitoring();
            this.startMemoryMonitoring();
        }
        logger.debug(`Memory check interval updated to ${intervalMs}ms`);
        this.emit('config:update', { memoryCheckIntervalMs: intervalMs });
    }
    /**
     * Log metrics periodically
     */
    logMetrics() {
        const stats = this.getStats();
        // Store metrics for trending
        this.metricsHistory.push({ timestamp: Date.now(), stats });
        if (this.metricsHistory.length > this.maxMetricsHistory) {
            this.metricsHistory.shift();
        }
        if (stats.overall.totalHits > 0 || stats.overall.totalMisses > 0) {
            logger.debug(`Cache stats: hit rate ${(stats.overall.hitRate * 100).toFixed(1)}%, entries ${stats.overall.totalEntries}`);
        }
        this.checkMemoryPressure();
    }
    /**
     * Get metrics trend over time
     */
    getMetricsTrend() {
        return {
            hitRateTrend: this.metricsHistory.map(m => m.stats.overall.hitRate),
            entriesTrend: this.metricsHistory.map(m => m.stats.overall.totalEntries),
            memoryTrend: this.metricsHistory.map(m => m.stats.overall.memoryUsageEstimate),
            timestamps: this.metricsHistory.map(m => m.timestamp),
        };
    }
    /**
     * Get detailed metrics report
     */
    getDetailedMetrics() {
        const current = this.getStats();
        const trend = this.getMetricsTrend();
        const dependencies = this.getDependencyStats();
        // Calculate health status
        const issues = [];
        let status = 'healthy';
        // Check hit rate
        if (current.overall.hitRate < 0.3 && (current.overall.totalHits + current.overall.totalMisses) > 100) {
            issues.push(`Low hit rate: ${(current.overall.hitRate * 100).toFixed(1)}%`);
            status = 'warning';
        }
        // Check memory usage
        const memoryMB = current.overall.memoryUsageEstimate / (1024 * 1024);
        if (memoryMB > this.config.memoryThresholdMB * 0.9) {
            issues.push(`High memory usage: ${memoryMB.toFixed(1)}MB`);
            status = status === 'warning' ? 'critical' : 'warning';
        }
        // Check eviction rate
        const totalEvictions = current.llmResponse.evictions +
            current.fileContent.evictions +
            current.embedding.evictions +
            current.searchResults.evictions;
        const totalOperations = current.overall.totalHits + current.overall.totalMisses;
        if (totalOperations > 0 && totalEvictions / totalOperations > 0.5) {
            issues.push(`High eviction rate: ${(totalEvictions / totalOperations * 100).toFixed(1)}%`);
            status = status === 'warning' ? 'critical' : 'warning';
        }
        return {
            current,
            trend,
            dependencies,
            health: { status, issues },
        };
    }
    /**
     * Export all cache data for persistence
     */
    async exportCacheState() {
        const metrics = this.getStats();
        const fileDeps = [];
        for (const [file, keys] of this.fileDependencies) {
            fileDeps.push([file, Array.from(keys)]);
        }
        const reverseDeps = [];
        for (const [key, files] of this.reverseDependencies) {
            reverseDeps.push([key, Array.from(files)]);
        }
        return {
            timestamp: Date.now(),
            metrics,
            dependencies: { fileDeps, reverseDeps },
        };
    }
    /**
     * Import cache state from persistence
     */
    async importCacheState(state) {
        // Restore dependencies
        for (const [file, keys] of state.dependencies.fileDeps) {
            this.fileDependencies.set(file, new Set(keys));
        }
        for (const [key, files] of state.dependencies.reverseDeps) {
            this.reverseDependencies.set(key, new Set(files));
        }
        logger.debug(`Imported cache state with ${state.dependencies.fileDeps.length} file dependencies`);
    }
    /**
     * Setup event forwarding from individual caches
     */
    setupEventForwarding() {
        this.llmCache.on('cache:hit', (data) => this.emit('llm:hit', data));
        this.llmCache.on('cache:miss', (data) => this.emit('llm:miss', data));
        this.fileCache.on('cache:hit', (data) => this.emit('file:hit', data));
        this.fileCache.on('cache:miss', (data) => this.emit('file:miss', data));
        this.embeddingCache.on('cache:hit', (data) => this.emit('embedding:hit', data));
        this.searchCache.on('cache:hit', (data) => this.emit('search:hit', data));
        this.searchCache.on('cache:miss', (data) => this.emit('search:miss', data));
    }
    /**
     * Get individual cache instances for advanced usage
     */
    getCaches() {
        return {
            llm: this.llmCache,
            file: this.fileCache,
            embedding: this.embeddingCache,
            search: this.searchCache,
        };
    }
    // ===========================================================================
    // Disk Persistence
    // ===========================================================================
    /**
     * Persist all cache state to disk for long sessions
     */
    async persistToDisk(basePath = '.codebuddy/cache') {
        const fs = await import('fs/promises');
        const path = await import('path');
        const { existsSync, mkdirSync } = await import('fs');
        // Ensure directory exists
        if (!existsSync(basePath)) {
            mkdirSync(basePath, { recursive: true });
        }
        try {
            // Export cache state
            const state = await this.exportCacheState();
            // Write state file
            const statePath = path.join(basePath, 'cache-manager-state.json');
            await fs.writeFile(statePath, JSON.stringify(state, null, 2));
            // Write metrics history
            const metricsPath = path.join(basePath, 'metrics-history.json');
            await fs.writeFile(metricsPath, JSON.stringify({
                history: this.metricsHistory,
                savedAt: Date.now(),
            }, null, 2));
            logger.debug(`Cache state persisted to ${basePath}`);
            this.emit('persist:complete', { path: basePath });
        }
        catch (error) {
            logger.debug('Failed to persist cache state', error instanceof Error ? { message: error.message } : undefined);
            this.emit('persist:error', { error });
        }
    }
    /**
     * Restore cache state from disk
     */
    async restoreFromDisk(basePath = '.codebuddy/cache') {
        const fs = await import('fs/promises');
        const path = await import('path');
        const { existsSync } = await import('fs');
        const statePath = path.join(basePath, 'cache-manager-state.json');
        if (!existsSync(statePath)) {
            logger.debug('No persisted cache state found');
            return false;
        }
        try {
            // Read state file
            const stateContent = await fs.readFile(statePath, 'utf-8');
            const state = JSON.parse(stateContent);
            // Check if state is too old (more than 24 hours)
            const maxAge = 24 * 60 * 60 * 1000;
            if (Date.now() - state.timestamp > maxAge) {
                logger.debug('Persisted cache state is too old, ignoring');
                return false;
            }
            // Import state
            await this.importCacheState(state);
            // Try to restore metrics history
            const metricsPath = path.join(basePath, 'metrics-history.json');
            if (existsSync(metricsPath)) {
                const metricsContent = await fs.readFile(metricsPath, 'utf-8');
                const metricsData = JSON.parse(metricsContent);
                // Only restore recent metrics (last hour)
                const oneHourAgo = Date.now() - 60 * 60 * 1000;
                this.metricsHistory = (metricsData.history || []).filter((m) => m.timestamp > oneHourAgo);
            }
            logger.debug(`Cache state restored from ${basePath}`);
            this.emit('restore:complete', { path: basePath });
            return true;
        }
        catch (error) {
            logger.debug('Failed to restore cache state', error instanceof Error ? { message: error.message } : undefined);
            this.emit('restore:error', { error });
            return false;
        }
    }
    /**
     * Clean up old cache files
     */
    async cleanupOldCacheFiles(basePath = '.codebuddy/cache', maxAgeDays = 7) {
        const fs = await import('fs/promises');
        const pathModule = await import('path');
        const { existsSync } = await import('fs');
        if (!existsSync(basePath)) {
            return 0;
        }
        let cleaned = 0;
        const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
        const now = Date.now();
        try {
            const files = await fs.readdir(basePath);
            for (const file of files) {
                const filePath = pathModule.join(basePath, file);
                const stats = await fs.stat(filePath);
                if (now - stats.mtime.getTime() > maxAgeMs) {
                    await fs.unlink(filePath);
                    cleaned++;
                }
            }
            if (cleaned > 0) {
                logger.debug(`Cleaned up ${cleaned} old cache files`);
            }
        }
        catch (error) {
            logger.debug('Failed to cleanup old cache files', error instanceof Error ? { message: error.message } : undefined);
        }
        return cleaned;
    }
    /**
     * Dispose resources
     */
    async dispose() {
        // Cancel pending invalidations
        if (this.invalidationTimeout) {
            clearTimeout(this.invalidationTimeout);
            this.invalidationTimeout = null;
        }
        if (this.metricsInterval) {
            clearInterval(this.metricsInterval);
            this.metricsInterval = null;
        }
        // Stop memory monitoring
        this.stopMemoryMonitoring();
        // Auto-persist if enabled
        if (this.config.autoPersist) {
            await this.persistToDisk();
        }
        this.llmCache.dispose();
        this.fileCache.dispose();
        this.embeddingCache.dispose();
        this.searchCache.dispose();
        // Clear dependency tracking
        this.fileDependencies.clear();
        this.reverseDependencies.clear();
        this.pendingInvalidations.clear();
        this.metricsHistory = [];
        this.removeAllListeners();
        this.initialized = false;
        logger.debug('Cache manager disposed');
    }
}
// ============================================================================
// Singleton
// ============================================================================
let instance = null;
export function getCacheManager(config) {
    if (!instance) {
        instance = new CacheManager(config);
    }
    return instance;
}
export async function initializeCacheManager(config) {
    const manager = getCacheManager(config);
    await manager.initialize();
    return manager;
}
export async function resetCacheManager() {
    if (instance) {
        await instance.dispose();
    }
    instance = null;
}
//# sourceMappingURL=cache-manager.js.map