/**
 * AudioManager Module
 * Manages loading and playback of non-TTS audio effects triggered by tags.
 */
import { BaseModule } from './base-module.js';

class AudioManagerModule extends BaseModule {
    constructor() {
        super('audio-manager', 'Audio Manager');
        this.sounds = new Map();
        this.sfxCache = new Map();
        this.currentAudio = null;
        this.currentAudioRole = null;
        this.currentLoop = null;
        this.currentMusic = null;
        this.queuedMusic = null;
        this.masterVolume = 1.0;
        this.musicVolume = 1.0;
        this.sfxVolume = 1.0;
        this.ttsVolume = 1.0;
        this.masterVolumeEnabled = true;
        this.musicVolumeEnabled = true;
        this.sfxVolumeEnabled = true;
        this.ttsVolumeEnabled = true;
        this.musicDuckingAmount = 0.3;
        this.musicDuckingEnabled = true;
        this.musicDuckingFactor = 1.0;
        this.musicFadeToken = 0;
        this.activeTtsPlaybackCount = 0;
        this.ttsQueueEmpty = true;
        this.pendingMusicPlayback = null;
        this.currentMusicState = null;
        this.assetRoots = {
            images: '/images/',
            music: '/music/',
            sounds: '/sounds/'
        };
        
        // Add persistence-manager as a dependency
        this.dependencies = ['persistence-manager'];
    }
    
    /**
     * Load module dependencies
     * @returns {Promise} - Resolves when dependencies are loaded
     */
    async loadDependencies() {
        try {
            this.reportProgress(40, "Initializing audio system");
            return true;
        } catch (error) {
            console.error("Error loading AudioManager dependencies:", error);
            return false;
        }
    }
    
