/**
 * KokoroTTSModule for AI Interactive Fiction
 * Implementation using the Kokoro library
 */
import { TTSHandlerModule } from './tts-handler-module.js';

export class KokoroTTSModule extends TTSHandlerModule {
    constructor() {
        super('kokoro-tts', 'Kokoro TTS');
        
        // Declare proper dependencies according to architecture principles
        this.dependencies = ['persistence-manager', 'localization', 'game-config'];
        
        // State
        this.iframe = null;
        this.currentAudio = null;
        this.pendingGenerations = new Map();
        this.generationCounter = 0;
        this.voices = [];
        this.lastProgressTime = null;
        this.lastProgressValue = null;
        this.modelLoaded = false;

        // Options for playback
        this.options = {
            volume: 1.0,
            rate: 1.0
        };
        
        // Bind additional methods beyond those in TTSHandlerModule
        this.bindMethods([
            'handleIframeMessage',
            'setupVoiceFromPreferences',
            'generateSpeech',
            'speakPreloaded',
            'preprocessText',
            'pause',
            'resume',
            'getDefaultVoices',
            'setVoiceOptions'
        ]);
    }
    
    /**
     * Initialize the Kokoro TTS module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            console.log('Kokoro TTS: Initializing');
            
            // Get dependencies
            this.reportProgress(10, 'Loading dependencies');
            
            // The persistence manager is required for preferences
            const persistenceManager = this.getModule('persistence-manager');
            if (!persistenceManager) {
                console.error('Kokoro TTS: Required dependency persistence-manager not found');
                return false;
            }

            this.addEventListener(document, 'preference-updated', (event) => {
                const { category, key } = event.detail || {};
                if (category === 'audio' && ['masterVolume', 'ttsVolume', 'masterVolumeEnabled', 'ttsVolumeEnabled'].includes(key) && this.currentAudio) {
                    this.currentAudio.volume = this.getPlaybackVolume();
                }
            });

            const ttsEnabled = persistenceManager.getPreference('tts', 'enabled', false);
            const preferredHandler = persistenceManager.getPreference('tts', 'preferred_handler', 'none');
            if (!ttsEnabled || preferredHandler !== this.id) {
                this.voices = this.getDefaultVoices();
                this.isReady = false;
                this.reportProgress(100, 'Kokoro TTS not selected');
                console.log('Kokoro TTS: Skipping model load because provider is not selected');
                return true;
            }
            
            // Try to check if the kokoro-js.js resource exists before proceeding
            try {
                this.reportProgress(20, 'Checking for Kokoro TTS resources');
                const response = await fetch('/js/kokoro-js.js', { method: 'HEAD' });
                if (!response.ok) {
                    console.error(`Kokoro TTS: Required resource kokoro-js.js not found (${response.status})`);
                    throw new Error('Kokoro TTS resource not available');
                }
                console.log('Kokoro TTS: Resources available');
            } catch (resourceError) {
                console.error('Kokoro TTS: Error checking resources', resourceError);
                return false;
            }
            
            // Create iframe for Kokoro TTS
            this.reportProgress(30, 'Creating Kokoro TTS iframe');
            console.log('Kokoro TTS: Creating iframe for Kokoro loader');
            const iframe = document.createElement('iframe');
            iframe.src = '/kokoro-loader.html';
            iframe.style.display = 'none';
            document.body.appendChild(iframe);
            this.iframe = iframe;
            
            // Wait for iframe to load
            try {
                await new Promise((resolve, reject) => {
                    iframe.onload = () => {
                        console.log('Kokoro TTS: Iframe loaded successfully');
                        resolve();
                    };
                    
                    iframe.onerror = (error) => {
                        console.error('Kokoro TTS: Iframe failed to load:', error);
                        reject(new Error('Kokoro TTS: Iframe failed to load'));
                    };
                    
                    iframe.onabort = () => {
                        console.error('Kokoro TTS: Iframe load aborted');
                        reject(new Error('Kokoro TTS: Iframe load aborted'));
                    };
                });
            } catch (iframeError) {
                console.error('Kokoro TTS: Error loading iframe:', iframeError);
                return false;
            }
            
            // Add message event listener for progress updates from iframe
            window.addEventListener('message', this.handleIframeMessage);
            
            // Wait for model to initialize
            try {
                this.reportProgress(50, 'Loading Kokoro model');
                console.log('Kokoro TTS: Waiting for model to initialize');
                
                await new Promise((resolve, reject) => {
                    // Create one-time handler for kokoro:ready message
                    const readyHandler = (event) => {
                        if (event.data && event.data.type === 'kokoro:ready') {
                            window.removeEventListener('message', readyHandler);
                            
                            // Validate the success status from the event
                            if (event.data.success === false) {
                                console.error('Kokoro TTS: Model initialization failed:', event.data.error || 'Unknown error');
                                reject(new Error('Kokoro TTS: ' + (event.data.error || 'Model initialization failed')));
                                return;
                            }
                            
                            console.log('Kokoro TTS: Model initialized successfully');
                            this.modelLoaded = true;
                            this.voices = event.data.voices || this.getDefaultVoices();
                            resolve();
                        }
                    };
                    
                    window.addEventListener('message', readyHandler);
                    
                    // Send initialization message to iframe
                    this.reportProgress(60, 'Initializing Kokoro model');
                    console.log('Kokoro TTS: Sending initialization message to iframe');
                    iframe.contentWindow.postMessage({ type: 'kokoro:initialize' }, '*');
                });
            } catch (modelError) {
                console.error('Kokoro TTS: Error initializing model:', modelError);
                return false;
            }
            
            // Get default voices
            this.reportProgress(80, 'Loading Kokoro voices');
            this.voices = this.getDefaultVoices();
            console.log('Kokoro TTS: Loaded default voices:', this.voices);
            
            // Set voice based on preferences
            this.reportProgress(90, 'Setting up voice preferences');
            await this.setupVoiceFromPreferences(persistenceManager);
            console.log('Kokoro TTS: Voice preferences set up');
            
            this.isReady = true;
            this.reportProgress(100, 'Kokoro TTS initialized');
            console.log('Kokoro TTS: Initialization complete');
            
            return true;
        } catch (error) {
            console.error('Kokoro TTS: Initialization error:', error);
            this.isReady = false;
            return false;
        }
    }
    
    /**
     * Handle messages from the iframe
     * @param {MessageEvent} event - Message event
     */
    handleIframeMessage = (event) => {
        // Only process messages from our iframe
        if (!this.iframe || event.source !== this.iframe.contentWindow) {
            return;
        }
        
        // Process message
        if (event.data && event.data.type) {
            switch (event.data.type) {
                case 'kokoro:progress':
                    if (event.data.progress) {
                        // Track the last time we received a progress update
                        this.lastProgressTime = Date.now();
                        this.lastProgressValue = event.data.progress;
                        this.modelLoadingProgress = event.data.progress;
                        
                        // Update progress
                        this.reportProgress(60 + Math.floor(event.data.progress * 0.3), `Loading Kokoro model: ${event.data.progress.toFixed(0)}%`);
                    }
                    break;
                    
                case 'kokoro:ready':
                    // Clear any timeout we might have set
                    this.modelLoaded = true;
                    this.reportProgress(90, 'Kokoro model loaded');
                    console.log('Kokoro TTS: Model ready event received');
                    break;
                    
                case 'kokoro:error':
                    console.error('Kokoro TTS: Error from iframe:', event.data.error);
                    // this.changeState('ERROR');
                    break;
                    
                case 'kokoro-generated':
                    // Handle speech generation completion
                    if (event.data.id !== undefined && this.pendingGenerations.has(event.data.id)) {
                        const resolver = this.pendingGenerations.get(event.data.id);
                        this.pendingGenerations.delete(event.data.id);
                        
                        if (!event.data.success || event.data.error) {
                            resolver.reject(new Error(event.data.error || 'Speech generation failed'));
                        } else {
                            const audioData = event.data.result && event.data.result.buffer;
                            console.log('Kokoro: Generation complete, audioData:', audioData ? `${audioData.byteLength} bytes` : 'UNDEFINED');
                            resolver.resolve({
                                success: true,
                                audioData: audioData,
                                duration: event.data.duration || 0
                            });
                        }
                    }
                    break;
                    
                case 'kokoro:voices':
                    // Update available voices
                    if (Array.isArray(event.data.voices)) {
                        this.voices = event.data.voices;
                        document.dispatchEvent(new CustomEvent('tts:voices-updated', { 
                            detail: { engine: 'kokoro', voices: this.voices } 
                        }));
                    }
                    break;
            }
        }
    }
    
