/**
 * Advanced caching utility for Vue 3 applications
 * Supports TTL, memory limits, persistence, and cache invalidation
 */

import { ref, computed, onUnmounted } from 'vue';

export interface CacheOptions {
    ttl?: number; // Time to live in milliseconds
    maxSize?: number; // Maximum cache size
    persistent?: boolean; // Store in localStorage
    keyPrefix?: string; // Prefix for localStorage keys
}

interface CacheEntry<T> {
    data: T;
    timestamp: number;
    ttl?: number;
    hits: number;
    lastAccessed: number;
}

class CacheManager<T = any> {
    private cache = new Map<string, CacheEntry<T>>();
    private options: Required<CacheOptions>;
    private cleanupInterval?: number;

    constructor(options: CacheOptions = {}) {
        this.options = {
            ttl: 5 * 60 * 1000, // 5 minutes default
            maxSize: 100,
            persistent: false,
            keyPrefix: 'app_cache_',
            ...options
        };

        // Setup cleanup interval for expired entries
        this.cleanupInterval = window.setInterval(() => {
            this.cleanup();
        }, 60000); // Cleanup every minute

        // Load from localStorage if persistent
        if (this.options.persistent) {
            this.loadFromStorage();
        }
    }

    /**
     * Store data in cache
     */
    set(key: string, data: T, customTtl?: number): void {
        const now = Date.now();
        const ttl = customTtl || this.options.ttl;

        // Remove oldest entries if cache is full
        if (this.cache.size >= this.options.maxSize) {
            this.evictLRU();
        }

        const entry: CacheEntry<T> = {
            data,
            timestamp: now,
            ttl,
            hits: 0,
            lastAccessed: now
        };

        this.cache.set(key, entry);

        // Persist to localStorage if enabled
        if (this.options.persistent) {
            this.saveToStorage(key, entry);
        }
    }

    /**
     * Get data from cache
     */
    get(key: string): T | null {
        const entry = this.cache.get(key);

        if (!entry) {
            return null;
        }

        const now = Date.now();

        // Check if entry has expired
        if (entry.ttl && (now - entry.timestamp) > entry.ttl) {
            this.delete(key);
            return null;
        }

        // Update access statistics
        entry.hits++;
        entry.lastAccessed = now;

        return entry.data;
    }

    /**
     * Check if key exists and is not expired
     */
    has(key: string): boolean {
        return this.get(key) !== null;
    }

    /**
     * Delete specific key
     */
    delete(key: string): boolean {
        const deleted = this.cache.delete(key);

        if (deleted && this.options.persistent) {
            localStorage.removeItem(this.options.keyPrefix + key);
        }

        return deleted;
    }

    /**
     * Clear all cache entries
     */
    clear(): void {
        if (this.options.persistent) {
            // Remove all persistent entries
            Object.keys(localStorage).forEach(key => {
                if (key.startsWith(this.options.keyPrefix)) {
                    localStorage.removeItem(key);
                }
            });
        }

        this.cache.clear();
    }

    /**
     * Get cache statistics
     */
    getStats() {
        const entries = Array.from(this.cache.values());
        const now = Date.now();

        return {
            size: this.cache.size,
            maxSize: this.options.maxSize,
            expired: entries.filter(e => e.ttl && (now - e.timestamp) > e.ttl).length,
            totalHits: entries.reduce((sum, e) => sum + e.hits, 0),
            averageAge: entries.length > 0
                ? entries.reduce((sum, e) => sum + (now - e.timestamp), 0) / entries.length
                : 0
        };
    }

    /**
     * Remove expired entries
     */
    private cleanup(): void {
        const now = Date.now();
        const keysToDelete: string[] = [];

        this.cache.forEach((entry, key) => {
            if (entry.ttl && (now - entry.timestamp) > entry.ttl) {
                keysToDelete.push(key);
            }
        });

        keysToDelete.forEach(key => this.delete(key));
    }

    /**
     * Evict least recently used entry
     */
    private evictLRU(): void {
        let oldestKey = '';
        let oldestTime = Date.now();

        this.cache.forEach((entry, key) => {
            if (entry.lastAccessed < oldestTime) {
                oldestTime = entry.lastAccessed;
                oldestKey = key;
            }
        });

        if (oldestKey) {
            this.delete(oldestKey);
        }
    }