    /**
     * Initialize the module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            // Set up audio context if needed
            this.setupAudioContext();
            this.loadPersistedVolumes();
            this.setupEventListeners();
            
            // Load some basic sound effects
            this.reportProgress(80, "Loading sound effects");
            
            this.reportProgress(100, "Audio system ready");
            return true;
        } catch (error) {
            console.error("Error initializing AudioManager:", error);
            return false;
        }
    }

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

        this.masterVolume = this.clampVolume(persistenceManager.getPreference('audio', 'masterVolume', this.masterVolume));
        this.musicVolume = this.clampVolume(persistenceManager.getPreference('audio', 'musicVolume', this.musicVolume));
        this.sfxVolume = this.clampVolume(persistenceManager.getPreference('audio', 'sfxVolume', this.sfxVolume));
        this.ttsVolume = this.clampVolume(persistenceManager.getPreference('audio', 'ttsVolume', this.ttsVolume));
        this.masterVolumeEnabled = persistenceManager.getPreference('audio', 'masterVolumeEnabled', this.masterVolumeEnabled) !== false;
        this.musicVolumeEnabled = persistenceManager.getPreference('audio', 'musicVolumeEnabled', this.musicVolumeEnabled) !== false;
        this.sfxVolumeEnabled = persistenceManager.getPreference('audio', 'sfxVolumeEnabled', this.sfxVolumeEnabled) !== false;
        this.ttsVolumeEnabled = persistenceManager.getPreference('audio', 'ttsVolumeEnabled', this.ttsVolumeEnabled) !== false;
        this.musicDuckingAmount = this.clampVolume(persistenceManager.getPreference('audio', 'musicDuckingAmount', this.musicDuckingAmount));
        this.musicDuckingEnabled = persistenceManager.getPreference('audio', 'musicDuckingEnabled', this.musicDuckingEnabled) !== false;
    }

    setupEventListeners() {
        this.addEventListener(document, 'story:media-cue', (event) => {
            this.handleMediaCue(event.detail || {});
        });

        this.addEventListener(document, 'story:media-block', (event) => {
            this.handleMediaBlock(event.detail || {});
        });

        this.addEventListener(document, 'story:tag', (event) => {
            this.handleStoryTag(event.detail || {});
        });

        this.addEventListener(document, 'game:config', (event) => {
            this.applyGameConfig(event.detail || {});
        });

        this.addEventListener(document, 'preference-updated', (event) => {
            const { category, key, value } = event.detail || {};
            if (category !== 'audio') {
                return;
            }

            if (key === 'masterVolume') this.setMasterVolume(value);
            if (key === 'musicVolume') this.setMusicVolume(value);
            if (key === 'sfxVolume') this.setSfxVolume(value);
            if (key === 'ttsVolume') this.setTtsVolume(value);
            if (key === 'masterVolumeEnabled') this.setVolumeEnabled('master', value);
            if (key === 'musicVolumeEnabled') this.setVolumeEnabled('music', value);
            if (key === 'sfxVolumeEnabled') this.setVolumeEnabled('sfx', value);
            if (key === 'ttsVolumeEnabled') this.setVolumeEnabled('tts', value);
            if (key === 'musicDuckingAmount') this.setMusicDuckingAmount(value);
            if (key === 'musicDuckingEnabled') this.setMusicDuckingEnabled(value);
        });

        this.addEventListener(document, 'tts:playback-start', () => {
            this.activeTtsPlaybackCount += 1;
            this.ttsQueueEmpty = false;
            this.duckMusicForSpeech();
        });

        this.addEventListener(document, 'tts:audio-started', () => {
            this.ttsQueueEmpty = false;
            this.duckMusicForSpeech();
        });

        this.addEventListener(document, 'tts:playback-end', () => {
            this.activeTtsPlaybackCount = Math.max(0, this.activeTtsPlaybackCount - 1);
            this.restoreMusicIfSpeechFinished();
        });

        this.addEventListener(document, 'tts:queue-empty', () => {
            this.ttsQueueEmpty = true;
            this.restoreMusicIfSpeechFinished();
        });

        this.addEventListener(document, 'tts:speechCompleted', () => {
            this.activeTtsPlaybackCount = Math.max(0, this.activeTtsPlaybackCount - 1);
            this.restoreMusicIfSpeechFinished();
        });

        const unlock = () => this.unlockPendingAudio();
        document.addEventListener('pointerdown', unlock, { passive: true });
        document.addEventListener('keydown', unlock);
    }

    applyGameConfig(config) {
        const assets = config?.assets || {};
        this.assetRoots = {
            images: assets.images || this.assetRoots.images,
            music: assets.music || this.assetRoots.music,
            sounds: assets.sounds || assets.sfx || this.assetRoots.sounds
        };
    }
    
    /**
     * Set up Web Audio API context if needed
     */
    setupAudioContext() {
        // Only create if needed for advanced audio features
        if (typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined') {
            const AudioContextClass = window.AudioContext || window.webkitAudioContext;
            this.audioContext = new AudioContextClass();
        }
    }

    /**
     * Load a sound file
     * @param {string} id - The identifier for the sound
     * @param {string} url - The URL of the sound file
     * @returns {Promise} A promise that resolves when the sound is loaded
     */
    loadSound(id, url) {
        return new Promise((resolve, reject) => {
            const audio = new Audio(url);
            audio.preload = 'auto';
            audio.addEventListener('canplaythrough', () => {
                this.sounds.set(id, audio);
                resolve(audio);
            }, { once: true });
            audio.addEventListener('error', (error) => {
                reject(error);
            });
            audio.load();
        });
    }

    /**
     * Play a sound
     * @param {string} id - The identifier for the sound
     * @param {boolean} loop - Whether to loop the sound
     * @returns {HTMLAudioElement|null} The audio element or null if not found
     */
    playSound(id, loop = false) {
        const audio = this.sounds.get(id);
        if (!audio) {
            console.warn(`Sound with id "${id}" not found.`);
            return null;
        }

        if (loop) {
            if (this.currentLoop) {
                this.currentLoop.pause();
                this.currentLoop.currentTime = 0;
            }
            audio.loop = true;
            this.currentLoop = audio;
        } else {
            this.currentAudio = audio.cloneNode(true);
            this.currentAudioRole = 'sfx';
            this.setMediaVolume(this.currentAudio, this.getSfxVolume());
            this.currentAudio.play().catch(error => {
                console.error('Error playing audio:', error);
            });
            return this.currentAudio;
        }

        this.setMediaVolume(audio, this.getMusicVolume());
        audio.play().catch(error => {
            console.error('Error playing audio:', error);
        });

        return audio;
    }

