/**
 * TTS Factory Module
 * Manages TTS handler instances and coordinates TTS functionality
 */
import { BaseModule } from './base-module.js';

class TTSFactoryModule extends BaseModule {
    /**
     * Create a new TTS factory
     */
    constructor() {
        super('tts-factory', 'TTS Factory');
        
        this.dependencies = [
            'persistence-manager', 
            'localization',
            'game-config',
            'browser-tts',   // Browser TTS handler
            'kokoro-tts',    // Kokoro TTS handler
            'elevenlabs-tts',// ElevenLabs TTS handler
            'openai-tts'     // OpenAI TTS handler
        ];
        this.handlers = {};
        this.initStatus = {};
        this.activeHandler = null;
        this.ttsAvailable = false;
        this.speed = 1; // Speech speed multiplier. 1.0 is normal speed.
        this.language = 'en_US';
        this.voice = '';
        this.volume = 1.0;
        
        // IndexedDB Cache Configuration
        this.db = null; // Will hold the DB connection
        this.dbName = 'ttsAudioCacheDB';
        this.storeName = 'audioCacheStore';
        this.dbVersion = 3;
        this.currentCacheSize = 0; // Track current size in bytes
        this.maxCacheSizeBytes = 100 * 1024 * 1024; // 100 MB by default
        this.cacheInitialized = false;
        this.cacheStatus = 'uninitialized'; // uninitialized, initializing, ready, error
        
        // Bind methods to this instance
        this.bindMethods([
            'initialize', 
            'registerHandler',
            'initializeHandler',
            'setActiveHandler',
            'getHandler',
            'getActiveHandler',
            'getAvailableHandlers',
            'speak',
            'stop',
            'pause',
            'resume',
            'fadeOut',
            'getVoices',
            'getPreference',
            'isSpeaking',
            'configure',
            'preloadSpeech',
            'generateSpeechHash',
            'speakPreloaded',
            'getCachedSpeech',
            'manageCacheSize',
            'cacheSpeech',
            'isSpeechCached',
            '_initializeDB',
            '_getDBItem',
            '_putDBItem',
            '_deleteDBItem',
            '_calculateTotalCacheSize',
            '_getAllDBItemsSortedByAccess',
            '_getDBItemOnly',
            '_generateHash',
            'initiatePreferredHandler',
            'attemptFallbackHandler',
            'initializeCache',
            'setupEvents',
            'reportProgress',
            'loadPreferences',
            'registerHandlers',
            'initializeHandlerSystem',
            'debugLogAllRegisteredModules',
            'debugTTSHandlers',
            'emitProcessState',
            'getEffectiveVoiceId',
            'disableAfterCurrentPlayback',
            'getHandlerStatuses',
            'getVoicesForHandler',
            'refreshHandlerStatus'
        ]);
        
        // Listen for kokoro:ready event
        document.addEventListener('kokoro:ready', (event) => {
            if (event.detail && typeof event.detail.success === 'boolean') {
                console.log('TTS Factory: Received kokoro:ready event with success =', event.detail.success);
                this.initStatus['kokoro-tts'] = event.detail.success;
                
                // If this is the current active handler or we don't have an active handler yet,
                // try to activate Kokoro if it's now ready
                if ((this.activeHandler === 'kokoro-tts' || !this.activeHandler) && event.detail.success) {
                    // Only activate Kokoro when it was explicitly selected.
                    const ttsEnabled = this.getPreference('tts', 'enabled', false);
                    const preferredHandler = this.getPreference('tts', 'preferred_handler', 'none');
                    if (ttsEnabled && preferredHandler === 'kokoro-tts') {
                        this.setActiveHandler('kokoro-tts');
                    }
                }
                
                // Update overall TTS availability
                this.updateTTSAvailability();
            }
        });

        document.addEventListener('tts:speechCompleted', () => {
            const persistenceManager = this.getModule('persistence-manager');
            const enabled = persistenceManager?.getPreference('tts', 'enabled', false);
            if (!enabled && this.activeHandler) {
                this.setActiveHandler('none');
            }
        });
        
        // Listen for handler availability changes
        document.addEventListener('tts:handler:availabilityChanged', (event) => {
            if (event && event.detail) {
                const { handlerId, available } = event.detail;
                console.log(`TTS Factory: Handler ${handlerId} availability changed to ${available}`);
                this.updateTTSAvailability();
            }
        });
    }
    
    /**
     * Initialize the TTS factory module
     * @returns {Promise<boolean>} - Promise resolves with initialization success
     */
    async initialize() {
        try {
            console.log('TTS Factory: Initializing');
            
            // Initialize cache
            this.reportProgress(20, 'Initializing TTS cache');
            const cacheInitialized = await this.initializeCache();
            if (cacheInitialized) {
                console.log('TTS Factory: Cache initialized successfully');
            } else {
                console.warn('TTS Factory: Cache initialization failed, continuing without cache');
            }
            
            // Load preferences
            this.reportProgress(40, 'Loading TTS preferences');
            const preferences = await this.loadPreferences();
            
            // Check for TTS handlers
            this.reportProgress(60, 'Finding TTS handlers');
            await this.registerHandlers();
            
            // Set default status
            this.ttsAvailable = false;
            
            // Set up event handlers - do this before initializing handlers
            // so we can listen for events during initialization
            this.setupEvents();
            
            // Initialize preferred or fallback handler
            this.reportProgress(80, 'Initializing TTS handler');
            await this.initializeHandlerSystem();
            
            // Apply configuration from preferences
            if (preferences) {
                console.log('TTS Factory: Applying saved configuration');
                // Apply speed setting
                this.configure({ speed: preferences.speed });
                
                // Update TTS availability based on active handler
                this.updateTTSAvailability();
            }
            
            // Debug: Log all registered modules and handlers
            this.debugLogAllRegisteredModules();
            this.debugTTSHandlers();
            
            this.reportProgress(100, 'TTS Factory initialized');
            console.log(`TTS Factory: Initialization complete, TTS available: ${this.ttsAvailable}`);
            
            // TTS is optional; the client must continue to function without it.
            return true;
        } catch (error) {
            console.error('TTS Factory: Initialization error:', error);
            return true;
        }
    }
    
    /**
     * Register event handlers for TTS system
     */
    setupEvents() {
        // Listen for TTS handler state changes
        document.addEventListener('tts:handler-state-changed', (event) => {
            if (event.detail && event.detail.handler) {
                const handlerId = event.detail.handler;
                const ready = event.detail.ready === true;
                
                console.log(`TTS Factory: Handler ${handlerId} reported state change, ready = ${ready}`);
                
                // Update handler initialization status
                if (this.handlers[handlerId]) {
                    this.initStatus[handlerId] = ready;
                    this.updateTTSAvailability();
                    
                    // If this is our active handler and it's no longer ready, try fallback
                    if (this.activeHandler === handlerId && !ready) {
                        console.warn(`TTS Factory: Active handler ${handlerId} is no longer ready, falling back`);
                        this.attemptFallbackHandler();
                    }
                }
            }
        });
        
        // Listen for speed change events from UI
        document.addEventListener('tts:speed:change', (event) => {
            if (event.detail && typeof event.detail.speed === 'number') {
                this.configure({ speed: event.detail.speed });
                console.log(`TTS Factory: Speed updated to ${this.speed} from UI event`);
            }
        });

        document.addEventListener('game:config', (event) => {
            const language = event.detail?.metadata?.language || event.detail?.locale;
            if (language) {
                this.configure({ language, persistLanguage: false });
            }
        });

        // Listen for kokoro error events
        document.addEventListener('kokoro:error', (event) => {
            console.error('TTS Factory: Received kokoro error event:', event.detail);
            if (this.handlers['kokoro-tts']) {
                this.initStatus['kokoro-tts'] = false;
                this.updateTTSAvailability();

                // If kokoro was our active handler, try fallback
                if (this.activeHandler === 'kokoro-tts') {
                    console.warn('TTS Factory: Kokoro handler failed, falling back');
                    this.attemptFallbackHandler();
                }
            }
        });
    }
    
