/**
 * Playback Coordinator Module
 * Synchronizes text animation with TTS audio playback to ensure exact timing match
 */
import { BaseModule } from './base-module.js';

class PlaybackCoordinatorModule extends BaseModule {
    constructor() {
        super('playback-coordinator', 'Playback Coordinator');

        // Module dependencies
        this.dependencies = ['animation-queue', 'tts-factory'];

        // Current playback state
        this.isPlaying = false;
        this.currentSentence = null;

        // Bind methods
        this.bindMethods([
            'play',
            'calculateWordTimings',
            'animateWords',
            'waitForAudioStart',
            'completeSentenceVisual',
            'fastForward',
            'stop'
        ]);
    }

    /**
     * Initialize the module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            this.reportProgress(50, "Initializing Playback Coordinator");

            // Verify dependencies
            const animQueue = this.getModule('animation-queue');
            const ttsFactory = this.getModule('tts-factory');

            if (!animQueue || !ttsFactory) {
                console.error("PlaybackCoordinator: Missing required dependencies");
                return false;
            }

            this.reportProgress(100, "Playback Coordinator ready");
            return true;
        } catch (error) {
            console.error("Error initializing Playback Coordinator:", error);
            return false;
        }
    }

    /**
     * Play a sentence with synchronized animation + TTS
     * @param {Object} sentence - Prepared sentence object with layout, TTS, and animation data
     * @returns {Promise<void>} - Resolves when playback completes
     */
    async play(sentence) {
        if (this.isPlaying) {
            console.warn('PlaybackCoordinator: Already playing, canceling previous');
            await this.stop();
        }

        this.isPlaying = true;
        this.currentSentence = sentence;

        try {
            // Start TTS first, then begin text animation when the audio element
            // confirms playback has started. Sentence preparation/prefetching is
            // handled by SentenceQueue and can still run while this sentence plays.
            const ttsPromise = this.playTTS(sentence);
            await this.waitForAudioStart(sentence, ttsPromise);
            const animPromise = this.animateWords(sentence);

            // Wait for both to complete
            await Promise.all([ttsPromise, animPromise]);

            console.log(`PlaybackCoordinator: Completed sentence ${sentence.id}`);
        } catch (error) {
            console.error('PlaybackCoordinator: Error during playback:', error);
            throw error;
        } finally {
            this.completeSentenceVisual(sentence);
            this.isPlaying = false;
            this.currentSentence = null;
        }
    }

    completeSentenceVisual(sentence) {
        if (!sentence?.element) return;
        sentence.element.dataset.playbackComplete = 'true';
        sentence.element.querySelectorAll('.word').forEach(word => {
            word.style.transition = 'none';
            word.style.animation = 'none';
            word.style.visibility = 'visible';
            word.style.opacity = '1';
            word.style.transform = 'translateY(0)';
            word.style.clipPath = 'inset(0 0 0 0)';
        });
    }

    /**
     * Play TTS audio for a sentence
     * @param {Object} sentence - Sentence object with TTS data
     * @returns {Promise<void>} - Resolves when TTS completes
     */
    async playTTS(sentence) {
        if (!sentence.tts || !sentence.tts.enabled) {
            // TTS disabled, return immediately
            return Promise.resolve();
        }

        try {
            document.dispatchEvent(new CustomEvent('tts:playback-start', {
                detail: { sentenceId: sentence.id }
            }));
            if (typeof sentence.tts.play === 'function') {
                await sentence.tts.play();
            } else {
                console.warn('PlaybackCoordinator: TTS play function not available');
            }
        } catch (error) {
            console.error('PlaybackCoordinator: TTS playback error:', error);
            // Don't throw - allow animation to continue
        } finally {
            document.dispatchEvent(new CustomEvent('tts:playback-end', {
                detail: { sentenceId: sentence.id }
            }));
        }
    }

    async waitForAudioStart(sentence, ttsPromise) {
        if (!sentence.tts || !sentence.tts.enabled) {
            return;
        }

        return new Promise((resolve) => {
            let settled = false;
            const cleanup = () => {
                document.removeEventListener('tts:audio-started', onStarted);
                document.removeEventListener('tts:playback-end', onEnded);
                clearTimeout(timeout);
            };
            const finish = (reason) => {
                if (settled) {
                    return;
                }
                settled = true;
                cleanup();
                console.log(`PlaybackCoordinator: Animation start released (${reason}) for ${sentence.id}`);
                resolve();
            };
            const onStarted = () => finish('audio-started');
            const onEnded = (event) => {
                if (!event.detail || event.detail.sentenceId === sentence.id) {
                    finish('tts-ended-before-start');
                }
            };
            const timeout = setTimeout(() => finish('audio-start-timeout'), 1500);

            document.addEventListener('tts:audio-started', onStarted, { once: true });
            document.addEventListener('tts:playback-end', onEnded);

            Promise.resolve(ttsPromise).then(() => finish('tts-promise-resolved')).catch(() => finish('tts-promise-rejected'));
        });
    }