    /**
     * Play a sound from a URL directly (without preloading)
     * @param {string} url - The URL of the sound file
     * @param {boolean} loop - Whether to loop the sound
     * @returns {HTMLAudioElement} The audio element
     */
    playSoundFromUrl(url, loop = false) {
        if (loop) {
            if (this.currentLoop) {
                this.currentLoop.pause();
                this.currentLoop.removeAttribute('src');
                this.currentLoop.load();
            }
            this.currentLoop = new Audio(url);
            this.currentLoop.loop = true;
            this.setMediaVolume(this.currentLoop, this.getMusicVolume());
            this.currentLoop.play().catch(error => {
                console.error('Error playing audio loop:', error);
            });
            return this.currentLoop;
        } else {
            this.currentAudio = new Audio(url);
            this.currentAudioRole = 'sfx';
            this.setMediaVolume(this.currentAudio, this.getSfxVolume());
            this.currentAudio.play().catch(error => {
                console.error('Error playing audio:', error);
            });
            return this.currentAudio;
        }
    }

    /**
     * Stop a specific sound
     * @param {string} id - The identifier for the sound
     */
    stopSound(id) {
        const audio = this.sounds.get(id);
        if (audio) {
            audio.pause();
            audio.currentTime = 0;
        }
    }

    /**
     * Stop all sounds
     */
    stopAllSounds() {
        if (this.currentAudio) {
            this.currentAudio.pause();
            this.currentAudio.currentTime = 0;
            this.currentAudio = null;
            this.currentAudioRole = null;
        }

        if (this.currentLoop) {
            this.currentLoop.pause();
            this.currentLoop.currentTime = 0;
            this.currentLoop = null;
        }

        this.sounds.forEach(audio => {
            audio.pause();
            audio.currentTime = 0;
        });

        this.stopCurrentMusic();
        this.queuedMusic = null;
        this.pendingMusicPlayback = null;
        this.activeTtsPlaybackCount = 0;
        this.ttsQueueEmpty = true;
        this.musicDuckingFactor = 1.0;
        this.musicFadeToken += 1;
    }

    /**
     * Set the master volume for all sounds
     * @param {number} volume - The volume level (0.0 to 1.0)
     */
    setMasterVolume(volume) {
        this.masterVolume = this.clampVolume(volume);
        this.updateVolumes();
    }

    /**
     * Set the speech volume
     * @param {number} volume - The volume level (0.0 to 1.0)
     */
    setTtsVolume(volume) {
        this.ttsVolume = this.clampVolume(volume);
        this.updateVolumes();
    }

    /**
     * Set the music volume
     * @param {number} volume - The volume level (0.0 to 1.0)
     */
    setMusicVolume(volume) {
        this.musicVolume = this.clampVolume(volume);
        this.updateVolumes();
    }
    
    /**
     * Set the sound effects volume
     * @param {number} volume - The volume level (0.0 to 1.0)
     */
    setSfxVolume(volume) {
        this.sfxVolume = this.clampVolume(volume);
        this.updateVolumes();
    }

    setVolumeEnabled(kind, enabled) {
        const value = enabled !== false;
        if (kind === 'master') this.masterVolumeEnabled = value;
        if (kind === 'music') this.musicVolumeEnabled = value;
        if (kind === 'sfx') this.sfxVolumeEnabled = value;
        if (kind === 'tts') this.ttsVolumeEnabled = value;
        this.updateVolumes();
    }

    setMusicDuckingAmount(amount) {
        this.musicDuckingAmount = this.clampVolume(amount);
        if (this.musicDuckingFactor !== 1.0) {
            this.duckMusicForSpeech();
        } else {
            this.updateVolumes();
        }
    }

    setMusicDuckingEnabled(enabled) {
        this.musicDuckingEnabled = enabled !== false;
        if (this.musicDuckingFactor !== 1.0) {
            this.duckMusicForSpeech();
        } else {
            this.updateVolumes();
        }
    }
    
