/**
 * Context Cache Store - In-memory cache with TTL and LRU eviction
 * Stores packed file contents with session-based lifecycle management
 */
/** Default configuration */
const DEFAULT_MAX_ENTRIES = 100;
const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
const DEFAULT_PAGE_SIZE = 65536; // 64KB
/**
 * Context Cache Store singleton
 * Manages in-memory cache with TTL expiration and LRU eviction
 */
class ContextCacheStore {
    cache = new Map();
    maxEntries;
    defaultTTL;
    cleanupInterval = null;
    constructor(options = {}) {
        this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
        this.defaultTTL = options.defaultTTL ?? DEFAULT_TTL_MS;
        // Start periodic cleanup
        const cleanupMs = options.cleanupIntervalMs ?? 60000; // 1 minute
        this.cleanupInterval = setInterval(() => {
            this.cleanupExpired();
        }, cleanupMs);
        // Allow cleanup to not keep process alive
        if (this.cleanupInterval.unref) {
            this.cleanupInterval.unref();
        }
    }
    /**
     * Store packed content in cache
     */
    set(sessionId, content, metadata, ttl) {
        const now = Date.now();
        const entryTTL = ttl ?? this.defaultTTL;
        // Evict if at capacity
        if (this.cache.size >= this.maxEntries && !this.cache.has(sessionId)) {
            this.evictOldest();
        }
        const entry = {
            session_id: sessionId,
            created_at: now,
            accessed_at: now,
            ttl: entryTTL,
            content,
            metadata,
        };
        this.cache.set(sessionId, entry);
        return entry;
    }
    /**
     * Get cache entry by session ID
     */
    get(sessionId) {
        const entry = this.cache.get(sessionId);
        if (!entry) {
            return null;
        }
        // Check TTL expiration
        if (this.isExpired(entry)) {
            this.cache.delete(sessionId);
            return null;
        }
        // Update access time (LRU)
        entry.accessed_at = Date.now();
        return entry;
    }
    /**
     * Read content with pagination
     */
    read(sessionId, offset = 0, limit = DEFAULT_PAGE_SIZE) {
        const entry = this.get(sessionId);
        if (!entry) {
            return null;
        }
        const content = entry.content;
        const totalBytes = Buffer.byteLength(content, 'utf-8');
        // Handle byte-based offset for UTF-8
        // For simplicity, we use character-based slicing
        // This is approximate but works for most use cases
        const charOffset = Math.min(offset, content.length);
        const charLimit = Math.min(limit, content.length - charOffset);
        const pageContent = content.slice(charOffset, charOffset + charLimit);
        const endOffset = charOffset + pageContent.length;
        const hasMore = endOffset < content.length;
        return {
            content: pageContent,
            offset: charOffset,
            limit: charLimit,
            total_bytes: totalBytes,
            has_more: hasMore,
            next_offset: hasMore ? endOffset : null,
        };
    }
    /**
     * Release (delete) cache entry
     */
    release(sessionId) {
        const entry = this.cache.get(sessionId);
        if (!entry) {
            return { released: false, freed_bytes: 0 };
        }
        const freedBytes = entry.metadata.total_bytes;
        this.cache.delete(sessionId);
        return { released: true, freed_bytes: freedBytes };
    }
    /**
     * Get session status
     */
    getSessionStatus(sessionId) {
        const entry = this.cache.get(sessionId);
        if (!entry) {
            return { session_id: sessionId, exists: false };
        }
        // Check if expired
        if (this.isExpired(entry)) {
            this.cache.delete(sessionId);
            return { session_id: sessionId, exists: false };
        }
        const now = Date.now();
        const expiresAt = entry.created_at + entry.ttl;
        const ttlRemaining = Math.max(0, expiresAt - now);
        return {
            session_id: sessionId,
            exists: true,
            files: entry.metadata.files,
            file_count: entry.metadata.file_count,
            total_bytes: entry.metadata.total_bytes,
            created_at: new Date(entry.created_at).toISOString(),
            expires_at: new Date(expiresAt).toISOString(),
            accessed_at: new Date(entry.accessed_at).toISOString(),
            ttl_remaining_ms: ttlRemaining,
        };
    }
    /**
     * Get overall cache status
     */
    getStatus() {
        let totalBytes = 0;
        let oldest = null;
        let newest = null;
        for (const entry of this.cache.values()) {
            // Skip expired entries
            if (this.isExpired(entry)) {
                continue;
            }
            totalBytes += entry.metadata.total_bytes;
            if (!oldest || entry.created_at < oldest.created_at) {
                oldest = entry;
            }
            if (!newest || entry.created_at > newest.created_at) {
                newest = entry;
            }
        }
        return {
            entries: this.cache.size,
            total_bytes: totalBytes,
            oldest_session: oldest?.session_id ?? null,
            newest_session: newest?.session_id ?? null,
        };
    }
    /**
     * Cleanup expired entries
     */
    cleanupExpired() {
        let removed = 0;
        const now = Date.now();
        for (const [sessionId, entry] of this.cache.entries()) {
            if (this.isExpired(entry, now)) {
                this.cache.delete(sessionId);
                removed++;
            }
        }
        return { removed };
    }
    /**
     * Clear all cache entries
     */
    clear() {
        const count = this.cache.size;
        this.cache.clear();
        return { removed: count };
    }
    /**
     * Check if entry is expired
     */
    isExpired(entry, now) {
        const currentTime = now ?? Date.now();
        return currentTime > entry.created_at + entry.ttl;
    }
    /**
     * Evict oldest entry (LRU)
     */
    evictOldest() {
        let oldest = null;
        for (const [sessionId, entry] of this.cache.entries()) {
            if (!oldest || entry.accessed_at < oldest[1].accessed_at) {
                oldest = [sessionId, entry];
            }
        }
        if (oldest) {
            this.cache.delete(oldest[0]);
        }
    }
    /**
     * Stop cleanup timer (for graceful shutdown)
     */
    destroy() {
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
            this.cleanupInterval = null;
        }
    }
    /**
     * List all session IDs
     */
    listSessions() {
        return Array.from(this.cache.keys());
    }
    /**
     * Check if session exists and is valid
     */
    has(sessionId) {
        const entry = this.cache.get(sessionId);
        if (!entry)
            return false;
        if (this.isExpired(entry)) {
            this.cache.delete(sessionId);
            return false;
        }
        return true;
    }
}
// Singleton instance
let cacheInstance = null;
/**
 * Get the singleton cache instance
 */
export function getContextCacheStore(options) {
    if (!cacheInstance) {
        cacheInstance = new ContextCacheStore(options);
    }
    return cacheInstance;
}
/**
 * Reset the cache instance (for testing)
 */
export function resetContextCacheStore() {
    if (cacheInstance) {
        cacheInstance.destroy();
        cacheInstance = null;
    }
}
export { ContextCacheStore };
//# sourceMappingURL=context-cache-store.js.map