/**
 * Registry Offline Handler
 *
 * Implements cache-first strategy with graceful fallback for offline scenarios.
 * Wraps RegistryClient and CacheManager to provide unified online/offline access.
 *
 * @module registry/offline/offline-handler
 * @version 2.0.0
 */
import { RegistryClient, RegistryFetchError } from '../client/registry-client.js';
import { CacheManager } from '../cache/cache-manager.js';
import { createIssue, withSuggestions, ErrorCodes } from '../types/index.js';
/**
 * Registry Offline Handler
 *
 * Provides unified access to registry metadata with offline-first strategy:
 * 1. Try to fetch from network
 * 2. If network fails, use fresh cache
 * 3. If no fresh cache, use stale cache as fallback (with warning)
 * 4. If no cache at all, throw error
 */
export class OfflineHandler {
    client;
    cache;
    config;
    constructor(config = {}) {
        this.config = {
            registryClient: config.registryClient || {},
            cache: config.cache || {},
            offlineMode: config.offlineMode || false,
            allowStaleCache: config.allowStaleCache !== undefined ? config.allowStaleCache : true
        };
        this.client = new RegistryClient(this.config.registryClient);
        this.cache = new CacheManager(this.config.cache);
    }
    /**
     * Fetch all factories with offline-first strategy
     *
     * @returns Fetch result with data, source, and issues
     */
    async fetchFactories() {
        const issues = [];
        // Step 1: Try network (unless in offline mode)
        if (!this.config.offlineMode) {
            try {
                const data = await this.client.fetchFactories();
                // Cache successful response
                await this.cache.write(data, this.client.getEndpoint());
                return {
                    data,
                    source: 'network',
                    issues: []
                };
            }
            catch (error) {
                // Network failed, add diagnostic issue
                if (error instanceof RegistryFetchError) {
                    if (error.isTimeout()) {
                        issues.push(createIssue(ErrorCodes.NETWORK_ERROR, 'warning', 'manifest', 'Registry request timed out', 'The registry request timed out. Falling back to cached metadata.'));
                    }
                    else if (error.isNetworkError()) {
                        issues.push(createIssue(ErrorCodes.NETWORK_ERROR, 'warning', 'manifest', 'Network error', 'Failed to connect to registry. Falling back to cached metadata.'));
                    }
                    else {
                        issues.push(createIssue(ErrorCodes.NETWORK_ERROR, 'warning', 'manifest', 'Registry fetch failed', `Registry returned error: ${error.message}. Falling back to cached metadata.`));
                    }
                }
            }
        }
        else {
            // Offline mode - intentionally skip network
            issues.push(createIssue('OFFLINE-001', 'info', 'manifest', 'Offline mode enabled', 'Running in offline mode. Using cached metadata only.'));
        }
        // Step 2: Try fresh cache
        const cached = await this.cache.read();
        if (cached) {
            if (this.cache.isValid(cached)) {
                return {
                    data: cached.data,
                    source: 'cache-fresh',
                    issues
                };
            }
            // Step 3: Try stale cache (if allowed)
            if (this.config.allowStaleCache) {
                const age = this.cache.getAge(cached);
                const ageHours = Math.floor(age / (1000 * 60 * 60));
                const ageMinutes = Math.floor((age % (1000 * 60 * 60)) / (1000 * 60));
                issues.push(withSuggestions(createIssue(ErrorCodes.STALE_CACHE, 'warning', 'manifest', 'Using stale cache', `Cache is ${ageHours}h ${ageMinutes}m old. Metadata may be outdated.`), [
                    'Run with network access to refresh cache',
                    'Check if factory versions have been updated',
                    'Consider factory deprecation warnings'
                ]));
                return {
                    data: cached.data,
                    source: 'cache-stale',
                    issues
                };
            }
        }
        // Step 4: No cache available - fail
        throw new OfflineError('No registry metadata available', 'network-unavailable-no-cache', issues);
    }
    /**
     * Get specific factory by name
     *
     * @param name - Factory name
     * @returns Factory metadata or null if not found
     */
    async getFactory(name) {
        const result = await this.fetchFactories();
        // Search for factory in results
        const factory = result.data.results.find(f => f.name === name);
        return factory || null;
    }
    /**
     * Search factories
     *
     * Note: Search functionality requires network access.
     * Falls back to local filtering of cached data when offline.
     *
     * @param query - Search query
     * @returns Search results
     */
    async search(query) {
        // Try network search first (unless offline mode)
        if (!this.config.offlineMode) {
            try {
                return await this.client.search(query);
            }
            catch (error) {
                // Network failed, fall back to local search
            }
        }
        // Offline fallback: simple local search
        const result = await this.fetchFactories();
        const lowerQuery = query.toLowerCase();
        const matches = result.data.results.filter(factory => {
            return (factory.name.toLowerCase().includes(lowerQuery) ||
                factory.displayName.toLowerCase().includes(lowerQuery) ||
                factory.description.toLowerCase().includes(lowerQuery) ||
                factory.tags.some(tag => tag.toLowerCase().includes(lowerQuery)));
        });
        return {
            query,
            results: matches.map(factory => ({
                factory,
                score: 1.0, // Local search doesn't have scoring
                match: 'fuzzy'
            })),
            suggestions: []
        };
    }
    /**
     * Get all capabilities
     *
     * @returns Capabilities response
     */
    async getCapabilities() {
        // Try network first (unless offline mode)
        if (!this.config.offlineMode) {
            try {
                return await this.client.getCapabilities();
            }
            catch (error) {
                // Network failed, fall back to extracting from cache
            }
        }
        // Offline fallback: extract capabilities from cached factories
        const result = await this.fetchFactories();
        // Map capabilities to factories
        const capabilityMap = new Map();
        for (const factory of result.data.results) {
            for (const capability of factory.capabilities) {
                if (!capabilityMap.has(capability)) {
                    capabilityMap.set(capability, []);
                }
                capabilityMap.get(capability).push(factory.name);
            }
        }
        return {
            capabilities: Array.from(capabilityMap.entries()).map(([name, factories]) => ({
                name,
                factories,
                count: factories.length
            }))
        };
    }
    /**
     * Check if registry is accessible
     *
     * @returns True if network access is available
     */
    async checkHealth() {
        if (this.config.offlineMode) {
            return false;
        }
        return await this.client.checkHealth();
    }
    /**
     * Clear cache
     */
    async clearCache() {
        await this.cache.clear();
    }
    /**
     * Get cache status
     *
     * @returns Cache status information
     */
    async getCacheStatus() {
        const exists = await this.cache.exists();
        if (!exists) {
            return { exists: false, valid: false };
        }
        const cached = await this.cache.read();
        if (!cached) {
            return { exists: true, valid: false };
        }
        return {
            exists: true,
            valid: this.cache.isValid(cached),
            age: this.cache.getAge(cached),
            timeToExpiration: this.cache.getTimeToExpiration(cached),
            createdAt: cached.metadata.createdAt,
            expiresAt: cached.metadata.expiresAt
        };
    }
    /**
     * Get registry client endpoint
     *
     * @returns Registry endpoint URL
     */
    getEndpoint() {
        return this.client.getEndpoint();
    }
    /**
     * Get cache file path
     *
     * @returns Absolute path to cache file
     */
    getCacheFile() {
        return this.cache.getCacheFile();
    }
    /**
     * Enable offline mode
     */
    enableOfflineMode() {
        this.config.offlineMode = true;
    }
    /**
     * Disable offline mode
     */
    disableOfflineMode() {
        this.config.offlineMode = false;
    }
    /**
     * Check if offline mode is enabled
     *
     * @returns True if offline mode is enabled
     */
    isOfflineMode() {
        return this.config.offlineMode;
    }
}
/**
 * Offline error
 *
 * Thrown when registry data is unavailable both from network and cache.
 */
export class OfflineError extends Error {
    code;
    issues;
    constructor(message, code, issues) {
        super(message);
        this.code = code;
        this.issues = issues;
        this.name = 'OfflineError';
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, OfflineError);
        }
    }
    /**
     * Get user-friendly error message
     */
    getUserMessage() {
        return `${this.message}\n\nSuggestions:\n- Check your internet connection\n- Run with network access to populate cache\n- Use --offline flag with existing cache`;
    }
}
//# sourceMappingURL=offline-handler.js.map