    /**
     * Update all volume levels based on current settings
     */
    updateVolumes() {
        this.sounds.forEach(audio => {
            const isMusic = audio.loop;
            this.setMediaVolume(audio, isMusic ? this.getMusicVolume() : this.getSfxVolume());
        });

        if (this.currentAudio) {
            this.setMediaVolume(this.currentAudio, this.currentAudioRole === 'tts' ? this.getTtsVolume() : this.getSfxVolume());
        }

        if (this.currentLoop) {
            this.setMediaVolume(this.currentLoop, this.getMusicVolume());
        }

        if (this.currentMusic) {
            this.setMediaVolume(this.currentMusic, this.getMusicVolume());
        }
    }

    clampVolume(volume) {
        return Math.max(0, Math.min(1, Number.isFinite(Number(volume)) ? Number(volume) : 1));
    }

    setMediaVolume(audio, volume) {
        if (!audio) return;
        audio.volume = this.clampVolume(volume);
    }

    getSfxVolume() {
        return this.getMasterVolume() * (this.sfxVolumeEnabled ? this.sfxVolume : 0);
    }

    getMusicVolume() {
        return this.getUnduckedMusicVolume() * this.musicDuckingFactor;
    }

    getUnduckedMusicVolume() {
        return this.getMasterVolume() * (this.musicVolumeEnabled ? this.musicVolume : 0);
    }

    getMasterVolume() {
        return this.masterVolumeEnabled ? this.masterVolume : 0;
    }

    getTtsVolume() {
        return this.getMasterVolume() * (this.ttsVolumeEnabled ? this.ttsVolume : 0);
    }

    duckMusicForSpeech() {
        console.log('AudioManager: Ducking music for TTS playback');
        const factor = this.musicDuckingEnabled ? 1 - this.musicDuckingAmount : 1;
        this.fadeMusicTo(factor, 500);
    }

    restoreMusicAfterSpeech() {
        console.log('AudioManager: Restoring music after TTS queue drained');
        this.fadeMusicTo(1.0, 900);
    }

    restoreMusicIfSpeechFinished() {
        if (this.activeTtsPlaybackCount === 0 && this.ttsQueueEmpty) {
            this.restoreMusicAfterSpeech();
        }
    }

    fadeMusicTo(factor, duration = 700) {
        this.musicDuckingFactor = Math.max(0, Math.min(1, factor));
        if (!this.currentMusic) {
            return;
        }

        const audio = this.currentMusic;
        const token = ++this.musicFadeToken;
        const startVolume = this.clampVolume(audio.volume);
        const targetVolume = this.clampVolume(this.getUnduckedMusicVolume() * this.musicDuckingFactor);
        const start = performance.now();

        const tick = () => {
            if (token !== this.musicFadeToken || this.currentMusic !== audio) {
                return;
            }
            const progress = Math.min(1, (performance.now() - start) / duration);
            this.setMediaVolume(audio, startVolume + ((targetVolume - startVolume) * progress));
            if (progress < 1) {
                requestAnimationFrame(tick);
            }
        };

        tick();
    }

    getAssetUrl(kind, filename) {
        const root = this.assetRoots[kind];
        if (!root) {
            throw new Error(`Unknown audio asset kind: ${kind}`);
        }

        const safeName = String(filename || '').replace(/\\/g, '/').replace(/^\/+/, '');
        if (!safeName || safeName.includes('..') || /^[a-z]+:/i.test(safeName)) {
            throw new Error(`Unsafe asset filename: ${filename}`);
        }

        return root + safeName.split('/').map(encodeURIComponent).join('/');
    }

    async preloadSfx(filename) {
        const url = this.getAssetUrl('sounds', filename);
        if (this.sfxCache.has(url)) {
            return this.sfxCache.get(url);
        }

        const promise = new Promise((resolve, reject) => {
            const audio = new Audio(url);
            audio.preload = 'auto';
            this.setMediaVolume(audio, this.getSfxVolume());
            audio.addEventListener('canplaythrough', () => resolve(audio), { once: true });
            audio.addEventListener('error', () => reject(new Error(`Failed to preload sound effect: ${url}`)), { once: true });
            audio.load();
        });

        this.sfxCache.set(url, promise);
        return promise;
    }

