/**
 * Registry Cache Manager
 *
 * Manages local caching of registry metadata with TTL and staleness handling.
 * Uses filesystem-based JSON cache with atomic writes.
 *
 * @module registry/cache/cache-manager
 * @version 2.0.0
 */
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
/**
 * Default configuration
 */
const DEFAULT_CONFIG = {
    cacheDir: join(homedir(), '.specverse', 'cache'),
    ttl: 24 * 60 * 60 * 1000, // 24 hours
    version: '2.0.0',
    enabled: true
};
/**
 * Registry Cache Manager
 *
 * Provides filesystem-based caching for registry metadata.
 */
export class CacheManager {
    config;
    cacheFile;
    constructor(config) {
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.cacheFile = join(this.config.cacheDir, 'registry-metadata.json');
    }
    /**
     * Read cache from disk
     *
     * @returns Cached data or null if not found/expired
     */
    async read() {
        if (!this.config.enabled) {
            return null;
        }
        try {
            const content = await fs.readFile(this.cacheFile, 'utf8');
            const cached = JSON.parse(content);
            // Validate cache structure
            if (!cached.metadata || !cached.data) {
                return null;
            }
            // Check version compatibility
            if (cached.metadata.version !== this.config.version) {
                return null;
            }
            return cached;
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                // Cache file doesn't exist
                return null;
            }
            // Other errors (permission, invalid JSON, etc.)
            return null;
        }
    }
    /**
     * Write cache to disk
     *
     * @param data - Registry data to cache
     * @param endpoint - Registry endpoint URL
     */
    async write(data, endpoint) {
        if (!this.config.enabled) {
            return;
        }
        try {
            // Ensure cache directory exists
            await fs.mkdir(this.config.cacheDir, { recursive: true });
            const now = new Date();
            const expiresAt = new Date(now.getTime() + this.config.ttl);
            const cached = {
                metadata: {
                    createdAt: now.toISOString(),
                    expiresAt: expiresAt.toISOString(),
                    version: this.config.version,
                    endpoint
                },
                data
            };
            // Atomic write with tmp file + rename
            const tmpFile = `${this.cacheFile}.tmp`;
            await fs.writeFile(tmpFile, JSON.stringify(cached, null, 2), 'utf8');
            await fs.rename(tmpFile, this.cacheFile);
        }
        catch (error) {
            // Silently fail on cache write errors
            // Cache is optional, don't break the application
        }
    }
    /**
     * Check if cache is valid (not expired)
     *
     * @param cached - Cached data to check
     * @returns True if cache is still valid
     */
    isValid(cached) {
        const now = new Date();
        const expiresAt = new Date(cached.metadata.expiresAt);
        return now < expiresAt;
    }
    /**
     * Check if cache is stale (expired but still usable as fallback)
     *
     * @param cached - Cached data to check
     * @returns True if cache is expired
     */
    isStale(cached) {
        return !this.isValid(cached);
    }
    /**
     * Get cache age in milliseconds
     *
     * @param cached - Cached data to check
     * @returns Age in milliseconds
     */
    getAge(cached) {
        const now = new Date();
        const createdAt = new Date(cached.metadata.createdAt);
        return now.getTime() - createdAt.getTime();
    }
    /**
     * Get time until expiration in milliseconds
     *
     * @param cached - Cached data to check
     * @returns Time until expiration (negative if expired)
     */
    getTimeToExpiration(cached) {
        const now = new Date();
        const expiresAt = new Date(cached.metadata.expiresAt);
        return expiresAt.getTime() - now.getTime();
    }
    /**
     * Clear cache
     */
    async clear() {
        if (!this.config.enabled) {
            return;
        }
        try {
            await fs.unlink(this.cacheFile);
        }
        catch (error) {
            if (error.code !== 'ENOENT') {
                // Only throw if error is not "file not found"
                throw error;
            }
        }
    }
    /**
     * Check if cache exists
     *
     * @returns True if cache file exists
     */
    async exists() {
        if (!this.config.enabled) {
            return false;
        }
        try {
            await fs.access(this.cacheFile);
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Get cache file path
     *
     * @returns Absolute path to cache file
     */
    getCacheFile() {
        return this.cacheFile;
    }
    /**
     * Get cache configuration
     *
     * @returns Current cache configuration
     */
    getConfig() {
        return { ...this.config };
    }
    /**
     * Update cache configuration
     *
     * @param config - Partial configuration to merge
     */
    updateConfig(config) {
        this.config = { ...this.config, ...config };
        // Update cache file path if directory changed
        if (config.cacheDir) {
            this.cacheFile = join(config.cacheDir, 'registry-metadata.json');
        }
    }
}
/**
 * Cache error class
 */
export class CacheError extends Error {
    cause;
    constructor(message, cause) {
        super(message);
        this.cause = cause;
        this.name = 'CacheError';
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CacheError);
        }
    }
}
//# sourceMappingURL=cache-manager.js.map