    /**
     * Set up the voice from preferences
     */
    async setupVoiceFromPreferences(persistenceManager) {
        if (!persistenceManager) {
            return false;
        }
        
        // Get current locale
        const gameConfig = this.getModule('game-config');
        const locale = gameConfig?.getLocale?.() || 'en_US';
        
        // Get preferred voice from preferences
        const preferredVoiceId = persistenceManager.getPreference('tts', 'kokoro_voice', '');
        
        // Find matching voice
        let selectedVoice = null;
        
        if (preferredVoiceId) {
            // Try to find the specific voice
            selectedVoice = this.voices.find(v => v.id === preferredVoiceId);
        }
        
        if (!selectedVoice) {
            // Find a voice for the current locale
            const normalizedLocale = locale ? locale.toLowerCase().replace('_', '-') : 'en-us';
            const languageCode = normalizedLocale.split('-')[0];
            
            // Try to find an exact locale match
            selectedVoice = this.voices.find(v => 
                v.lang && v.lang.toLowerCase() === normalizedLocale
            );
            
            // If not found, try to find a language match
            if (!selectedVoice) {
                selectedVoice = this.voices.find(v => 
                    v.lang && v.lang.toLowerCase().startsWith(languageCode)
                );
            }
            
            // If still not found, use the first voice
            if (!selectedVoice && this.voices.length > 0) {
                selectedVoice = this.voices[0];
            }
        }
        
        // Set the voice
        if (selectedVoice) {
            this.setVoice(selectedVoice);
            return true;
        }
        
        return false;
    }
    