    async preloadMediaCues(cues = []) {
        const tasks = cues
            .filter(cue => cue && cue.type === 'sfx' && cue.filename)
            .map(cue => this.preloadSfx(cue.filename).catch(error => {
                console.warn('AudioManager: SFX preload failed:', error);
                return null;
            }));

        await Promise.all(tasks);
    }

    handleMediaCue(cue) {
        if (!cue || !cue.type) {
            return;
        }

        if (cue.type === 'sfx') {
            this.playSfx(cue.filename, cue);
        } else if (cue.type === 'music') {
            this.playMusic(cue.filename, cue.mode || 'crossfade', { loop: cue.loop !== false });
        }
    }

    handleMediaBlock(block) {
        if (!block || block.type !== 'music') {
            return;
        }

        this.playMusic(block.filename, block.mode || 'crossfade', { loop: block.loop !== false });
    }

    handleStoryTag(tag) {
        const key = String(tag?.key || '').toLowerCase();
        const filename = String(tag?.value || tag?.filename || '').trim();
        if (!key || !filename) {
            return;
        }

        if (key === 'sfx' || key === 'sound' || key === 'audio') {
            this.playSfx(filename, this.parseSfxTagOptions(tag.param || tag.options || ''));
            return;
        }

        if (key === 'music') {
            const options = this.parseMusicTagOptions(tag.param || tag.options || '');
            this.playMusic(filename, options.mode, { loop: options.loop });
        }
    }

    parseMusicTagOptions(optionText) {
        const options = {
            mode: 'crossfade',
            loop: true
        };

        String(optionText || '')
            .split(/[,\s]+/)
            .map(token => token.trim().toLowerCase())
            .filter(Boolean)
            .forEach(token => {
                const [key, value] = token.split('=');
                if (['queue', 'crossfade', 'cut'].includes(token)) {
                    options.mode = token;
                } else if (['loop', 'looped', 'repeat'].includes(token)) {
                    options.loop = true;
                } else if (['once', 'single', 'no-loop', 'noloop'].includes(token)) {
                    options.loop = false;
                } else if (key === 'loop') {
                    options.loop = !['false', '0', 'no', 'once'].includes(value);
                } else if (key === 'mode' && ['queue', 'crossfade', 'cut'].includes(value)) {
                    options.mode = value;
                }
            });

        return options;
    }

    parseSfxTagOptions(optionText) {
        const options = {
            maxDurationSeconds: 0,
            endMode: 'stop',
            fadeDurationSeconds: 2
        };

        String(optionText || '')
            .split(/[,\s]+/)
            .map(token => token.trim().toLowerCase())
            .filter(Boolean)
            .forEach(token => {
                const [key, value] = token.split('=');
                if (['fade', 'fadeout', 'fade-out'].includes(token)) {
                    options.endMode = 'fade';
                } else if (['stop', 'cut', 'halt'].includes(token)) {
                    options.endMode = 'stop';
                } else if (['max', 'duration', 'max-duration', 'limit', 'stop-after', 'fade-after'].includes(key)) {
                    const seconds = Number(value);
                    options.maxDurationSeconds = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
                    if (key === 'fade-after') options.endMode = 'fade';
                } else if (/^\d+(\.\d+)?s?$/.test(token)) {
                    options.maxDurationSeconds = Number(token.replace(/s$/, ''));
                } else if (key === 'mode' && ['fade', 'fadeout', 'fade-out', 'stop', 'cut'].includes(value)) {
                    options.endMode = value.startsWith('fade') ? 'fade' : 'stop';
                } else if (['fade-duration', 'fade-time', 'fade'].includes(key)) {
                    const seconds = Number(value);
                    if (Number.isFinite(seconds)) {
                        options.fadeDurationSeconds = Math.max(0.1, seconds);
                        options.endMode = 'fade';
                    }
                }
            });

        return options;
    }