    /**
     * Save entry to localStorage
     */
    private saveToStorage(key: string, entry: CacheEntry<T>): void {
        try {
            const storageKey = this.options.keyPrefix + key;
            localStorage.setItem(storageKey, JSON.stringify(entry));
        } catch (error) {
            console.warn('Failed to save cache entry to localStorage:', error);
        }
    }

    /**
     * Load cache from localStorage
     */
    private loadFromStorage(): void {
        try {
            Object.keys(localStorage).forEach(storageKey => {
                if (storageKey.startsWith(this.options.keyPrefix)) {
                    const key = storageKey.replace(this.options.keyPrefix, '');
                    const entryData = localStorage.getItem(storageKey);

                    if (entryData) {
                        const entry: CacheEntry<T> = JSON.parse(entryData);
                        const now = Date.now();

                        // Check if entry is still valid
                        if (!entry.ttl || (now - entry.timestamp) <= entry.ttl) {
                            this.cache.set(key, entry);
                        } else {
                            // Remove expired entry from storage
                            localStorage.removeItem(storageKey);
                        }
                    }
                }
            });
        } catch (error) {
            console.warn('Failed to load cache from localStorage:', error);
        }
    }

    /**
     * Cleanup resources
     */
    destroy(): void {
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
        }
    }
}

// Global cache instances
const apiCache = new CacheManager({
    ttl: 5 * 60 * 1000, // 5 minutes
    maxSize: 50,
    persistent: true,
    keyPrefix: 'api_'
});

const uiCache = new CacheManager({
    ttl: 30 * 60 * 1000, // 30 minutes  
    maxSize: 100,
    persistent: false,
    keyPrefix: 'ui_'
});

/**
 * Composable for reactive caching
 */
export function useCache<T>(
    key: string,
    fetcher: () => Promise<T>,
    options: CacheOptions = {}
) {
    const cache = options.persistent !== false ? apiCache : uiCache;

    const data = ref<T | null>(cache.get(key));
    const isLoading = ref(false);
    const error = ref<Error | null>(null);

    const isStale = computed(() => {
        return data.value === null;
    });

    const refresh = async (force = false): Promise<T | null> => {
        if (isLoading.value) return data.value;

        if (!force && !isStale.value) {
            return data.value;
        }

        isLoading.value = true;
        error.value = null;

        try {
            const result = await fetcher();
            cache.set(key, result, options.ttl);
            data.value = result;
            return result;
        } catch (err) {
            error.value = err instanceof Error ? err : new Error(String(err));
            throw err;
        } finally {
            isLoading.value = false;
        }
    };

    const invalidate = () => {
        cache.delete(key);
        data.value = null;
    };

    // Auto-fetch if data is stale
    if (isStale.value) {
        refresh();
    }

    // Cleanup on unmount
    onUnmounted(() => {
        // Cache persists beyond component lifecycle
    });

    return {
        data: computed(() => data.value),
        isLoading: computed(() => isLoading.value),
        error: computed(() => error.value),
        isStale,
        refresh,
        invalidate
    };
}

/**
 * Preload data into cache
 */
export async function preloadCache<T>(
    key: string,
    fetcher: () => Promise<T>,
    options: CacheOptions = {}
): Promise<void> {
    const cache = options.persistent !== false ? apiCache : uiCache;

    if (!cache.has(key)) {
        try {
            const data = await fetcher();
            cache.set(key, data, options.ttl);
        } catch (error) {
            console.warn(`Failed to preload cache for key: ${key}`, error);
        }
    }
}

/**
 * Cache invalidation utilities
 */
export const cacheUtils = {
    invalidatePattern: (pattern: string) => {
        [apiCache, uiCache].forEach(cache => {
            Array.from(cache['cache'].keys()).forEach(key => {
                if (key.includes(pattern)) {
                    cache.delete(key);
                }
            });
        });
    },

    invalidateAll: () => {
        apiCache.clear();
        uiCache.clear();
    },

    getStats: () => ({
        api: apiCache.getStats(),
        ui: uiCache.getStats()
    })
};

// Export instances for direct use
export { apiCache, uiCache };
export default CacheManager; 