    /**
     * Set voice for TTS
     * @param {Object} voice - Voice to set
     * @returns {boolean} - Success status
     */
    setVoice(voice) {
        if (!voice || !voice.id) {
            return false;
        }
        
        this.currentVoice = voice;
        
        // Save to preferences
        const persistenceManager = this.getModule('persistence-manager');
        if (persistenceManager) {
            persistenceManager.updatePreference('tts', 'kokoro_voice', voice.id);
        }
        
        // Send message to iframe
        if (this.iframe && this.iframe.contentWindow) {
            this.iframe.contentWindow.postMessage({
                type: 'kokoro:set-voice',
                voiceId: voice.id
            }, '*');
        }
        
        return true;
    }
    
    /**
     * Set options for TTS
     * @param {Object} options - Options to set
     * @returns {boolean} - Success status
     */
    setOptions(options) {
        if (!options) {
            return false;
        }
        
        // Update rate and volume if provided
        if (options.rate !== undefined) {
            this.options.rate = options.rate;
        }
        
        if (options.volume !== undefined) {
            this.options.volume = options.volume;
        }
        
        return true;
    }

    setVoiceOptions(options = {}) {
        if (options.voice) {
            const voice = this.voices.find(v => v.id === options.voice) || { id: options.voice };
            this.setVoice(voice);
        }

        if (typeof options.speed === 'number') {
            this.setOptions({ rate: Math.max(0.5, Math.min(2.0, options.speed)) });
        }

        if (typeof options.volume === 'number') {
            this.setOptions({ volume: Math.max(0, Math.min(1, options.volume)) });
        }
    }

    getPlaybackVolume() {
        const persistenceManager = this.getModule('persistence-manager');
        if (!persistenceManager) {
            return this.options.volume;
        }

        const masterVolume = persistenceManager.getPreference('audio', 'masterVolume', 1.0);
        const ttsVolume = persistenceManager.getPreference('audio', 'ttsVolume', 1.0);
        const masterEnabled = persistenceManager.getPreference('audio', 'masterVolumeEnabled', true) !== false;
        const ttsEnabled = persistenceManager.getPreference('audio', 'ttsVolumeEnabled', true) !== false;

        return Math.max(0, Math.min(1, this.options.volume * (masterEnabled ? masterVolume : 0) * (ttsEnabled ? ttsVolume : 0)));
    }
    
    /**
     * Get available voices
     * @returns {Array} - Array of voice objects
     */
    async getVoices() {
        // If no voices are loaded yet, return default voices
        if (!this.voices || this.voices.length === 0) {
            return this.getDefaultVoices();
        }
        
        return this.voices;
    }
    
    /**
     * Preprocess text for TTS
     * @param {string} text - Text to preprocess
     * @returns {string} - Preprocessed text
     */
    preprocessText(text) {
        // Remove HTML tags
        text = text.replace(/<[^>]*>/g, ' ');
        
        // Replace special characters
        text = text.replace(/&/g, ' and ');
        
        // Normalize whitespace
        text = text.replace(/\s+/g, ' ').trim();
        
        return text;
    }
    