    /**
     * Initialize a specific TTS handler
     * @param {string} id - Handler ID to initialize
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initializeHandler(id) {
        if (!this.handlers[id]) {
            console.error(`TTS Factory: Handler ${id} not found, cannot initialize`);
            return false;
        }
        
        try {
            console.log(`TTS Factory: Initializing handler ${id}`);
            
            const handler = this.handlers[id];
            console.log(`TTS Factory: Handler ${id} object:`, handler);
            
            // Check if handler has initialize method
            if (typeof handler.initialize !== 'function') {
                console.error(`TTS Factory: Handler ${id} does not have an initialize method`);
                this.initStatus[id] = false;
                return false;
            }
            
            // Call the handler's initialize method
            console.log(`TTS Factory: Calling initialize method on ${id} handler`);
            const result = await handler.initialize();
            console.log(`TTS Factory: ${id} initialize() returned:`, result);
            
            // Store initialization result
            this.initStatus[id] = result;
            
            // Double-check the handler's isReady flag
            // Note: some handlers may return true from initialize() before they're fully ready
            // (e.g., Kokoro continues loading the model asynchronously)
            if (result && typeof handler.isReady === 'boolean') {
                console.log(`TTS Factory: Handler ${id} has isReady = ${handler.isReady}`);
                
                // If handler is still loading after initialize(), set up a listener
                if (result === true && handler.isReady === false && handler.state === 'INITIALIZING') {
                    console.log(`TTS Factory: Handler ${id} is still initializing, waiting for completion`);
                    
                    // Wait up to 5 seconds for the handler to become ready or error out
                    const readyTimeout = setTimeout(() => {
                        console.warn(`TTS Factory: Handler ${id} did not become ready in time`);
                        // If the handler didn't explicitly fail, we'll still consider it as potentially available
                    }, 5000);
                    
                    // Check every second if the handler is ready
                    const checkInterval = setInterval(() => {
                        if (handler.isReady === true || handler.state === 'FINISHED') {
                            clearInterval(checkInterval);
                            clearTimeout(readyTimeout);
                            console.log(`TTS Factory: Handler ${id} is now ready`);
                            this.initStatus[id] = true;
                            this.updateTTSAvailability();
                        } else if (handler.state === 'ERROR') {
                            clearInterval(checkInterval);
                            clearTimeout(readyTimeout);
                            console.warn(`TTS Factory: Handler ${id} failed to initialize (state=ERROR)`);
                            this.initStatus[id] = false;
                            this.updateTTSAvailability();
                        }
                    }, 1000);
                } else if (result === true && handler.isReady === false) {
                    // If handler says it's ready but didn't set its own isReady flag, set it
                    console.log(`TTS Factory: Setting ${id} isReady = true based on successful initialize()`);
                    handler.isReady = true;
                }
            }
            
            console.log(`TTS Factory: Handler ${id} initialized with result: ${result}`);
            
            return result;
        } catch (error) {
            console.error(`TTS Factory: Error initializing handler ${id}:`, error);
            console.error(`TTS Factory: Handler ${id} stack trace:`, error.stack);
            this.initStatus[id] = false;
            return false;
        }
    }
    
    /**
     * Get available TTS handlers
     * @returns {Array} - Array of handler objects
     */
    getAvailableHandlers() {
        const availableHandlers = [];
        
        // Always include a 'none' option
        availableHandlers.push({
            id: 'none',
            handler: null,
            displayName: 'None'
        });
        
        // Always add all registered handlers to the dropdown, regardless of ready state
        for (const id in this.handlers) {
            const handler = this.handlers[id];
            availableHandlers.push({
                id: handler.getId(),
                handler: handler,
                displayName: handler.getName(),
                isReady: handler.isReady === true
            });
        }
        
        // Add placeholder entries for important API handlers that might not be registered yet
        const apiHandlerIds = ['elevenlabs-tts', 'openai-tts'];
        for (const id of apiHandlerIds) {
            // Only add if not already in the list
            if (!this.handlers[id] && !availableHandlers.some(h => h.id === id)) {
                console.log(`TTS Factory: Adding placeholder for API handler ${id} to available handlers list`);
                availableHandlers.push({
                    id: id,
                    handler: null,
                    displayName: id.split('-')[0].charAt(0).toUpperCase() + id.split('-')[0].slice(1),
                    isReady: false
                });
            }
        }
        
        console.log(`TTS Factory: Returning ${availableHandlers.length} handlers for UI (including 'none'):`, 
                  availableHandlers.map(h => h.id).join(', '));
        
        return availableHandlers;
    }
    
    /**
     * Report progress during initialization
     * @param {number} progress - Progress percentage (0-100)
     * @param {string} message - Progress message
     */
    reportProgress(progress, message) {
        // Report progress
        if (this.progressCallback) {
            this.progressCallback(progress, message);
        }
    }
    
    /**
     * Load TTS preferences from persistence manager
     */
    async loadPreferences() {
        // Get the persistence manager for preferences
        const persistenceManager = this.getModule('persistence-manager');
        if (!persistenceManager) {
            console.warn('TTS Factory: No persistence manager available, using default settings');
            return;
        }
        
        // Default settings for first run
        const defaults = {
            'speed': 1.0,                              // Default speech speed multiplier
            'preferred_handler': 'none',               // Development default: TTS disabled
            'enabled': false,                          // TTS disabled by default
            'voice': '',                               // Empty default - will be selected based on handler
            'language': 'en_US',                       // Legacy stored value; game metadata now owns active TTS language
            'volume': 1.0,                             // Default volume
            'elevenlabs_api_key': '',                  // Empty API key by default
            'elevenlabs_api_url': 'https://api.elevenlabs.io/v1',  // Default ElevenLabs API URL
            'openai_api_key': '',                      // Empty API key by default
            'openai_api_url': 'https://api.openai.com/v1'  // Default OpenAI API URL
        };
        
        // Ensure all defaults are set in persistence if they don't exist
        for (const [key, value] of Object.entries(defaults)) {
            if (persistenceManager.getPreference('tts', key) === undefined) {
                console.log(`TTS Factory: Setting default for '${key}': ${value}`);
                persistenceManager.updatePreference('tts', key, value);
            }
        }
        
        // Load speech rate preference exactly as persisted. Do not migrate or
        // rewrite this value on load; the UI must reflect the browser state.
        const savedSpeed = persistenceManager.getPreference('tts', 'speed');
        if (Number.isFinite(savedSpeed)) {
            this.speed = savedSpeed;
            console.log(`TTS Factory: Loaded speed preference: ${this.speed}`);
        } else {
            this.speed = defaults.speed;
            console.log(`TTS Factory: Using default speed: ${this.speed}`);
        }
        
        // Load other preferences we need for initialization
        const preferredHandler = persistenceManager.getPreference('tts', 'preferred_handler');
        console.log(`TTS Factory: Loaded preferred handler: ${preferredHandler || 'none'}`);
        const gameConfig = this.getModule('game-config');
        this.language = String(gameConfig?.getLocale?.() || defaults.language).replace('_', '-').toLowerCase();
        this.voice = persistenceManager.getPreference('tts', 'voice', defaults.voice);
        this.volume = persistenceManager.getPreference('tts', 'volume', defaults.volume);
        
        // We'll handle the preferred handler in initializeHandlerSystem()
        
        // Check if TTS is enabled
        const ttsEnabled = persistenceManager.getPreference('tts', 'enabled');
        console.log(`TTS Factory: TTS enabled: ${ttsEnabled}`);
        
        // Return the loaded preferences for convenience
        return {
            preferredHandler: preferredHandler || defaults.preferred_handler,
            enabled: ttsEnabled !== undefined ? ttsEnabled : defaults.enabled,
            speed: this.speed
        };
    }
    