    async playSfx(filename, options = {}) {
        try {
            const template = await this.preloadSfx(filename);
            const audio = template.cloneNode(true);
            this.setMediaVolume(audio, this.getSfxVolume());
            this.currentAudio = audio;
            this.currentAudioRole = 'sfx';
            const maxDuration = Math.max(0, Number(options.maxDurationSeconds || options.maxDuration || 0)) * 1000;
            const endMode = String(options.endMode || options.mode || 'stop').toLowerCase().startsWith('fade') ? 'fade' : 'stop';
            const fadeDuration = Math.max(100, Number(options.fadeDurationSeconds || options.fadeDuration || 2) * 1000);
            let maxTimer = null;
            audio.addEventListener('ended', () => {
                if (maxTimer) clearTimeout(maxTimer);
                if (this.currentAudio === audio) {
                    this.currentAudio = null;
                    this.currentAudioRole = null;
                }
            }, { once: true });
            await audio.play();
            if (maxDuration > 0) {
                const timeoutDuration = endMode === 'fade'
                    ? Math.max(0, maxDuration - fadeDuration)
                    : maxDuration;
                maxTimer = setTimeout(() => {
                    if (audio.paused || audio.ended) return;
                    if (endMode === 'fade') {
                        console.log(`AudioManager: Fading sound effect ${filename} over ${fadeDuration}ms`);
                        this.fadeOutAudio(audio, fadeDuration);
                    } else {
                        audio.pause();
                        audio.currentTime = 0;
                        if (this.currentAudio === audio) this.currentAudio = null;
                        if (this.currentAudio === null) this.currentAudioRole = null;
                    }
                }, timeoutDuration);
            }
            console.log(`AudioManager: Playing sound effect ${filename}`);
            return audio;
        } catch (error) {
            console.error('AudioManager: Failed to play sound effect:', error);
            return null;
        }
    }

    fadeOutAudio(audio, duration = 1000) {
        if (!audio) return Promise.resolve(false);
        const startVolume = this.clampVolume(audio.volume);
        const startedAt = performance.now();
        return new Promise(resolve => {
            const step = () => {
                const progress = Math.min(1, (performance.now() - startedAt) / duration);
                this.setMediaVolume(audio, startVolume * (1 - progress));
                if (progress < 1 && !audio.paused && !audio.ended) {
                    requestAnimationFrame(step);
                    return;
                }
                audio.pause();
                audio.currentTime = 0;
                if (this.currentAudio === audio) this.currentAudio = null;
                if (this.currentAudio === null) this.currentAudioRole = null;
                resolve(true);
            };
            requestAnimationFrame(step);
        });
    }

    async playMusic(filename, mode = 'crossfade', options = {}) {
        const url = this.getAssetUrl('music', filename);
        const shouldLoop = options.loop !== false;
        const startAt = Math.max(0, Number(options.startAt ?? options.currentTime ?? 0) || 0);
        const fadeInSeconds = Math.max(0, Number(options.fadeInSeconds ?? options.fadeIn ?? 0) || 0);

        if (mode === 'queue' && this.currentMusic && !this.currentMusic.paused) {
            this.queuedMusic = { filename, mode: 'cut', options: { loop: shouldLoop } };
            this.currentMusic.addEventListener('ended', () => {
                const queued = this.queuedMusic;
                this.queuedMusic = null;
                if (queued) this.playMusic(queued.filename, queued.mode, queued.options);
            }, { once: true });
            console.log(`AudioManager: Queued music ${filename}`);
            return this.currentMusic;
        }

        const next = new Audio(url);
        next.loop = shouldLoop;
        this.setMediaVolume(next, (mode === 'crossfade' && this.currentMusic) || fadeInSeconds > 0 ? 0 : this.getMusicVolume());
        if (startAt > 0) {
            try {
                next.currentTime = startAt;
            } catch {
                next.addEventListener('loadedmetadata', () => {
                    next.currentTime = Math.min(startAt, Number.isFinite(next.duration) ? next.duration : startAt);
                }, { once: true });
            }
        }
        next.addEventListener('ended', () => {
            if (this.currentMusic === next) {
                this.currentMusic = null;
                this.currentMusicState = null;
            }
        });
        const nextState = {
            filename,
            url,
            loop: shouldLoop,
            mode,
            startedAt: Date.now()
        };

        if (mode === 'cut' || !this.currentMusic) {
            this.stopCurrentMusic();
            this.currentMusic = next;
            this.currentMusicState = nextState;
            await this.startMusicAudio(next, filename);
            if (fadeInSeconds > 0) {
                this.fadeAudioTo(next, this.getMusicVolume(), fadeInSeconds * 1000);
            }
            return next;
        }

        const previous = this.currentMusic;
        this.currentMusic = next;
        this.currentMusicState = nextState;
        await this.startMusicAudio(next, filename);
        this.crossfade(previous, next, 1500);
        console.log(`AudioManager: Crossfading music to ${filename}`);
        return next;
    }