    /**
     * Preload speech for later playback
     * @param {string} text - Text to preload
     * @returns {Promise<Object>} - Resolves with preloaded audio data
     */
    async preloadSpeech(text) {
        if (!this.isReady) {
            return { success: false, reason: 'not_ready' };
        }
        
        // Generate speech audio data
        const result = await this.generateSpeech(text);
        
        if (!result.success) {
            return { success: false, reason: 'generation_failed' };
        }
        
        return {
            success: true,
            audioData: result.audioData,
            text,
            duration: result.duration || 0
        };
    }
    
    /**
     * Speak text using preloaded audio
     * @param {Object} preloadData - Preloaded audio data
     * @param {Function} callback - Callback for when speech completes
     * @returns {boolean} - Success status
     */
    speakPreloaded(preloadData, callback = null) {
        const completionCallback = typeof callback === 'function' ? callback : null;
        if (!this.isReady || !preloadData || !preloadData.audioData) {
            if (completionCallback) {
                completionCallback({ success: false, reason: 'invalid_data' });
            }
            return false;
        }
        
        // Stop any ongoing speech
        this.stop();
        
        // Create audio from blob
        const audioBlob = new Blob([preloadData.audioData], { type: 'audio/mp3' });
        const audioUrl = URL.createObjectURL(audioBlob);
        
        const audio = new Audio(audioUrl);
        audio.volume = this.getPlaybackVolume();
        audio.playbackRate = this.options.rate;
        
        // Set up event handlers
        audio.onended = () => {
            this.isSpeaking = false;
            if (completionCallback) {
                completionCallback({ success: true });
            }
            URL.revokeObjectURL(audioUrl);
        };
        
        audio.onerror = (error) => {
            this.isSpeaking = false;
            if (completionCallback) {
                completionCallback({ success: false, reason: 'playback_error', error });
            }
            URL.revokeObjectURL(audioUrl);
        };
        
        // Start playback
        this.currentAudio = audio;
        this.isSpeaking = true;
        audio.play().then(() => {
            document.dispatchEvent(new CustomEvent('tts:audio-started', {
                detail: { provider: this.id || this.name }
            }));
        }).catch(error => {
            this.isSpeaking = false;
            if (completionCallback) {
                completionCallback({ success: false, reason: 'playback_error', error });
            }
            URL.revokeObjectURL(audioUrl);
        });
        
        return true;
    }
    
    /**
     * Speak text
     * @param {string} text - Text to speak
     * @param {Function} callback - Callback for when speech completes
     * @returns {boolean} - Success status
     */
    speak(text, callback = null) {
        if (!this.isReady) {
            if (callback) {
                callback({ success: false, reason: 'not_ready' });
            }
            return false;
        }
        
        // Preprocess text
        const processedText = this.preprocessText(text);
        
        // Generate and play speech
        this.generateSpeech(processedText).then(result => {
            if (result.success && result.audioData) {
                // Create audio blob and URL
                const audioBlob = new Blob([result.audioData], { type: 'audio/mp3' });
                const audioUrl = URL.createObjectURL(audioBlob);
                
                // Stop any ongoing speech
                this.stop();
                
                // Create and play audio
                const audio = new Audio(audioUrl);
                audio.volume = this.getPlaybackVolume();
                audio.playbackRate = this.options.rate;
                
                // Set up event handlers
                audio.onended = () => {
                    this.isSpeaking = false;
                    if (callback) {
                        callback({ success: true });
                    }
                    URL.revokeObjectURL(audioUrl);
                };
                
                audio.onerror = (error) => {
                    this.isSpeaking = false;
                    if (callback) {
                        callback({ success: false, reason: 'playback_error', error });
                    }
                    URL.revokeObjectURL(audioUrl);
                };
                
                // Start playback
                this.currentAudio = audio;
                this.isSpeaking = true;
                audio.play().then(() => {
                    document.dispatchEvent(new CustomEvent('tts:audio-started', {
                        detail: { provider: this.id || this.name }
                    }));
                }).catch(error => {
                    this.isSpeaking = false;
                    if (callback) {
                        callback({ success: false, reason: 'playback_error', error });
                    }
                });
            } else {
                if (callback) {
                    callback({ success: false, reason: 'generation_failed' });
                }
            }
        }).catch(error => {
            if (callback) {
                callback({ success: false, reason: 'generation_error', error });
            }
        });
        
        return true;
    }
    
