/**
 * Adapter Provider
 *
 * Factory that creates appropriate adapters based on configuration and
 * Redis availability. Supports both Redis-backed and in-memory implementations.
 *
 * Features:
 * - Automatic Redis availability detection
 * - Singleton pattern for adapter instances
 * - Health check support
 * - Graceful fallback to memory when Redis is unavailable
 *
 * @packageDocumentation
 */
import { createLogger } from '../logger.js';
import { getConfig } from '../config.js';
// Import memory implementations
import { createMemoryQueueAdapter } from './memory-queue.js';
import { createMemoryCacheAdapter } from './memory-cache.js';
import { createMemoryLockAdapter } from './memory-lock.js';
import { createMemorySessionStoreAdapter } from './memory-session.js';
import { createMemoryRateLimitAdapter } from './memory-ratelimit.js';
const logger = createLogger({ component: 'adapter-provider' });
/**
 * Default configuration
 */
const DEFAULT_CONFIG = {
    forceMemoryMode: false,
    redisConnectionTimeoutMs: 5000,
};
/**
 * Main adapter provider implementation
 */
class DefaultAdapterProvider {
    config;
    redisAvailable = null;
    redisCheckPromise = null;
    // Singleton instances
    queueAdapters = new Map();
    cacheAdapter = null;
    lockAdapter = null;
    sessionStoreAdapter = null;
    rateLimitAdapter = null;
    // Memory adapter instances for cleanup
    memoryCacheAdapter = null;
    memoryLockAdapter = null;
    memorySessionAdapter = null;
    memoryRateLimitAdapter = null;
    constructor(config) {
        this.config = { ...DEFAULT_CONFIG, ...config };
        // Check if lite mode is enabled
        const appConfig = getConfig();
        if (appConfig.lite?.enabled && appConfig.lite?.redisOptional) {
            logger.info({}, 'Lite mode enabled with optional Redis');
        }
    }
    /**
     * Get a queue adapter by name
     */
    getQueueAdapter(name) {
        let adapter = this.queueAdapters.get(name);
        if (!adapter) {
            if (this.shouldUseMemory()) {
                adapter = createMemoryQueueAdapter(name);
                logger.debug({ queueName: name }, 'Created memory queue adapter');
            }
            else {
                // Redis queue adapter would be created here
                // For now, fall back to memory
                adapter = createMemoryQueueAdapter(name);
                logger.debug({ queueName: name }, 'Created memory queue adapter (Redis not implemented in adapter)');
            }
            this.queueAdapters.set(name, adapter);
        }
        return adapter;
    }
    /**
     * Get the cache adapter
     */
    getCacheAdapter() {
        if (!this.cacheAdapter) {
            if (this.shouldUseMemory()) {
                this.memoryCacheAdapter = createMemoryCacheAdapter();
                this.cacheAdapter = this.memoryCacheAdapter;
                logger.debug({}, 'Created memory cache adapter');
            }
            else {
                // Redis cache adapter would be created here
                // For now, fall back to memory
                this.memoryCacheAdapter = createMemoryCacheAdapter();
                this.cacheAdapter = this.memoryCacheAdapter;
                logger.debug({}, 'Created memory cache adapter (Redis not implemented in adapter)');
            }
        }
        return this.cacheAdapter;
    }
    /**
     * Get the lock adapter
     */
    getLockAdapter() {
        if (!this.lockAdapter) {
            if (this.shouldUseMemory()) {
                this.memoryLockAdapter = createMemoryLockAdapter();
                this.lockAdapter = this.memoryLockAdapter;
                logger.debug({}, 'Created memory lock adapter');
            }
            else {
                // Redis lock adapter would be created here
                // For now, fall back to memory
                this.memoryLockAdapter = createMemoryLockAdapter();
                this.lockAdapter = this.memoryLockAdapter;
                logger.debug({}, 'Created memory lock adapter (Redis not implemented in adapter)');
            }
        }
        return this.lockAdapter;
    }
    /**
     * Get the session store adapter
     */
    getSessionStoreAdapter() {
        if (!this.sessionStoreAdapter) {
            if (this.shouldUseMemory()) {
                this.memorySessionAdapter = createMemorySessionStoreAdapter();
                this.sessionStoreAdapter = this.memorySessionAdapter;
                logger.debug({}, 'Created memory session store adapter');
            }
            else {
                // Redis session adapter would be created here
                // For now, fall back to memory
                this.memorySessionAdapter = createMemorySessionStoreAdapter();
                this.sessionStoreAdapter = this.memorySessionAdapter;
                logger.debug({}, 'Created memory session store adapter (Redis not implemented in adapter)');
            }
        }
        return this.sessionStoreAdapter;
    }
    /**
     * Get the rate limit adapter
     */
    getRateLimitAdapter() {
        if (!this.rateLimitAdapter) {
            if (this.shouldUseMemory()) {
                this.memoryRateLimitAdapter = createMemoryRateLimitAdapter();
                this.rateLimitAdapter = this.memoryRateLimitAdapter;
                logger.debug({}, 'Created memory rate limit adapter');
            }
            else {
                // Redis rate limit adapter would be created here
                // For now, fall back to memory
                this.memoryRateLimitAdapter = createMemoryRateLimitAdapter();
                this.rateLimitAdapter = this.memoryRateLimitAdapter;
                logger.debug({}, 'Created memory rate limit adapter (Redis not implemented in adapter)');
            }
        }
        return this.rateLimitAdapter;
    }
    /**
     * Check if Redis is available
     */
    isRedisAvailable() {
        // If we haven't checked yet, return false (conservative default)
        if (this.redisAvailable === null) {
            // Trigger async check
            void this.checkRedisAvailability();
            return false;
        }
        return this.redisAvailable;
    }
    /**
     * Get health status of the adapter system
     */
    async getHealthStatus() {
        const startTime = Date.now();
        const redisAvailable = await this.checkRedisAvailability();
        const latencyMs = Date.now() - startTime;
        const appConfig = getConfig();
        const liteMode = appConfig.lite?.enabled && appConfig.lite?.redisOptional;
        const useMemory = this.config.forceMemoryMode || liteMode || !redisAvailable;
        return {
            redis: {
                available: redisAvailable,
                latencyMs: redisAvailable ? latencyMs : undefined,
                error: !redisAvailable ? 'Redis connection failed or not configured' : undefined,
            },
            mode: useMemory ? 'memory' : 'redis',
        };
    }
    /**
     * Shutdown all adapters and clean up resources
     */
    async shutdown() {
        logger.info({}, 'Shutting down adapter provider');
        // Close all queue adapters
        for (const [name, adapter] of this.queueAdapters) {
            try {
                await adapter.close();
                logger.debug({ queueName: name }, 'Queue adapter closed');
            }
            catch (error) {
                logger.error({ error, queueName: name }, 'Error closing queue adapter');
            }
        }
        this.queueAdapters.clear();
        // Stop memory adapters
        if (this.memoryCacheAdapter) {
            this.memoryCacheAdapter.stop();
            this.memoryCacheAdapter = null;
        }
        if (this.memoryLockAdapter) {
            this.memoryLockAdapter.stop();
            this.memoryLockAdapter = null;
        }
        if (this.memorySessionAdapter) {
            this.memorySessionAdapter.stop();
            this.memorySessionAdapter = null;
        }
        if (this.memoryRateLimitAdapter) {
            this.memoryRateLimitAdapter.stop();
            this.memoryRateLimitAdapter = null;
        }
        // Clear references
        this.cacheAdapter = null;
        this.lockAdapter = null;
        this.sessionStoreAdapter = null;
        this.rateLimitAdapter = null;
        logger.info({}, 'Adapter provider shutdown complete');
    }
    /**
     * Determine if memory mode should be used
     */
    shouldUseMemory() {
        // Check forced memory mode
        if (this.config.forceMemoryMode) {
            return true;
        }
        // Check lite mode configuration
        const appConfig = getConfig();
        if (appConfig.lite?.enabled && appConfig.lite?.redisOptional) {
            // In lite mode with optional Redis, use memory if Redis is not available
            if (this.redisAvailable === false) {
                return true;
            }
            // If we haven't checked Redis yet, default to memory
            if (this.redisAvailable === null) {
                return true;
            }
        }
        return false;
    }
    /**
     * Check Redis availability
     */
    async checkRedisAvailability() {
        // Return cached result if available
        if (this.redisAvailable !== null) {
            return this.redisAvailable;
        }
        // Deduplicate concurrent checks
        if (this.redisCheckPromise) {
            return this.redisCheckPromise;
        }
        this.redisCheckPromise = this.doRedisCheck();
        try {
            this.redisAvailable = await this.redisCheckPromise;
        }
        finally {
            this.redisCheckPromise = null;
        }
        return this.redisAvailable;
    }
    /**
     * Perform actual Redis availability check
     */
    async doRedisCheck() {
        try {
            // Dynamically import Redis to avoid errors when not configured
            const { getRedis } = await import('../redis.js');
            const redis = getRedis();
            // Try a simple PING with timeout
            const pingPromise = redis.ping();
            const timeoutPromise = new Promise((_, reject) => {
                setTimeout(() => reject(new Error('Redis check timeout')), this.config.redisConnectionTimeoutMs);
            });
            const result = await Promise.race([pingPromise, timeoutPromise]);
            if (result === 'PONG') {
                logger.info({}, 'Redis is available');
                return true;
            }
            logger.warn({ result }, 'Unexpected Redis PING response');
            return false;
        }
        catch (error) {
            logger.info({ error: error instanceof Error ? error.message : 'Unknown error' }, 'Redis is not available, using memory adapters');
            return false;
        }
    }
}
// =============================================================================
// SINGLETON MANAGEMENT
// =============================================================================
/**
 * Singleton provider instance
 */
let providerInstance = null;
/**
 * Get the adapter provider singleton
 */
export function getAdapterProvider(config) {
    if (!providerInstance) {
        providerInstance = new DefaultAdapterProvider(config);
    }
    return providerInstance;
}
/**
 * Create a new adapter provider instance (for testing)
 */
export function createAdapterProvider(config) {
    return new DefaultAdapterProvider(config);
}
/**
 * Reset the singleton provider (for testing)
 */
export async function resetAdapterProvider() {
    if (providerInstance) {
        await providerInstance.shutdown();
        providerInstance = null;
    }
}
/**
 * Convenience function to get adapters directly
 */
export function getQueueAdapter(name) {
    return getAdapterProvider().getQueueAdapter(name);
}
export function getCacheAdapter() {
    return getAdapterProvider().getCacheAdapter();
}
export function getLockAdapter() {
    return getAdapterProvider().getLockAdapter();
}
export function getSessionStoreAdapter() {
    return getAdapterProvider().getSessionStoreAdapter();
}
export function getRateLimitAdapter() {
    return getAdapterProvider().getRateLimitAdapter();
}
//# sourceMappingURL=provider.js.map