    async startMusicAudio(audio, filename) {
        try {
            await audio.play();
            console.log(`AudioManager: Playing music ${filename}`);
        } catch (error) {
            this.pendingMusicPlayback = { audio, filename };
            console.warn('AudioManager: Music playback is waiting for user interaction:', error);
        }
    }

    async unlockPendingAudio() {
        if (this.audioContext && this.audioContext.state === 'suspended') {
            try {
                await this.audioContext.resume();
            } catch (error) {
                console.warn('AudioManager: Failed to resume audio context:', error);
            }
        }

        if (!this.pendingMusicPlayback) {
            return;
        }

        const pending = this.pendingMusicPlayback;
        this.pendingMusicPlayback = null;
        this.setMediaVolume(pending.audio, this.getMusicVolume());

        try {
            await pending.audio.play();
            console.log(`AudioManager: Resumed pending music ${pending.filename}`);
        } catch (error) {
            this.pendingMusicPlayback = pending;
            console.warn('AudioManager: Pending music still blocked:', error);
        }
    }

    stopCurrentMusic() {
        if (!this.currentMusic) {
            return;
        }

        this.currentMusic.pause();
        this.currentMusic.currentTime = 0;
        this.currentMusic = null;
        this.currentMusicState = null;
    }

    getMusicState() {
        if (!this.currentMusic || this.currentMusic.paused || this.currentMusic.ended || !this.currentMusicState?.filename) {
            return null;
        }

        return {
            filename: this.currentMusicState.filename,
            currentTime: Math.max(0, Number(this.currentMusic.currentTime || 0)),
            loop: Boolean(this.currentMusic.loop),
            mode: this.currentMusicState.mode || 'cut',
            volume: this.currentMusic.volume,
            duckingFactor: this.musicDuckingFactor
        };
    }

    async restoreMusicState(state = null) {
        if (!state?.filename) return null;
        return this.playMusic(state.filename, 'cut', {
            loop: state.loop !== false,
            startAt: Number(state.currentTime || 0),
            fadeInSeconds: 1.5
        });
    }

    fadeAudioTo(audio, targetVolume, duration = 1000) {
        if (!audio) return Promise.resolve(false);
        const startVolume = this.clampVolume(audio.volume);
        const target = this.clampVolume(targetVolume);
        const startedAt = performance.now();
        return new Promise(resolve => {
            const step = (now) => {
                const progress = duration <= 0 ? 1 : Math.min(1, (now - startedAt) / duration);
                this.setMediaVolume(audio, startVolume + ((target - startVolume) * progress));
                if (progress < 1 && !audio.paused && !audio.ended) {
                    requestAnimationFrame(step);
                    return;
                }
                resolve(true);
            };
            requestAnimationFrame(step);
        });
    }

    crossfade(previous, next, duration = 1500) {
        const start = performance.now();
        const previousStart = previous ? this.clampVolume(previous.volume) : 0;
        const target = this.clampVolume(this.getMusicVolume());

        const tick = () => {
            const progress = Math.min(1, (performance.now() - start) / duration);
            if (previous) this.setMediaVolume(previous, previousStart * (1 - progress));
            this.setMediaVolume(next, target * progress);

            if (progress < 1) {
                requestAnimationFrame(tick);
                return;
            }

            if (previous) {
                previous.pause();
                previous.currentTime = 0;
            }
            this.setMediaVolume(next, this.getMusicVolume());
        };

        tick();
    }