    /**
     * Register TTS handlers from module registry
     */
    async registerHandlers() {
        // Access module registry directly through window
        // This is correct architectural pattern - module registry is NOT a dependency
        const moduleRegistry = window.moduleRegistry;
        
        if (!moduleRegistry) {
            console.error('TTS Factory: Module registry not available via window.moduleRegistry');
            return;
        }
        
        console.log('TTS Factory: Module registry found, scanning for TTS modules...');
        console.log('TTS Factory: All modules in registry:', Object.keys(moduleRegistry.modules).join(', '));
        
        // Register handlers (in order of preference)
        const handlers = [
            { id: 'kokoro-tts', displayName: 'Kokoro TTS' },
            { id: 'browser-tts', displayName: 'Browser TTS' },
            { id: 'elevenlabs-tts', displayName: 'ElevenLabs TTS' },
            { id: 'openai-tts', displayName: 'OpenAI TTS' }
        ];
        
        // Register each handler
        for (const [index, handler] of handlers.entries()) {
            try {
                console.log(`TTS Factory: Attempting to get module '${handler.id}'`);
                const module = this.getModule(handler.id);
                
                if (module) {
                    console.log(`TTS Factory: Successfully got module '${handler.id}'`, module);
                    console.log(`TTS Factory: Module class: ${module.constructor.name}, ID: ${module.id}`);
                    this.registerHandler(handler.id, module);
                    console.log(`TTS Factory: Registered handler ${handler.id}`);
                } else {
                    console.warn(`TTS Factory: Module ${handler.id} not found in module registry. All registered modules: ${Object.keys(moduleRegistry.modules).join(', ')}`);
                }
            } catch (error) {
                console.error(`TTS Factory: Error registering handler ${handler.id}:`, error);
            }
        }
    }
    
    /**
     * Initialize the preferred or fallback TTS handler
     */
    async initializeHandlerSystem() {
        // Get the preferred handler from persistence manager
        const persistenceManager = this.getModule('persistence-manager');
        let preferredHandler = null;
        
        if (persistenceManager) {
            preferredHandler = persistenceManager.getPreference('tts', 'preferred_handler');
            const ttsEnabled = persistenceManager.getPreference('tts', 'enabled', false);
            console.log(`TTS Factory: Preferred handler from settings: ${preferredHandler || 'none'}`);
            if (!ttsEnabled) {
                console.log('TTS Factory: TTS toggle is disabled, starting with no active handler');
                this.activeHandler = null;
                this.updateTTSAvailability();
                return true;
            }
        }
        
        // Special case for 'none' preference
        if (preferredHandler === 'none') {
            console.log('TTS Factory: User has disabled TTS (none selected)');
            this.activeHandler = null;
            this.updateTTSAvailability();
            return true;
        }
        
        // If user has a preferred handler, attempt to set it even if not initialized
        if (preferredHandler) {
            // Check if handler exists
            if (this.handlers[preferredHandler]) {
                console.log(`TTS Factory: Attempting to initialize preferred handler: ${preferredHandler}`);
                
                // Try to initialize the preferred handler
                const success = await this.initializeHandler(preferredHandler);
                
                // Set as active regardless of initialization result
                // TTS will be considered disabled if handler exists but isn't ready
                console.log(`TTS Factory: Setting preferred handler ${preferredHandler} as active (init success: ${success})`);
                await this.setActiveHandler(preferredHandler);
                return true;
            } else {
                console.log(`TTS Factory: Preferred handler ${preferredHandler} not registered yet, will be set when available`);
                // We can't set it as active yet since it doesn't exist, but we've stored the preference
            }
        }
        
        // Default to no TTS. Games or users can explicitly select a provider later.
        console.log('TTS Factory: No preferred TTS handler selected, defaulting to none');
        this.activeHandler = null;
        this.updateTTSAvailability();
        return true;
    }
    
    /**
     * Attempt to initialize and set fallback handlers in order
     * @returns {Promise<boolean>} - Success status
     */
    async attemptFallbackHandler() {
        // Providers are opt-in. Keep the baseline as text-only unless explicitly selected.
        const fallbackOrder = [];
        
        // Try each fallback in order
        for (const handlerId of fallbackOrder) {
            if (this.handlers[handlerId]) {
                console.log(`TTS Factory: Trying fallback handler: ${handlerId}`);
                
                // Try to initialize this handler
                const success = await this.initializeHandler(handlerId);
                
                if (success) {
                    console.log(`TTS Factory: Fallback handler ${handlerId} initialized successfully`);
                    return await this.setActiveHandler(handlerId);
                } else {
                    console.warn(`TTS Factory: Fallback handler ${handlerId} initialization failed`);
                }
            }
        }
        
        // If no explicit provider is selected, update TTS availability and continue.
        console.log('TTS Factory: No fallback handler selected, TTS will be unavailable');
        this.activeHandler = null;
        this.updateTTSAvailability();
        
        // TTS is optional, so return true even if no handler is available
        return true;
    }
    
    /**
     * Initialize the audio cache system
     * @returns {Promise<boolean>} - Success status
     */
    async initializeCache() {
        console.log('TTS Factory: Initializing cache');
        
        if (this.cacheInitialized) {
            console.log('TTS Factory: Cache already initialized');
            return true;
        }
        
        try {
            // Initialize IndexedDB for audio cache
            await this._initializeDB();
            
            // Calculate current cache size
            await this._calculateTotalCacheSize();
            
            this.cacheInitialized = true;
            console.log(`TTS Factory: Cache initialized, current size: ${this.currentCacheSize} bytes`);
            return true;
        } catch (error) {
            console.error('TTS Factory: Failed to initialize cache:', error);
            // Cache is non-essential, continue without it
            this.cacheInitialized = false;
            return false;
        }
    }
    
    /**
     * Register a TTS handler
     * @param {string} id - Handler ID
     * @param {Object} handler - TTS handler instance
     */
    registerHandler(id, handler) {
        if (!handler) {
            console.error(`TTS Factory: Cannot register null handler for ${id}`);
            return;
        }
        
        console.log(`TTS Factory: Registering handler ${id}`);
        this.handlers[id] = handler;
        
        // Note: Handlers now declare their own dependencies and access them via getModule()
        // They no longer need dependencies to be provided by the factory
    }
    
    /**
     * Set the active TTS handler
     * @param {string} id - Handler ID
     * @returns {boolean} - Success status
     */
    async setActiveHandler(id) {
        // Special case for 'none' option
        if (id === 'none') {
            console.log('TTS Factory: Disabling TTS (none selected)');
            this.activeHandler = null;
            
            // Save the preference
            const persistenceManager = this.getModule('persistence-manager');
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'preferred_handler', 'none');
            }
            
            // Dispatch events
            document.dispatchEvent(new CustomEvent('tts:handler:changed', {
                detail: { handler: 'none', available: false }
            }));

            document.dispatchEvent(new CustomEvent('tts:engine:change', {
                detail: { engine: 'none', handler: 'none', available: false }
            }));

