/**
 * Enterprise Performance Optimizer - Advanced caching, memory management, and optimization
 * Multi-layer caching with intelligent eviction, compression, and performance monitoring
 */
// @ts-expect-error - LRU cache version compatibility
import LRUCache from 'lru-cache';
import { EventEmitter } from 'events';
import * as zlib from 'zlib';
import { promisify } from 'util';
import { handleError } from '../../utils/common.js';
import { getLogger } from '../../core/logger.js';
const logger = getLogger('performance-optimizer');
const gzip = promisify(zlib.gzip);
const gunzip = promisify(zlib.gunzip);
// Multi-Level Cache with Intelligent Management
export class IntelligentCache extends EventEmitter {
    constructor(options) {
        super();
        this.l2Cache = new Map(); // Compressed cache
        this.accessPattern = new Map(); // Track access patterns
        this.compressionThreshold = options.compressionThreshold || 1024; // 1KB
        this.l1Cache = new LRUCache({
            max: options.maxSize,
            maxAge: options.maxAge || 1000 * 60 * 30, // 30 minutes default
            sizeCalculation: (entry) => entry.size,
            dispose: (value, key) => this.onEviction(key, value, 'l1'),
        });
        this.metrics = {
            hits: 0,
            misses: 0,
            hitRate: 0,
            totalSize: 0,
            entryCount: 0,
            avgEntrySize: 0,
            evictions: 0,
            compressionRatio: 0,
        };
        this.startCleanupProcess(options.cleanupInterval || 60000); // 1 minute
    }
    async get(key, context) {
        const span = this.createSpan('cache-get', context);
        try {
            // Record access pattern
            this.recordAccess(key);
            // Try L1 cache first
            const l1Entry = this.l1Cache.get(key);
            if (l1Entry) {
                l1Entry.lastAccessed = Date.now();
                l1Entry.accessCount++;
                this.metrics.hits++;
                this.updateMetrics();
                this.finishSpan(span, { hit: true, level: 'l1', size: l1Entry.size });
                return l1Entry.value;
            }
            // Try L2 cache (compressed)
            const l2Entry = this.l2Cache.get(key);
            if (l2Entry) {
                const decompressed = await this.decompress(l2Entry.value);
                const value = JSON.parse(decompressed.toString());
                // Promote to L1
                const entry = {
                    value,
                    compressed: false,
                    size: decompressed.length,
                    createdAt: l2Entry.createdAt,
                    lastAccessed: Date.now(),
                    accessCount: l2Entry.accessCount + 1,
                    tags: l2Entry.tags,
                    ttl: l2Entry.ttl,
                };
                this.l1Cache.set(key, entry);
                this.l2Cache.delete(key);
                this.metrics.hits++;
                this.updateMetrics();
                this.finishSpan(span, { hit: true, level: 'l2', size: entry.size });
                return value;
            }
            this.metrics.misses++;
            this.updateMetrics();
            this.finishSpan(span, { hit: false });
            return undefined;
        }
        catch (error) {
            this.finishSpan(span, { error: error.message });
            throw handleError(error, 'cache-get');
        }
    }
    async set(key, value, options = {}, context) {
        const span = this.createSpan('cache-set', context);
        try {
            const serialized = JSON.stringify(value);
            const size = Buffer.byteLength(serialized);
            const entry = {
                value,
                compressed: false,
                size,
                createdAt: Date.now(),
                lastAccessed: Date.now(),
                accessCount: 0,
                tags: options.tags || [],
                ttl: options.ttl,
            };
            // Decide whether to compress based on size or force flag
            if (size > this.compressionThreshold || options.forceCompress) {
                await this.setCompressed(key, entry, context);
            }
            else {
                this.l1Cache.set(key, entry);
            }
            this.updateMetrics();
            this.finishSpan(span, { size, compressed: entry.compressed });
            this.emit('entry-set', { key, size, compressed: entry.compressed });
        }
        catch (error) {
            this.finishSpan(span, { error: error.message });
            throw handleError(error, 'cache-set');
        }
    }
    async setCompressed(key, entry, _context) {
        const serialized = JSON.stringify(entry.value);
        const compressed = await this.compress(Buffer.from(serialized));
        const compressedEntry = {
            value: compressed,
            compressed: true,
            size: compressed.length,
            createdAt: entry.createdAt,
            lastAccessed: entry.lastAccessed,
            accessCount: entry.accessCount,
            tags: entry.tags,
            ttl: entry.ttl,
        };
        this.l2Cache.set(key, compressedEntry);
        // Update compression ratio metric
        const originalSize = Buffer.byteLength(serialized);
        const ratio = compressed.length / originalSize;
        this.metrics.compressionRatio = (this.metrics.compressionRatio + ratio) / 2;
    }
    delete(key) {
        const l1Deleted = this.l1Cache.delete(key);
        const l2Deleted = this.l2Cache.delete(key);
        if (l1Deleted || l2Deleted) {
            this.updateMetrics();
            this.emit('entry-deleted', { key });
            return true;
        }
        return false;
    }
    clear() {
        this.l1Cache.clear();
        this.l2Cache.clear();
        this.accessPattern.clear();
        this.resetMetrics();
        this.emit('cache-cleared');
    }
    // Tag-based cache invalidation
    invalidateByTag(tag) {
        let invalidated = 0;
        // L1 cache
        for (const [key, entry] of this.l1Cache.entries()) {
            if (entry.tags.includes(tag)) {
                this.l1Cache.delete(key);
                invalidated++;
            }
        }
        // L2 cache
        for (const [key, entry] of this.l2Cache.entries()) {
            if (entry.tags.includes(tag)) {
                this.l2Cache.delete(key);
                invalidated++;
            }
        }
        this.updateMetrics();
        this.emit('tag-invalidation', { tag, count: invalidated });
        return invalidated;
    }
    // Batch operations for performance
    async mget(keys, context) {
        const span = this.createSpan('cache-mget', context);
        const results = new Map();
        try {
            const promises = keys.map(async (key) => {
                const value = await this.get(key, context);
                if (value !== undefined) {
                    results.set(key, value);
                }
            });
            await Promise.all(promises);
            this.finishSpan(span, { requestedKeys: keys.length, foundKeys: results.size });
            return results;
        }
        catch (error) {
            this.finishSpan(span, { error: error.message });
            throw handleError(error, 'cache-mget');
        }
    }
    async mset(entries, context) {
        const span = this.createSpan('cache-mset', context);
        try {
            const promises = entries.map(({ key, value, options }) => this.set(key, value, options, context));
            await Promise.all(promises);
            this.finishSpan(span, { entryCount: entries.length });
        }
        catch (error) {
            this.finishSpan(span, { error: error.message });
            throw handleError(error, 'cache-mset');
        }
    }
    // Intelligent prefetching based on access patterns
    getPrefetchCandidates(limit = 10) {
        const patterns = Array.from(this.accessPattern.entries())
            .map(([key, times]) => ({
            key,
            frequency: times.length,
            lastAccess: Math.max(...times),
            pattern: this.analyzeAccessPattern(times),
        }))
            .filter((item) => item.pattern.predictable && !this.l1Cache.has(item.key))
            .sort((a, b) => b.frequency - a.frequency)
            .slice(0, limit)
            .map((item) => item.key);
        return patterns;
    }
    analyzeAccessPattern(times) {
        if (times.length < 3)
            return { predictable: false };
        const intervals = [];
        for (let i = 1; i < times.length; i++) {
            intervals.push(times[i] - times[i - 1]);
        }
        const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
        const variance = intervals.reduce((sum, interval) => sum + Math.pow(interval - avgInterval, 2), 0) /
            intervals.length;
        const stdDev = Math.sqrt(variance);
        // Consider predictable if standard deviation is less than 20% of average
        const predictable = stdDev < avgInterval * 0.2;
        return { predictable, interval: predictable ? avgInterval : undefined };
    }
    recordAccess(key) {
        const times = this.accessPattern.get(key) || [];
        times.push(Date.now());
        // Keep only recent accesses (last 100)
        if (times.length > 100) {
            times.splice(0, times.length - 50);
        }
        this.accessPattern.set(key, times);
    }
    async compress(data) {
        return await gzip(data);
    }
    async decompress(data) {
        return await gunzip(data);
    }
    onEviction(key, entry, level) {
        this.metrics.evictions++;
        this.emit('entry-evicted', { key, level, size: entry.size });
        // If evicted from L1 and entry is frequently accessed, move to L2
        if (level === 'l1' && entry.accessCount > 5) {
            this.setCompressed(key, entry).catch((error) => {
                logger.warn('Failed to compress evicted entry', { key, error: error.message });
            });
        }
    }
    updateMetrics() {
        this.metrics.entryCount = this.l1Cache.size + this.l2Cache.size;
        this.metrics.totalSize = this.calculateTotalSize();
        this.metrics.avgEntrySize =
            this.metrics.entryCount > 0 ? this.metrics.totalSize / this.metrics.entryCount : 0;
        this.metrics.hitRate = this.metrics.hits / (this.metrics.hits + this.metrics.misses) || 0;
    }
    calculateTotalSize() {
        let size = 0;
        for (const entry of this.l1Cache.values()) {
            size += entry.size;
        }
        for (const entry of this.l2Cache.values()) {
            size += entry.size;
        }
        return size;
    }
    resetMetrics() {
        this.metrics = {
            hits: 0,
            misses: 0,
            hitRate: 0,
            totalSize: 0,
            entryCount: 0,
            avgEntrySize: 0,
            evictions: 0,
            compressionRatio: 0,
        };
    }
    startCleanupProcess(interval) {
        this.cleanupInterval = setInterval(() => {
            this.runCleanup();
        }, interval);
    }
    runCleanup() {
        const now = Date.now();
        let cleaned = 0;
        // Clean expired L2 entries
        for (const [key, entry] of this.l2Cache.entries()) {
            if (entry.ttl && now - entry.createdAt > entry.ttl) {
                this.l2Cache.delete(key);
                cleaned++;
            }
        }
        // Clean old access patterns
        for (const [key, times] of this.accessPattern.entries()) {
            const recentTimes = times.filter((time) => now - time < 24 * 60 * 60 * 1000); // 24 hours
            if (recentTimes.length === 0) {
                this.accessPattern.delete(key);
            }
            else if (recentTimes.length !== times.length) {
                this.accessPattern.set(key, recentTimes);
            }
        }
        if (cleaned > 0) {
            this.updateMetrics();
            this.emit('cleanup-completed', { entriesRemoved: cleaned });
        }
    }
    createSpan(operation, parentContext) {
        return {
            traceId: parentContext?.traceId || crypto.randomUUID(),
            spanId: crypto.randomUUID(),
            baggage: { ...parentContext?.baggage, cacheOperation: operation },
        };
    }
    finishSpan(span, data) {
        // Emit span data for observability
        this.emit('span-finished', { ...span, ...data });
    }
    getMetrics() {
        this.updateMetrics();
        return { ...this.metrics };
    }
    destroy() {
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
        }
        this.clear();
        this.removeAllListeners();
    }
}
// Performance Profiler for Operation Monitoring
export class PerformanceProfiler {
    constructor() {
        this.profiles = new Map();
        this.responseTimes = new Map();
    }
    async profile(operationName, operation, _context) {
        const startTime = Date.now();
        try {
            const result = await operation();
            this.recordSuccess(operationName, Date.now() - startTime);
            return result;
        }
        catch (error) {
            this.recordError(operationName, Date.now() - startTime);
            throw error;
        }
    }
    recordSuccess(operationName, duration) {
        this.updateProfile(operationName, duration, false);
    }
    recordError(operationName, duration) {
        this.updateProfile(operationName, duration, true);
    }
    updateProfile(operationName, duration, isError) {
        const profile = this.profiles.get(operationName) || {
            operationName,
            totalCalls: 0,
            totalTime: 0,
            avgTime: 0,
            minTime: Infinity,
            maxTime: 0,
            p50: 0,
            p95: 0,
            p99: 0,
            errorRate: 0,
            lastCall: 0,
        };
        profile.totalCalls++;
        profile.totalTime += duration;
        profile.avgTime = profile.totalTime / profile.totalCalls;
        profile.minTime = Math.min(profile.minTime, duration);
        profile.maxTime = Math.max(profile.maxTime, duration);
        profile.lastCall = Date.now();
        if (isError) {
            profile.errorRate =
                (profile.errorRate * (profile.totalCalls - 1) + 1) / profile.totalCalls;
        }
        else {
            profile.errorRate = (profile.errorRate * (profile.totalCalls - 1)) / profile.totalCalls;
        }
        // Track response times for percentile calculation
        const times = this.responseTimes.get(operationName) || [];
        times.push(duration);
        // Keep only recent measurements
        if (times.length > 1000) {
            times.splice(0, 500);
        }
        this.responseTimes.set(operationName, times);
        // Calculate percentiles
        const sortedTimes = times.slice().sort((a, b) => a - b);
        profile.p50 = sortedTimes[Math.floor(sortedTimes.length * 0.5)] || 0;
        profile.p95 = sortedTimes[Math.floor(sortedTimes.length * 0.95)] || 0;
        profile.p99 = sortedTimes[Math.floor(sortedTimes.length * 0.99)] || 0;
        this.profiles.set(operationName, profile);
    }
    getProfile(operationName) {
        return this.profiles.get(operationName);
    }
    getAllProfiles() {
        return Array.from(this.profiles.values());
    }
    getTopSlowestOperations(limit = 10) {
        return Array.from(this.profiles.values())
            .sort((a, b) => b.avgTime - a.avgTime)
            .slice(0, limit);
    }
    getHighErrorRateOperations(threshold = 0.05) {
        return Array.from(this.profiles.values())
            .filter((profile) => profile.errorRate > threshold)
            .sort((a, b) => b.errorRate - a.errorRate);
    }
    reset() {
        this.profiles.clear();
        this.responseTimes.clear();
    }
}
//# sourceMappingURL=performance-optimizer.js.map