    /**
     * Check if a sound is currently playing
     * @param {string} id - The identifier for the sound
     * @returns {boolean} Whether the sound is playing
     */
    isPlaying(id) {
        const audio = this.sounds.get(id);
        return audio ? !audio.paused : false;
    }

    /**
     * Fade out the current audio
     * @param {number} duration - The duration of the fade in milliseconds
     * @returns {Promise} A promise that resolves when the fade is complete
     */
    fadeOutCurrentAudio(duration = 1000) {
        return new Promise((resolve) => {
            if (!this.currentAudio || this.currentAudio.paused) {
                resolve();
                return;
            }

            const audio = this.currentAudio;
            const initialVolume = this.clampVolume(audio.volume);
            const volumeStep = initialVolume / (duration / 50);
            let currentVolume = initialVolume;

            const fadeInterval = setInterval(() => {
                currentVolume -= volumeStep;
                if (currentVolume <= 0) {
                    clearInterval(fadeInterval);
                    audio.pause();
                    audio.currentTime = 0;
                    this.setMediaVolume(audio, initialVolume); // Reset volume for future use
                    if (this.currentAudio === audio) {
                        this.currentAudio = null;
                        this.currentAudioRole = null;
                    }
                    resolve();
                } else {
                    this.setMediaVolume(audio, currentVolume);
                }
            }, 50);
        });
    }

    /**
     * Play speech audio from a Blob
     * @param {Blob} audioBlob - The audio Blob to play
     * @param {Object} options - Playback options
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async playSpeech(audioBlob, options = {}) {
        if (!audioBlob || !(audioBlob instanceof Blob)) {
            console.error('AudioManager: Invalid speech audio blob provided');
            return false;
        }

        try {
            // Create object URL from Blob
            const audioUrl = URL.createObjectURL(audioBlob);
            
            // Stop any current non-looping audio
            if (this.currentAudio) {
                this.currentAudio.pause();
                this.currentAudio.currentTime = 0;
                this.currentAudioRole = null;
            }
            
            // Create new audio element
            const audio = new Audio(audioUrl);
            
            // Get speech volume from options or preferences
            const persistenceManager = this.getModule('persistence-manager');
            let speechVolume = 1.0;
            
            if (persistenceManager) {
                // Get speech volume from preferences (0.0 to 1.0)
                speechVolume = persistenceManager.getPreference('tts', 'volume') || 1.0;
            }
            
            // Override with options if provided
            if (options.volume !== undefined) {
                speechVolume = options.volume;
            }
            
            // Apply master volume and speech volume
            this.setMediaVolume(audio, speechVolume * this.getTtsVolume());
            
            // Set up cleanup
            audio.onended = () => {
                URL.revokeObjectURL(audioUrl);
                if (this.currentAudio === audio) {
                    this.currentAudio = null;
                    this.currentAudioRole = null;
                }
                if (options.onComplete && typeof options.onComplete === 'function') {
                    options.onComplete();
                }
                
                // Dispatch event for speech completion
                window.dispatchEvent(new CustomEvent('tts:speak-completed'));
            };
            
            // Handle errors
            audio.onerror = (error) => {
                console.error('AudioManager: Error playing speech audio:', error);
                URL.revokeObjectURL(audioUrl);
                if (this.currentAudio === audio) {
                    this.currentAudio = null;
                    this.currentAudioRole = null;
                }
                if (options.onError && typeof options.onError === 'function') {
                    options.onError(error);
                }
                
                // Dispatch event for speech error
                window.dispatchEvent(new CustomEvent('tts:speak-error', { 
                    detail: { error: error } 
                }));
            };
            
            // Store as current audio
            this.currentAudio = audio;
            this.currentAudioRole = 'tts';
            
            // Play the audio
            await audio.play();
            return true;
        } catch (error) {
            console.error('AudioManager: Error setting up speech audio:', error);
            
            // Dispatch event for speech error
            window.dispatchEvent(new CustomEvent('tts:speak-error', { 
                detail: { error: error } 
            }));
            
            return false;
        }
    }
}

// Create the singleton instance
const AudioManager = new AudioManagerModule();

// Export the module
export { AudioManager };