            this.updateTTSAvailability();
            return true;
        }
        
        // Check if the handler exists
        if (!this.handlers[id]) {
            console.warn(`TTS Factory: Handler ${id} not registered - still setting as preferred`);
            // We'll still set the preference but won't set as active until it's registered
            
            // Save the preference
            const persistenceManager = this.getModule('persistence-manager');
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'preferred_handler', id);
            }
            
            // We should not set this.activeHandler since the handler doesn't exist
            return false;
        }
        
        console.log(`TTS Factory: Setting active handler to ${id}`);
        
        if (this.handlers[id].isReady !== true) {
            console.log(`TTS Factory: Initializing handler ${id} before activation`);
            await this.initializeHandler(id);
        }

        // Check if the handler is ready after initialization
        const isReady = this.handlers[id].isReady === true;
        if (!isReady) {
            console.warn(`TTS Factory: Handler ${id} is not ready - TTS will be considered disabled until ready`);
        }
        
        // Set the new active handler
        this.activeHandler = id;
        
        // Save the preference
        const persistenceManager = this.getModule('persistence-manager');
        if (persistenceManager) {
            persistenceManager.updatePreference('tts', 'preferred_handler', id);
            this.voice = persistenceManager.getPreference('tts', 'voice', this.voice || '');
            this.language = String(this.getModule('game-config')?.getLocale?.() || this.language || 'en_US').replace('_', '-').toLowerCase();
            this.speed = persistenceManager.getPreference('tts', 'speed', this.speed || 1.0);
        }

        const handler = this.handlers[id];
        if (handler && typeof handler.setVoiceOptions === 'function') {
            handler.setVoiceOptions({
                voice: this.voice,
                speed: this.speed,
                language: this.language
            });
        }
        
        // Dispatch events
        const event = new CustomEvent('tts:handler:changed', {
            detail: { handler: id, available: isReady }
        });
        document.dispatchEvent(event);

        document.dispatchEvent(new CustomEvent('tts:engine:change', {
            detail: { engine: id, handler: id, available: isReady }
        }));

        // Update overall TTS availability
        this.updateTTSAvailability();

        return true;
    }
    
    /**
     * Get the active TTS handler
     * @returns {Object|null} - Active TTS handler instance or null if none active
     */
    getActiveHandler() {
        if (!this.activeHandler || !this.handlers[this.activeHandler]) {
            return null;
        }
        return this.handlers[this.activeHandler];
    }
    
    /**
     * Speak text using the active TTS handler
     * @param {string} text - Text to speak
     * @param {Object} options - TTS options
     * @returns {Promise<boolean>} - Success status
     */
    async speak(text, options = {}) {
        // Check if we have an active handler
        if (!this.activeHandler) {
            console.warn('TTS Factory: No active handler set');
            return false;
        }
        
        // Get the active handler
        const handler = this.handlers[this.activeHandler];
        if (!handler) {
            console.error('TTS Factory: Active handler not found');
            return false;
        }
        
        // Check if the handler is ready
        if (!handler.isReady) {
            console.warn(`TTS Factory: Active handler ${this.activeHandler} is not ready`);
            return false;
        }
        
        try {
            // Apply speed option if specified
            const effectiveOptions = { ...options };
            if (typeof effectiveOptions.speed === 'undefined') {
                effectiveOptions.speed = this.speed;
            }
            
            // Check if we have this speech cached
            const hash = await this.generateSpeechHash(text);
            const cached = await this.getCachedSpeech(hash);
            
            if (cached && cached.success) {
                console.log(`TTS Factory: Using cached speech for hash ${hash}`);
                // Use cached speech
                return handler.speakPreloaded(cached, result => {
                    document.dispatchEvent(new CustomEvent('tts:speechCompleted', {
                        detail: { success: result?.success === true, error: result?.error }
                    }));
                });
            }
            
            // Not cached, generate and cache
            if (typeof handler.preloadSpeech === 'function') {
                console.log(`TTS Factory: Generating and caching speech for hash ${hash}`);
                const preloadData = await handler.preloadSpeech(text, options);
                if (preloadData && preloadData.success) {
                    // Cache the speech
                    await this.cacheSpeech(hash, preloadData.audioData, preloadData.duration);
                    
                    // Speak the preloaded speech
                    return handler.speakPreloaded(preloadData, result => {
                        document.dispatchEvent(new CustomEvent('tts:speechCompleted', {
                            detail: { success: result?.success === true, error: result?.error }
                        }));
                    });
                }
            }
            
            // Fallback to direct speak if preloading failed or not supported
            console.log(`TTS Factory: Falling back to direct speak (no caching)`);
            return handler.speak(text, result => {
                // Forward speech completion event
                document.dispatchEvent(new CustomEvent('tts:speechCompleted', {
                    detail: { success: result?.success === true, error: result?.error }
                }));
            });
        } catch (error) {
            console.error('TTS Factory: Error speaking:', error);
            return false;
        }
    }
    
    /**
     * Preload speech for later playback
     * @param {string} text - Text to preload
     * @param {number} [priority=5] - Priority for preloading (1-10, higher is more important)
     * @returns {Promise<Object>} - Preloaded speech data
     */
    async preloadSpeech(text, options = {}) {
        // Check if we have an active handler
        if (!this.activeHandler || !this.ttsAvailable) {
            console.warn('TTS Factory: Cannot preload speech - no active handler or TTS not available');
            return { success: false, reason: 'no_active_handler' };
        }
        
        // Get the active handler
        const handler = this.handlers[this.activeHandler];
        if (!handler) {
            return { success: false, reason: 'handler_not_found' };
        }
        
        try {
            // Generate a hash for this speech request
            const hash = await this.generateSpeechHash(text);
            
            // Check if we have this speech cached
            const cached = await this.getCachedSpeech(hash);
            if (cached) {
                console.log(`TTS Factory: Using cached speech for hash ${hash} (hits: ${this.cacheHits}, misses: ${this.cacheMisses})`);
                // Move this item to the end of the Map to mark it as most recently used
                // this.audioCache.delete(hash);
                // this.audioCache.set(hash, cached);
                this.cacheHits++;
                return cached;
            }
            
            // Cache miss - need to generate new speech data
            this.cacheMisses++;
            
            // If the handler has a preloadSpeech method, use it
            if (typeof this.handlers[this.activeHandler].preloadSpeech === 'function') {
                const preloadData = await this.handlers[this.activeHandler].preloadSpeech(text, options);

                // Cache the generated speech data (extract audioData from result object)
                if (preloadData && preloadData.audioData) {
                    await this.cacheSpeech(hash, preloadData.audioData, preloadData.duration);
                    console.log(`TTS Factory: Added speech to cache for hash ${hash} (size: ${this.currentCacheSize}/${this.maxCacheSizeBytes})`);
                }

                return preloadData;
            } else {
                console.warn(`TTS Factory: Handler ${this.activeHandler} does not support preloading`);
                return null;
            }
        } catch (error) {
            console.error("TTS Factory: Error preloading speech:", error);
            return null;
        }
    }
    
    /**
     * Stop speaking
     * @returns {boolean} - Success status
     */
    stop() {
        // Check if we have an active handler
        if (!this.activeHandler || !this.ttsAvailable) {
            return false;
        }
        
        // Get the active handler
        const handler = this.handlers[this.activeHandler];
        if (!handler) {
            return false;
        }
        
        // Call the handler's stop method
        try {
            return handler.stop();
        } catch (error) {
            console.error('TTS Factory: Error stopping speech:', error);
            return false;
        }
    }
    
    /**
     * Pause speaking
     * @returns {boolean} - Success status
     */
    pause() {
        if (!this.activeHandler) return false;
        
        try {
            return this.handlers[this.activeHandler].pause();
        } catch (error) {
            console.error("Error pausing TTS:", error);
            return false;
        }
    }
    
    /**
     * Resume speaking
     * @returns {boolean} - Success status
     */
    resume() {
        if (!this.activeHandler) return false;
        
        try {
            return this.handlers[this.activeHandler].resume();
        } catch (error) {
            console.error("Error resuming TTS:", error);
            return false;
        }
    }

    fadeOut(duration = 1000) {
        const handlers = Object.values(this.handlers);
        const fades = handlers.map(handler => {
            if (!handler) return Promise.resolve(false);
            if (typeof handler.fadeOutCurrentAudio === 'function') {
                return handler.fadeOutCurrentAudio(duration);
            } else if (handler.isSpeaking && typeof handler.stop === 'function') {
                return new Promise(resolve => {
                    setTimeout(() => {
                        resolve(handler.stop());
                    }, duration);
                });
            }
            return Promise.resolve(false);
        });
        return Promise.all(fades);
    }
    
    /**
     * Get voices from the active handler
     * @returns {Array} - Array of voices
     */
    async getVoices() {
        // Check if we have an active handler
        if (!this.activeHandler || !this.handlers[this.activeHandler]) {
            return [];
        }
        
        const handler = this.handlers[this.activeHandler];
        
        try {
            // Return voices from handler if it supports it
            if (typeof handler.getVoices === 'function') {
                const voices = await handler.getVoices();
                return voices || [];
            }
            
            // If no getVoices method, try accessing voices property
            if (Array.isArray(handler.voices)) {
                return handler.voices;
            }
            
            return [];
        } catch (error) {
            console.error('TTS Factory: Error getting voices:', error);
            return [];
        }
    }
    
    /**
     * Get a preference from the persistence manager
     * @param {string} category - Preference category
     * @param {string} key - Preference key
     * @param {*} defaultValue - Default value if preference doesn't exist
     * @returns {*} - Preference value
     */
    getPreference(category, key, defaultValue) {
        const persistenceManager = this.getModule('persistence-manager');
        if (persistenceManager) {
            return persistenceManager.getPreference(category, key, defaultValue);
        }
        return defaultValue;
    }
    
    /**
     * Check if any TTS handler is currently speaking
     * @returns {boolean} - True if speaking, false otherwise
     */
    isSpeaking() {
        // Check active handler first
        if (this.activeHandler && this.handlers[this.activeHandler]) {
            if (this.handlers[this.activeHandler].isSpeaking) {
                return true;
            }
        }
        
        // Check all handlers
        return Object.values(this.handlers).some(handler => 
            handler && typeof handler.isSpeaking === 'boolean' && handler.isSpeaking
        );
    }
    
    /**
     * Update overall TTS availability
     */
    updateTTSAvailability() {
        const wasAvailable = this.ttsAvailable;
        
        // TTS is considered available only if the active handler exists and is ready
        let ttsAvailable = false;
        
        if (this.activeHandler && this.handlers[this.activeHandler]) {
            // Check if the active handler is ready
            ttsAvailable = this.handlers[this.activeHandler].isReady === true;
        }
        
        this.ttsAvailable = ttsAvailable;
        
        console.log(`TTS Factory: Availability updated: ${this.ttsAvailable} (active handler: ${this.activeHandler || 'none'})`);
        
        // Only dispatch event if availability changed
        if (wasAvailable !== this.ttsAvailable) {
            // Notify the UI about TTS availability
            const event = new CustomEvent('tts:availability', {
                detail: { 
                    available: this.ttsAvailable,
                    activeHandler: this.activeHandler
                }
            });
            document.dispatchEvent(event);
        }
    }

    disableAfterCurrentPlayback() {
        const previousHandler = this.activeHandler;
        if (previousHandler) {
            const persistenceManager = this.getModule('persistence-manager');
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'last_enabled_handler', previousHandler);
            }
        }

        this.activeHandler = null;
        console.log('TTS Factory: TTS disabled for future generation; current playback may finish');
        document.dispatchEvent(new CustomEvent('tts:handler:changed', {
            detail: { handler: 'none', available: false, previousHandler }
        }));
        document.dispatchEvent(new CustomEvent('tts:engine:change', {
            detail: { engine: 'none', handler: 'none', available: false, previousHandler }
        }));
        this.updateTTSAvailability();
        return true;
    }

    getHandlerStatuses() {
        const statuses = [{
            id: 'none',
            name: 'None',
            ready: true,
            active: !this.activeHandler,
            message: 'Text-only mode'
        }];

        for (const id in this.handlers) {
            const handler = this.handlers[id];
            statuses.push({
                id,
                name: typeof handler.getName === 'function' ? handler.getName() : id,
                ready: handler.isReady === true,
                active: this.activeHandler === id,
                message: this.getHandlerStatusMessage(id, handler)
            });
        }

        return statuses;
    }

    getHandlerStatusMessage(id, handler) {
        if (!handler) return 'Not registered';
        if (handler.isReady === true) return 'Ready';
        if (id === 'kokoro-tts') return handler.state === 'INITIALIZING' ? 'Loading model' : 'Not loaded';
        if (handler.apiKey === '') return 'API key missing';
        if (handler.apiKey && handler.isReady !== true) return 'API unavailable or invalid settings';
        return 'Not ready';
    }

    async refreshHandlerStatus(id) {
        if (!id || id === 'none') {
            this.updateTTSAvailability();
            return true;
        }

        if (!this.handlers[id]) {
            return false;
        }

        const handler = this.handlers[id];
        if (id === 'kokoro-tts') {
            this.updateTTSAvailability();
            return handler.isReady === true;
        }

        const success = await this.initializeHandler(id);
        this.updateTTSAvailability();
        document.dispatchEvent(new CustomEvent('tts:status:updated', {
            detail: { statuses: this.getHandlerStatuses() }
        }));
        return success;
    }

    async getVoicesForHandler(handlerId) {
        if (!handlerId || handlerId === 'none' || !this.handlers[handlerId]) {
            return [];
        }

        const handler = this.handlers[handlerId];
        if (handler.isReady !== true && handlerId !== 'kokoro-tts') {
            await this.initializeHandler(handlerId);
        }

        if (typeof handler.getVoices === 'function') {
            return await handler.getVoices() || [];
        }

        return Array.isArray(handler.voices) ? handler.voices : [];
    }
    
    /**
     * Configure TTS settings for all handlers
     * @param {Object} options - TTS options
     * @param {number} [options.speed] - Speech speed multiplier
     */
    configure(options = {}) {
        if (!options || typeof options !== 'object') {
            return;
        }

        const persistenceManager = this.getModule('persistence-manager');
        const voiceOptions = {};
        
        if (typeof options.speed === 'number') {
            this.speed = Math.max(0.5, Math.min(2.0, options.speed));
            voiceOptions.speed = this.speed;
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'speed', this.speed);
            }
        }

        if (typeof options.voice === 'string' && options.voice) {
            this.voice = options.voice;
            voiceOptions.voice = options.voice;
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'voice', options.voice);
                if (this.activeHandler) {
                    persistenceManager.updatePreference('tts', `${this.activeHandler}_voice`, options.voice);
                }
            }
        }

        if (typeof options.language === 'string' && options.language) {
            this.language = options.language.replace('_', '-').toLowerCase();
            voiceOptions.language = this.language;
            if (persistenceManager && options.persistLanguage === true) {
                persistenceManager.updatePreference('tts', 'language', this.language);
            }
        }

        if (typeof options.volume === 'number') {
            this.volume = Math.max(0, Math.min(1, options.volume));
            voiceOptions.volume = this.volume;
            if (persistenceManager) {
                persistenceManager.updatePreference('tts', 'volume', this.volume);
            }
        }

        for (const id in this.handlers) {
            const handler = this.handlers[id];
            if (handler && typeof handler.setVoiceOptions === 'function') {
                handler.setVoiceOptions(voiceOptions);
            } else if (handler && typeof handler.configure === 'function') {
                handler.configure(voiceOptions);
            }
            if (voiceOptions.language && !voiceOptions.voice && handler && typeof handler.selectVoiceForLocale === 'function') {
                handler.selectVoiceForLocale(voiceOptions.language);
            }
        }
        
        // Update UI that TTS settings have changed
        document.dispatchEvent(new CustomEvent('tts:configured', {
            detail: { 
                options: {
                    speed: this.speed,
                    voice: this.voice,
                    language: this.language,
                    volume: this.volume
                }, 
                activeHandler: this.activeHandler 
            }
        }));
    }
    
    /**
     * Preload speech for a text
     * @param {string} text - Text to preload
     * @returns {Promise<Object>} - Resolves with preloaded speech data
     */
    async preloadSpeech(text, options = {}) {
        if (!this.activeHandler) {
            console.warn("TTS Factory: No active TTS handler for preload");
            return null;
        }
        
        try {
            // Generate a hash for this speech request
            const hash = await this.generateSpeechHash(text);
            
            // Check if we have this audio in cache
            const cachedData = await this.getCachedSpeech(hash);
            if (cachedData) {
                console.log(`TTS Factory: Using cached speech for hash ${hash} (hits: ${this.cacheHits}, misses: ${this.cacheMisses})`);
                this.emitProcessState('playing-ready', { reason: 'tts-cache-hit', hash });
                // Move this item to the end of the Map to mark it as most recently used
                // this.audioCache.delete(hash);
                // this.audioCache.set(hash, cachedData);
                this.cacheHits++;
                return cachedData;
            }
            
            // Cache miss - need to generate new speech data
            this.cacheMisses++;
            this.emitProcessState('waiting-generating', { reason: 'tts-cache-miss', hash });
            
            // If the handler has a preloadSpeech method, use it
            if (typeof this.handlers[this.activeHandler].preloadSpeech === 'function') {
                const preloadData = await this.handlers[this.activeHandler].preloadSpeech(text, options);

                // Cache the generated speech data (extract audioData from result object)
                if (preloadData && preloadData.audioData) {
                    await this.cacheSpeech(hash, preloadData.audioData, preloadData.duration);
                    console.log(`TTS Factory: Added speech to cache for hash ${hash} (size: ${this.currentCacheSize}/${this.maxCacheSizeBytes})`);
                    this.emitProcessState('playing-ready', { reason: 'tts-generated', hash });
                }

                return preloadData;
            } else {
                console.warn(`TTS Factory: Handler ${this.activeHandler} does not support preloading`);
                return null;
            }
        } catch (error) {
            console.error("TTS Factory: Error preloading speech:", error);
            return null;
        }
    }
    
    /**
     * Generate a unique hash for a speech request
     * @param {string} text - Text to generate hash for
     * @returns {Promise<string>} - Hash string
     */
    async generateSpeechHash(text) {
        const handler = this.getActiveHandler();
        const provider = this.activeHandler || 'none';
        const voiceInfo = this.getEffectiveVoiceId(handler);
        const speed = this.speed || 1.0;
        const language = this.language || 'en-us';
        const key = JSON.stringify({
            provider,
            voice: voiceInfo,
            speed,
            language,
            text
        });
        
        try {
            const encoder = new TextEncoder();
            const data = encoder.encode(key);
            const hashBuffer = await crypto.subtle.digest('SHA-256', data);
            
            // Convert to hex string
            const hashArray = Array.from(new Uint8Array(hashBuffer));
            const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
            
            return hashHex;
        } catch (error) {
            console.error('TTS Factory: Error generating hash:', error);
            // Simple fallback hash if crypto API fails
            return key.replace(/[^a-z0-9]/gi, '').substring(0, 32);
        }
    }
    
    /**
     * Speak using preloaded speech data
     * @param {Object} preloadData - Preloaded speech data
     * @param {Object} options - Speech options
     * @returns {Promise<boolean>} - Success status
     */
    async speakPreloaded(preloadData, options = {}) {
        if (!this.activeHandler) {
            console.warn("TTS Factory: No active TTS handler for speak preloaded");
            return false;
        }
        
        // If the handler has a speakPreloaded method, use it
        if (typeof this.handlers[this.activeHandler].speakPreloaded === 'function') {
            return await this.handlers[this.activeHandler].speakPreloaded(preloadData, result => {
                document.dispatchEvent(new CustomEvent('tts:speechCompleted', {
                    detail: { success: result?.success === true, error: result?.error }
                }));
            });
        } else {
            console.warn(`TTS Factory: Handler ${this.activeHandler} does not support speaking preloaded data`);
            return false;
        }
    }
    
    /**
     * Get cached speech data
     * @param {string} hash - Hash of the speech data
     * @returns {Promise<ArrayBuffer|null>} - Cached speech data or null if not found
     */
    async getCachedSpeech(hash) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn('TTS Factory: Cache not ready, cannot retrieve cached speech');
            return null;
        }

        try {
            const item = await this._getDBItem(hash);
            if (item && item.audioData) {
                console.log(`TTS Factory: Found cached speech for hash ${hash}`);
                // Return in the same format as handlers' preloadSpeech() method
                return {
                    success: true,
                    audioData: item.audioData,
                    duration: item.duration || 0
                };
            }
        } catch (error) {
            console.error('TTS Factory: Error retrieving cached speech:', error);
        }

        return null;
    }
    
    /**
     * Add speech data to the cache
     * @param {string} hash - Hash of the speech data
     * @param {ArrayBuffer} audioData - Audio data to cache
     * @returns {Promise<boolean>} - Success status
     */
    async cacheSpeech(hash, audioData, duration = 0) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn('TTS Factory: Cache not ready, cannot cache speech');
            return false;
        }

        if (!audioData) {
            console.error('TTS Factory: No audio data provided to cache');
            return false;
        }

        console.log(`TTS Factory: cacheSpeech called with audioData:`, audioData ? `${audioData.byteLength} bytes` : 'UNDEFINED', 'duration:', duration);

        try {
            // Make sure we have room in the cache
            await this.manageCacheSize(audioData.byteLength);

            // Store the speech data with duration
            await this._putDBItem(hash, audioData, duration);

            console.log(`TTS Factory: Cached speech for hash ${hash}`);
            return true;
        } catch (error) {
            console.error('TTS Factory: Error caching speech:', error);
            return false;
        }
    }
    
    /**
     * Manages the cache size, ensuring it doesn't exceed the limit using LRU.
     * @param {number} [sizeToAdd] - Optional size to add to the cache before checking
     * @returns {Promise<void>}
     */
    async manageCacheSize(sizeToAdd = 0) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn("TTSFactory: Cache DB not ready for size management.");
            return;
        }

        let iterations = 0;
        const maxIterations = 100; // Safety break to prevent infinite loops

        try {
            // Ensure currentCacheSize is up-to-date before starting eviction
            // This is important especially on startup or if background writes happened
            this.currentCacheSize = await this._calculateTotalCacheSize();
            console.log(`TTS Factory: Recalculated cache size: ${(this.currentCacheSize / (1024*1024)).toFixed(2)} MB`);

            // Add the size to be added to the current size
            this.currentCacheSize += sizeToAdd;

            while (this.currentCacheSize > this.maxCacheSizeBytes && iterations < maxIterations) {
                iterations++;
                console.log(`TTS Factory: Cache limit exceeded (${(this.currentCacheSize / (1024*1024)).toFixed(2)}MB > ${(this.maxCacheSizeBytes / (1024*1024)).toFixed(2)}MB). Evicting oldest entry.`);

                const sortedItems = await this._getAllDBItemsSortedByAccess();
                if (sortedItems.length === 0) {
                    console.warn("TTS Factory: Cache size exceeds limit, but no items found to evict.");
                    this.currentCacheSize = 0; // Reset size if store is empty
                    break; // Exit loop
                }

                const oldestItem = sortedItems[0];
                console.log(`TTS Factory: Evicting item with hash ${oldestItem.hash}, size ${oldestItem.size}, lastAccessed ${new Date(oldestItem.lastAccessed).toISOString()}`);

                await this._deleteDBItem(oldestItem.hash);
                if (typeof oldestItem.size === 'number') {
                    this.currentCacheSize -= oldestItem.size;
                } else {
                    // Size was invalid, recalculate total size for safety
                    console.warn(`TTS Factory: Evicted item ${oldestItem.hash} had invalid size. Recalculating total size.`);
                    this.currentCacheSize = await this._calculateTotalCacheSize();
                }
                console.log(`TTS Factory: New estimated cache size: ${(this.currentCacheSize / (1024*1024)).toFixed(2)} MB`);
            }

            if (iterations >= maxIterations) {
                console.error("TTS Factory: Max iterations reached during cache eviction. Cache might still be oversized.");
            }

        } catch (error) {
            console.error("TTS Factory: Error during cache size management:", error);
            // Consider setting cache status to error or attempting recovery
        }
    }
    
    /**
     * Checks if speech for the given text is likely cached.
     * @param {string} text - The original text.
     * @returns {Promise<boolean>}
     */
    async isSpeechCached(text) {
        if (!this.cacheInitialized && this.cacheStatus !== 'ready') {
            console.warn("TTSFactory: Cache not ready for checking.");
            return false;
        }
        const handler = this.getActiveHandler();
        if (!handler) return false;
        const hash = await this.generateSpeechHash(text);

        try {
            const item = await this._getDBItem(hash); // _getDBItem updates timestamp if found
            return !!item;
        } catch (error) {
            console.error(`TTS Factory: Error checking cache for hash ${hash}:`, error);
            return false;
        }
    }

    getEffectiveVoiceId(handler = this.getActiveHandler()) {
        if (!handler) return this.voice || '';
        const voice = handler.voiceOptions?.voice || handler.currentVoice || this.voice || '';
        if (typeof voice === 'string') return voice;
        return voice.id || voice.name || '';
    }

    emitProcessState(state, detail = {}) {
        console.log(`Process state: ${state}`, detail);
        document.dispatchEvent(new CustomEvent('story:process-state', {
            detail: { state, ...detail }
        }));
    }
    
    /**
     * Opens and initializes the IndexedDB database.
     */
    async _initializeDB() {
        return new Promise((resolve, reject) => {
            if (this.db) {
                resolve(); // Already initialized
                return;
            }

            const request = indexedDB.open(this.dbName, this.dbVersion);

            request.onerror = (event) => {
                console.error("IndexedDB error:", event.target.error);
                this.cacheStatus = 'error';
                reject(new Error(`IndexedDB error: ${event.target.error.message}`));
            };

            request.onsuccess = (event) => {
                this.db = event.target.result;
                console.log("IndexedDB initialized successfully.");
                this.cacheStatus = 'ready';
                // Calculate initial size after successful opening
                this._calculateTotalCacheSize().then(size => {
                    this.currentCacheSize = size;
                    console.log(`Initial cache size: ${(size / (1024*1024)).toFixed(2)} MB`);
                    resolve();
                }).catch(error => {
                    console.error("Error calculating initial cache size:", error);
                    this.cacheStatus = 'error';
                    reject(error); // Propagate calculation error
                });
            };

            request.onupgradeneeded = (event) => {
                console.log("IndexedDB upgrade needed.");
                const db = event.target.result;
                if (!db.objectStoreNames.contains(this.storeName)) {
                    const store = db.createObjectStore(this.storeName, { keyPath: 'hash' });
                    // Index for LRU eviction
                    store.createIndex('lastAccessed', 'lastAccessed', { unique: false });
                    // Index to potentially help with size calculation, though iterating might be needed anyway
                    store.createIndex('size', 'size', { unique: false });
                    console.log(`Object store '${this.storeName}' created.`);
                } else {
                    // Handle potential future schema upgrades here if needed
                    console.log(`Object store '${this.storeName}' already exists.`);
                    const transaction = event.target.transaction;
                    const store = transaction.objectStore(this.storeName);
                    // Ensure indexes exist if upgrading from a version without them
                    if (!store.indexNames.contains('lastAccessed')) {
                        store.createIndex('lastAccessed', 'lastAccessed', { unique: false });
                        console.log("Created 'lastAccessed' index.");
                    }
                    if (!store.indexNames.contains('size')) {
                        store.createIndex('size', 'size', { unique: false });
                        console.log("Created 'size' index.");
                    }
                }
                if (!db.objectStoreNames.contains('storyHistoryStore')) {
                    const historyStore = db.createObjectStore('storyHistoryStore', { keyPath: 'key' });
                    historyStore.createIndex('gameId', 'gameId', { unique: false });
                    historyStore.createIndex('gameOrder', ['gameId', 'blockId'], { unique: true });
                    console.log("Object store 'storyHistoryStore' created.");
                }
                if (!db.objectStoreNames.contains('storySaveStore')) {
                    db.createObjectStore('storySaveStore', { keyPath: 'slot' });
                    console.log("Object store 'storySaveStore' created.");
                }
            };
        });
    }

    /**
     * Gets an item from the IndexedDB store and updates its lastAccessed timestamp.
     * @param {string} hash - The key (hash) of the item to retrieve.
     * @returns {Promise<Object|null>} - The cached item object or null if not found.
     */
    async _getDBItem(hash) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn("IndexedDB not ready, cannot get item.");
            return null;
        }

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.storeName], 'readwrite'); // Need readwrite to update timestamp
            const store = transaction.objectStore(this.storeName);
            const request = store.get(hash);

            request.onerror = (event) => {
                console.error("Error getting item from IndexedDB:", event.target.error);
                reject(event.target.error);
            };

            request.onsuccess = (event) => {
                const result = event.target.result;
                if (result) {
                    // Update lastAccessed timestamp
                    result.lastAccessed = Date.now();
                    const updateRequest = store.put(result);
                    updateRequest.onerror = (updateEvent) => {
                        console.error("Error updating lastAccessed timestamp:", updateEvent.target.error);
                        // Still resolve with data, timestamp update failure is non-critical for retrieval
                        resolve(result);
                    };
                    updateRequest.onsuccess = () => {
                        // console.log(`Updated lastAccessed for hash: ${hash}`);
                        resolve(result);
                    };
                } else {
                    resolve(null); // Not found
                }
            };

            transaction.oncomplete = () => {
                // Transaction completed (either get or get+update)
            };
            transaction.onerror = (event) => {
                console.error("Readwrite transaction error during get/update:", event.target.error);
                // If transaction failed before request.onsuccess, we need to reject
                if (!request.result) { // Check if we already resolved
                   reject(event.target.error);
                }
            };
        });
    }

    /**
     * Adds or updates an item in the IndexedDB store.
     * @param {string} hash - The key (hash) of the item to store.
     * @param {ArrayBuffer} audioData - The audio data to cache.
     * @param {number} duration - Duration of the audio in milliseconds.
     * @returns {Promise<void>}
     */
    async _putDBItem(hash, audioData, duration = 0) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn("IndexedDB not ready, cannot put item.");
            return Promise.reject(new Error("IndexedDB not ready"));
        }
        if (!hash || !audioData) {
             console.error("Invalid item provided to _putDBItem:", hash, audioData);
             return Promise.reject(new Error("Invalid item format"));
        }

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.storeName], 'readwrite');
            const store = transaction.objectStore(this.storeName);
            const request = store.put({ hash, audioData, size: audioData.byteLength, duration: duration || 0, lastAccessed: Date.now() });

            request.onerror = (event) => {
                console.error("Error putting item into IndexedDB:", event.target.error);
                reject(event.target.error);
            };

            request.onsuccess = () => {
                // console.log(`Successfully put item with hash: ${hash}`);
                resolve();
            };

             transaction.onerror = (event) => {
                 console.error("Readwrite transaction error during put:", event.target.error);
                 reject(event.target.error);
             };
        });
    }

    /**
     * Deletes an item from the IndexedDB store.
     * @param {string} hash - The key (hash) of the item to delete.
     * @returns {Promise<void>}
     */
    async _deleteDBItem(hash) {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn("IndexedDB not ready, cannot delete item.");
             return Promise.reject(new Error("IndexedDB not ready"));
        }

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.storeName], 'readwrite');
            const store = transaction.objectStore(this.storeName);
            const request = store.delete(hash);

            request.onerror = (event) => {
                console.error("Error deleting item from IndexedDB:", event.target.error);
                reject(event.target.error);
            };

            request.onsuccess = () => {
                // console.log(`Successfully deleted item with hash: ${hash}`);
                resolve();
            };
            transaction.onerror = (event) => {
                 console.error("Readwrite transaction error during delete:", event.target.error);
                 reject(event.target.error);
             };
        });
    }

    /**
     * Calculates the total size of all items currently in the cache.
     * @returns {Promise<number>} - The total size in bytes.
     */
    async _calculateTotalCacheSize() {
        if (!this.db || this.cacheStatus !== 'ready') {
            console.warn("IndexedDB not ready, cannot calculate size.");
            return 0;
        }

        return new Promise((resolve, reject) => {
            let totalSize = 0;
            const transaction = this.db.transaction([this.storeName], 'readonly');
            const store = transaction.objectStore(this.storeName);
            const cursorRequest = store.openCursor();

            cursorRequest.onerror = (event) => {
                console.error("Error opening cursor for size calculation:", event.target.error);
                reject(event.target.error);
            };

            cursorRequest.onsuccess = (event) => {
                const cursor = event.target.result;
                if (cursor) {
                    // Check if size property exists and is a number
                    if (typeof cursor.value.size === 'number') {
                         totalSize += cursor.value.size;
                    } else {
                         // Old cache entry without size - calculate it
                         if (cursor.value.audioData && cursor.value.audioData.byteLength) {
                             const calculatedSize = cursor.value.audioData.byteLength;
                             totalSize += calculatedSize;

                             // Update the entry with the size property
                             const transaction = this.db.transaction([this.storeName], 'readwrite');
                             const store = transaction.objectStore(this.storeName);
                             const updatedValue = {
                                 ...cursor.value,
                                 size: calculatedSize
                             };
                             store.put(updatedValue);
                             console.log(`Updated cache entry ${cursor.key} with size: ${calculatedSize} bytes`);
                         } else {
                             console.warn(`Item with hash ${cursor.key} missing size and cannot calculate - will be deleted`);
                             // Delete invalid entry
                             const transaction = this.db.transaction([this.storeName], 'readwrite');
                             const store = transaction.objectStore(this.storeName);
                             store.delete(cursor.key);
                         }
                    }
                    cursor.continue();
                } else {
                    // No more entries
                    resolve(totalSize);
                }
            };
             transaction.onerror = (event) => {
                 console.error("Readonly transaction error during size calculation:", event.target.error);
                 reject(event.target.error);
             };
        });
    }

    /**
     * Gets all items sorted by lastAccessed timestamp (ascending, oldest first).
     * @returns {Promise<Array<object>>} - Array of cache item objects.
     */
    async _getAllDBItemsSortedByAccess() {
         if (!this.db || this.cacheStatus !== 'ready') {
             console.warn("IndexedDB not ready, cannot get sorted items.");
             return [];
         }

        return new Promise((resolve, reject) => {
            const items = [];
            const transaction = this.db.transaction([this.storeName], 'readonly');
            const store = transaction.objectStore(this.storeName);
            const index = store.index('lastAccessed'); // Use the index
            const cursorRequest = index.openCursor(); // Open cursor on the index

            cursorRequest.onerror = (event) => {
                console.error("Error opening cursor on lastAccessed index:", event.target.error);
                reject(event.target.error);
            };

            cursorRequest.onsuccess = (event) => {
                const cursor = event.target.result;
                if (cursor) {
                    items.push(cursor.value); // Add the object to the array
                    cursor.continue();
                } else {
                    // No more entries
                    resolve(items);
                }
            };
             transaction.onerror = (event) => {
                 console.error("Readonly transaction error during sorted get:", event.target.error);
                 reject(event.target.error);
             };
        });
    }

    /**
     * Helper to get item data without updating the lastAccessed timestamp.
     * Used internally by cacheSpeech to check existing size.
     * @param {string} hash
     * @returns {Promise<object|null>}
     */
    async _getDBItemOnly(hash) {
        if (!this.db || this.cacheStatus !== 'ready') return null;
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([this.storeName], 'readonly');
            const store = transaction.objectStore(this.storeName);
            const request = store.get(hash);
            request.onerror = (event) => reject(event.target.error);
            request.onsuccess = (event) => resolve(event.target.result || null);
        });
    }

    /**
     * Generates a SHA-256 hash for the given string.
     * @param {string} text - Input text.
     * @returns {Promise<string>} - Hexadecimal hash string.
     */
    async _generateHash(text) {
        try {
            const encoder = new TextEncoder();
            const data = encoder.encode(text);
            const hashBuffer = await crypto.subtle.digest('SHA-256', data);
            
            // Convert to hex string
            const hashArray = Array.from(new Uint8Array(hashBuffer));
            const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
            
            return hashHex;
        } catch (error) {
            console.error("Error generating SHA-256 hash:", error);
            // Fallback to simple text if crypto fails (less ideal for caching complex text)
            return text.replace(/[^a-zA-Z0-9]/g, ''); // Basic fallback
        }
    }

    /**
     * Get a TTS handler by ID
     * @param {string} id - Handler ID
     * @returns {Object|null} - TTS handler instance or null if not found
     */
    getHandler(id) {
        if (!id || !this.handlers[id]) return null;
        return this.handlers[id];
    }

    /**
     * Attempt to initialize and set the preferred handler
     * @returns {Promise<boolean>} - Success status
     */
    async initiatePreferredHandler() {
        // Get the preferred handler from persistence manager
        const persistenceManager = this.getModule('persistence-manager');
        let preferredHandler = null;
        
        if (persistenceManager) {
            preferredHandler = persistenceManager.getPreference('tts', 'preferred_handler');
            console.log(`TTS Factory: Preferred handler from settings: ${preferredHandler || 'none'}`);
        }
        
        // Try to initialize and set the preferred handler
        if (preferredHandler && this.handlers[preferredHandler]) {
            console.log(`TTS Factory: Attempting to initialize preferred handler: ${preferredHandler}`);
            
            // Try to initialize the preferred handler
            const success = await this.initializeHandler(preferredHandler);
            
            if (success) {
                console.log(`TTS Factory: Preferred handler ${preferredHandler} initialized successfully`);
                return await this.setActiveHandler(preferredHandler);
            } else {
                console.warn(`TTS Factory: Preferred handler ${preferredHandler} initialization failed, trying fallbacks`);
            }
        }
        
        // Default to no TTS. Games or users can explicitly select a provider later.
        console.log('TTS Factory: No preferred TTS handler selected, defaulting to none');
        this.activeHandler = null;
        this.updateTTSAvailability();
        return true;
    }
    
    /**
     * Attempt to initialize and set fallback handlers in order
     * @returns {Promise<boolean>} - Success status
     */
    async attemptFallbackHandler() {
        // Providers are opt-in. Keep the baseline as text-only unless explicitly selected.
        const fallbackOrder = [];
        
        // Try each fallback in order
        for (const handlerId of fallbackOrder) {
            if (this.handlers[handlerId]) {
                console.log(`TTS Factory: Trying fallback handler: ${handlerId}`);
                
                // Try to initialize this handler
                const success = await this.initializeHandler(handlerId);
                
                if (success) {
                    console.log(`TTS Factory: Fallback handler ${handlerId} initialized successfully`);
                    return await this.setActiveHandler(handlerId);
                } else {
                    console.warn(`TTS Factory: Fallback handler ${handlerId} initialization failed`);
                }
            }
        }
        
        // If no explicit provider is selected, update TTS availability and continue.
        console.log('TTS Factory: No fallback handler selected, TTS will be unavailable');
        this.activeHandler = null;
        this.updateTTSAvailability();
        
        // TTS is optional, so return true even if no handler is available
        return true;
    }

    /**
     * Debug TTS handlers and their status
     * This will log detailed information about all TTS handlers
     */
    debugTTSHandlers() {
        console.log('===== DEBUG TTS HANDLERS START =====');

        // Log all registered handlers
        console.log('Registered Handlers:');
        for (const id in this.handlers) {
            const handler = this.handlers[id];
            const isInitialized = !!this.initStatus[id];
            const isReady = handler && handler.isReady;
            const isApiHandler = ['elevenlabs', 'openai', 'kokoro'].includes(id);

            console.log(`Handler ID: ${id}`);
            console.log(`  - Handler Exists: ${!!handler}`);
            console.log(`  - Is API Handler: ${isApiHandler}`);
            console.log(`  - Init Status: ${isInitialized}`);
            console.log(`  - Is Ready: ${isReady}`);
            console.log(`  - Would Include in UI: ${isApiHandler || isInitialized || isReady}`);
            
            // Check handler properties
            if (handler) {
                console.log(`  - Type: ${handler.constructor.name}`);
                console.log(`  - Module ID: ${handler.id}`);
            }
        }

        // Check what getAvailableHandlers is returning
        const availableHandlers = this.getAvailableHandlers();
        console.log('\ngetAvailableHandlers() returns:', availableHandlers.map(h => h.id));
        
        // Check module registry
        console.log('\nModules in Registry:');
        const registry = window.moduleRegistry;
        if (registry && registry.modules) {
            // Find all TTS-related modules
            const ttsModules = Object.keys(registry.modules).filter(id => {
                const module = registry.modules[id];
                return module && (
                    id === 'tts-factory' ||
                    id === 'kokoro' ||
                    id === 'browser' ||
                    id === 'elevenlabs' ||
                    id === 'openai'
                );
            });
            
            ttsModules.forEach(id => {
                const module = registry.modules[id];
                console.log(`  - Module ID: ${id}`);
                console.log(`    - Type: ${module.constructor.name}`);
                console.log(`    - Is Initialized: ${module.isInitialized}`);
            });
        } else {
            console.log('  Module Registry not available');
        }
        
        console.log('===== DEBUG TTS HANDLERS END =====');
    }

    /**
     * For debugging: Log all registered modules in the registry
     */
    debugLogAllRegisteredModules() {
        console.log('=== DEBUG: All registered modules ===');
        if (window.moduleRegistry && window.moduleRegistry.modules) {
            const moduleIds = Object.keys(window.moduleRegistry.modules);
            moduleIds.forEach(id => {
                const module = window.moduleRegistry.modules[id];
                console.log(`Module [${id}]: ${module.constructor.name}`);
            });
            console.log(`Total modules: ${moduleIds.length}`);
        } else {
            console.log('Module registry not available');
        }
        console.log('=== END DEBUG ===');
    }

    /**
     * Clean up when module is disposed
     */
    dispose() {
        // Stop any active TTS
        if (this.activeHandler) {
            this.handlers[this.activeHandler].stop();
        }
        
        // Dispose all handlers
        for (const id in this.handlers) {
            if (this.handlers[id].dispose) {
                this.handlers[id].dispose();
            }
        }
        
        // Clear handlers
        this.handlers = {};
        this.activeHandler = null;
    }
}

// Create module instance 
const TTSFactory = new TTSFactoryModule();

// Export the module
export { TTSFactory };