    /**
     * Generate speech using the iframe
     * @param {string} text - Text to generate speech for
     * @returns {Promise<Object>} - Resolves with audio data
     */
    async generateSpeech(text) {
        if (!this.isReady || !this.iframe || !this.iframe.contentWindow) {
            return { success: false, reason: 'not_ready' };
        }
        
        // Process text
        const processedText = this.preprocessText(text);
        
        return new Promise((resolve, reject) => {
            // Generate unique ID for this request
            const id = this.generationCounter++;
            
            // Store resolver functions
            this.pendingGenerations.set(id, { resolve, reject });
            
            // Send request to iframe
            this.iframe.contentWindow.postMessage({
                type: 'kokoro-generate',
                text: processedText,
                id,
                voice: this.currentVoice ? this.currentVoice.id : null
            }, '*');
        });
    }
    
    /**
     * Stop current speech
     * @returns {boolean} - Success status
     */
    stop() {
        if (this.currentAudio) {
            try {
                this.currentAudio.pause();
                this.currentAudio.currentTime = 0;
                this.currentAudio = null;
                this.isSpeaking = false;
                return true;
            } catch (error) {
                console.error('Kokoro TTS: Error stopping speech:', error);
                return false;
            }
        }
        return true;
    }
    
    /**
     * Pause current speech
     * @returns {boolean} - Success status
     */
    pause() {
        if (this.currentAudio) {
            try {
                this.currentAudio.pause();
                return true;
            } catch (error) {
                console.error('Kokoro TTS: Error pausing speech:', error);
                return false;
            }
        }
        return true;
    }
    
    /**
     * Resume current speech
     * @returns {boolean} - Success status
     */
    resume() {
        if (this.currentAudio) {
            try {
                this.currentAudio.play();
                return true;
            } catch (error) {
                console.error('Kokoro TTS: Error resuming speech:', error);
                return false;
            }
        }
        return false;
    }
    
    /**
     * Get default voices for current locale
     * @returns {Array} Default voices
     */
    getDefaultVoices() {
        return [
            // American Female voices
            { id: 'af_heart', name: 'Heart', lang: 'en-US', gender: 'female' },
            { id: 'af_daisy', name: 'Daisy', lang: 'en-US', gender: 'female' },
            { id: 'af_soft', name: 'Soft', lang: 'en-US', gender: 'female' },
            { id: 'af_glados', name: 'GLaDOS', lang: 'en-US', gender: 'female' },
            { id: 'af_southern_belle', name: 'Southern Belle', lang: 'en-US', gender: 'female' },
            { id: 'af_dramatic', name: 'Dramatic', lang: 'en-US', gender: 'female' },
            { id: 'af_valley_girl', name: 'Valley Girl', lang: 'en-US', gender: 'female' },
            { id: 'af_british', name: 'British', lang: 'en-US', gender: 'female' },
            { id: 'af_russian', name: 'Russian', lang: 'en-US', gender: 'female' },
            { id: 'af_german', name: 'German', lang: 'en-US', gender: 'female' },
            { id: 'af_cheeky_cute', name: 'Cheeky Cute', lang: 'en-US', gender: 'female' },
            
            // American Male voices
            { id: 'am_bruce', name: 'Bruce', lang: 'en-US', gender: 'male' },
            { id: 'am_announcer', name: 'Announcer', lang: 'en-US', gender: 'male' },
            { id: 'am_radio_host', name: 'Radio Host', lang: 'en-US', gender: 'male' },
            
            // British Female voices
            { id: 'bf_charlotte', name: 'Charlotte', lang: 'en-GB', gender: 'female' },
            { id: 'bf_elizabeth', name: 'Elizabeth', lang: 'en-GB', gender: 'female' },
            { id: 'bf_lily', name: 'Lily', lang: 'en-GB', gender: 'female' },
            { id: 'bf_olivia', name: 'Olivia', lang: 'en-GB', gender: 'female' },
            { id: 'bf_victoria', name: 'Victoria', lang: 'en-GB', gender: 'female' },
            
            // British Male voices
            { id: 'bm_william', name: 'William', lang: 'en-GB', gender: 'male' },
            { id: 'bm_arthur', name: 'Arthur', lang: 'en-GB', gender: 'male' },
            { id: 'bm_george', name: 'George', lang: 'en-GB', gender: 'male' },
            { id: 'bm_harry', name: 'Harry', lang: 'en-GB', gender: 'male' },
            { id: 'bm_jack', name: 'Jack', lang: 'en-GB', gender: 'male' }
        ];
    }
}

const kokoroTTSModule = new KokoroTTSModule();

export { kokoroTTSModule };