    /**
     * Animate words using calculated timings
     * @param {Object} sentence - Sentence object with animation data and DOM element
     * @returns {Promise<void>} - Resolves when animation completes
     */
    async animateWords(sentence) {
        if (!sentence.element || !sentence.animation || !sentence.animation.wordTimings) {
            console.error('PlaybackCoordinator: Missing animation data');
            return Promise.resolve();
        }

        const animQueue = this.getModule('animation-queue');
        if (!animQueue) {
            console.error('PlaybackCoordinator: Animation queue not available');
            return Promise.resolve();
        }

        const wordElements = sentence.element.querySelectorAll('.word');
        let wordTimings = sentence.animation.wordTimings;
        let cueTimings = sentence.animation.cueTimings || [];

        if (wordElements.length !== wordTimings.length) {
            console.info(`PlaybackCoordinator: Word count mismatch (DOM: ${wordElements.length}, timings: ${wordTimings.length}); recalculating timings from rendered words`);
            const renderedWords = Array.from(wordElements).map(word => word.textContent || '');
            const duration = sentence.tts?.duration || sentence.animation.totalDuration || 0;
            wordTimings = this.calculateWordTimings(renderedWords, duration).wordTimings;
            cueTimings = cueTimings.map(cue => {
                const wordIndex = Math.max(0, Math.min(cue.wordIndex || 0, wordTimings.length - 1));
                return {
                    ...cue,
                    delay: (wordTimings[wordIndex] || { delay: duration }).delay
                };
            });
        }

        return new Promise((resolve) => {
            const totalDuration = wordTimings.length > 0
                ? Math.max(...wordTimings.map(timing => timing.delay + timing.duration))
                : 0;

            console.log(`PlaybackCoordinator: Animating ${wordTimings.length} words over ${totalDuration}ms`);
            if (wordTimings.length > 0) {
                console.log(`  First word delay: ${wordTimings[0].delay}ms, Last word delay: ${wordTimings[wordTimings.length-1].delay}ms`);
            }

            // Schedule each word animation
            wordTimings.forEach((timing, i) => {
                if (i < wordElements.length) {
                    animQueue.schedule(() => {
                        const word = wordElements[i];
                        const duration = Math.max(0, timing.duration || 0);
                        word.style.transition = 'none';
                        word.style.animation = 'none';
                        word.style.visibility = 'visible';
                        word.style.opacity = '1';
                        word.style.transform = 'translateY(0)';
                        word.style.clipPath = 'inset(0 100% 0 0)';
                        word.style.animation = `wordReveal ${duration}ms linear forwards`;
                    }, timing.delay);
                }
            });

            cueTimings.forEach(cue => {
                animQueue.schedule(() => {
                    document.dispatchEvent(new CustomEvent('story:media-cue', {
                        detail: {
                            sentenceId: sentence.id,
                            ...cue
                        }
                    }));
                }, cue.delay || 0);
            });

            // Schedule completion callback
            animQueue.schedule(() => {
                resolve();
            }, totalDuration + 100); // Small buffer
        });
    }

    /**
     * Calculate word-level timing to match total TTS duration
     * This is a utility method that can be called by SentenceQueue during preparation
     * @param {Array<string>} words - Array of words to animate
     * @param {number} totalDuration - Total duration in milliseconds
     * @returns {Object} - Object with wordTimings array and totalDuration
     */
    calculateWordTimings(words, totalDuration) {
        if (!words || words.length === 0) {
            return {
                wordTimings: [],
                totalDuration: 0
            };
        }

        // Calculate characters per word
        const totalChars = words.reduce((sum, word) => sum + word.length, 0);

        if (totalChars === 0) {
            // Edge case: all empty words
            return {
                wordTimings: words.map(word => ({ word, delay: 0, duration: 0 })),
                totalDuration: 0
            };
        }

        const msPerChar = totalDuration / totalChars;

        let currentDelay = 0;
        const wordTimings = words.map(word => {
            const duration = word.length * msPerChar;
            const timing = {
                word: word,
                delay: currentDelay,
                duration: duration
            };
            currentDelay += duration;
            return timing;
        });

        return {
            wordTimings,
            totalDuration: Math.round(currentDelay)
        };
    }

    /**
     * Fast forward current playback
     * Completes all animations immediately and stops TTS
     */
    async fastForward() {
        if (!this.isPlaying || !this.currentSentence) {
            return;
        }

        console.log('PlaybackCoordinator: Fast forwarding');

        const animQueue = this.getModule('animation-queue');
        if (animQueue) {
            animQueue.fastForward();
        }

        const ttsFactory = this.getModule('tts-factory');
        if (ttsFactory && typeof ttsFactory.fadeOut === 'function') {
            await ttsFactory.fadeOut(1000);
        } else if (this.currentSentence.tts && typeof this.currentSentence.tts.stop === 'function') {
            await new Promise(resolve => {
                setTimeout(() => {
                    this.currentSentence.tts.stop();
                    resolve();
                }, 1000);
            });
        }

        // Complete all word animations immediately
        this.completeSentenceVisual(this.currentSentence);
    }

    /**
     * Stop current playback
     */
    async stop() {
        if (!this.isPlaying) {
            return;
        }

        console.log('PlaybackCoordinator: Stopping');

        // Stop TTS
        if (this.currentSentence && this.currentSentence.tts && typeof this.currentSentence.tts.stop === 'function') {
            this.currentSentence.tts.stop();
        }

        // Clear animation queue
        const animQueue = this.getModule('animation-queue');
        if (animQueue) {
            animQueue.clearAll();
        }

        this.isPlaying = false;
        this.currentSentence = null;
        document.dispatchEvent(new CustomEvent('tts:playback-end', {
            detail: { reason: 'playback-coordinator-stop' }
        }));
    }
}

// Create the singleton instance
const PlaybackCoordinator = new PlaybackCoordinatorModule();

// Export the module
export { PlaybackCoordinator };

// Register with the module registry
if (window.moduleRegistry) {
    window.moduleRegistry.register(PlaybackCoordinator);
}

// Keep a reference in window for loader system
window.PlaybackCoordinator = PlaybackCoordinator;
