'use strict';

var EventBus = require('../core/EventBus.js');

/* ────────────────────────────────────────────────────────────────
   AudioManager — Web Audio API wrapper for games
──────────────────────────────────────────────────────────────── */
/* ── AudioManager ─────────────────────────────────────────────── */
class AudioManager {
    constructor() {
        this.events = new EventBus.EventBus();
        this.ctx = null;
        this.masterGain = null;
        this.channelGains = new Map();
        this.bufferCache = new Map();
        this.activeMusic = null;
        this.activeSounds = new Set();
        this.pool = [];
        this._listenerPos = { x: 0, y: 0 };
        this._muted = false;
        // Volume settings
        this.volumes = {
            master: 1,
            music: 0.7,
            sfx: 1,
            voice: 1,
            ui: 0.8,
        };
    }
    /* ── Lifecycle ──────────────────────────────────────────────── */
    /** Initialize audio context (must be called after user gesture). */
    init() {
        if (this.ctx)
            return;
        this.ctx = new AudioContext();
        this.masterGain = this.ctx.createGain();
        this.masterGain.connect(this.ctx.destination);
        // Create channel gains
        const channels = ['music', 'sfx', 'voice', 'ui'];
        for (const ch of channels) {
            const gain = this.ctx.createGain();
            gain.gain.value = this.volumes[ch];
            gain.connect(this.masterGain);
            this.channelGains.set(ch, gain);
        }
        this.masterGain.gain.value = this.volumes.master;
    }
    /** Ensure context is running (after autoplay policy). */
    async resume() {
        var _a;
        if (((_a = this.ctx) === null || _a === void 0 ? void 0 : _a.state) === 'suspended') {
            await this.ctx.resume();
        }
    }
    dispose() {
        var _a;
        this.stopAll();
        this.bufferCache.clear();
        (_a = this.ctx) === null || _a === void 0 ? void 0 : _a.close();
        this.ctx = null;
    }
    /* ── Loading ────────────────────────────────────────────────── */
    /** Load an audio file and cache its buffer. */
    async load(id, url) {
        if (this.bufferCache.has(id))
            return;
        if (!this.ctx)
            this.init();
        const response = await fetch(url);
        const arrayBuf = await response.arrayBuffer();
        const audioBuffer = await this.ctx.decodeAudioData(arrayBuf);
        this.bufferCache.set(id, audioBuffer);
    }
    /** Check if a sound is loaded. */
    isLoaded(id) {
        return this.bufferCache.has(id);
    }
    /* ── Play SFX ───────────────────────────────────────────────── */
    /** Play a sound effect. Returns an ID to control it. */
    play(id, options = {}) {
        var _a, _b, _c, _d;
        if (!this.ctx || !this.masterGain) {
            if (typeof console !== 'undefined')
                console.warn(`[AudioManager] play("${id}"): AudioContext not initialized. Call init() first.`);
            return -1;
        }
        const buffer = this.bufferCache.get(id);
        if (!buffer) {
            if (typeof console !== 'undefined')
                console.warn(`[AudioManager] play("${id}"): sound not loaded. Call loadSound() first.`);
            return -1;
        }
        const channel = (_a = options.channel) !== null && _a !== void 0 ? _a : 'sfx';
        const channelGain = this.channelGains.get(channel);
        if (!channelGain)
            return -1;
        const source = this.ctx.createBufferSource();
        source.buffer = buffer;
        source.loop = (_b = options.loop) !== null && _b !== void 0 ? _b : false;
        source.playbackRate.value = (_c = options.pitch) !== null && _c !== void 0 ? _c : 1;
        // Volume
        const volNode = this.ctx.createGain();
        volNode.gain.value = (_d = options.volume) !== null && _d !== void 0 ? _d : 1;
        // Pan
        let panNode = null;
        if (options.pan !== undefined) {
            panNode = this.ctx.createStereoPanner();
            panNode.pan.value = Math.max(-1, Math.min(1, options.pan));
        }
        // Chain: source → vol → [pan] → channel → master
        source.connect(volNode);
        if (panNode) {
            volNode.connect(panNode);
            panNode.connect(channelGain);
        }
        else {
            volNode.connect(channelGain);
        }
        const soundId = nextSoundId++;
        const active = {
            id: soundId,
            source,
            gain: volNode,
            channel,
        };
        source.onended = () => {
            this.activeSounds.delete(active);
        };
        source.start();
        this.activeSounds.add(active);
        this.events.emit('sound:play', { id, soundId });
        return soundId;
    }
    /** Stop a specific playing sound. */
    stopSound(soundId) {
        for (const snd of this.activeSounds) {
            if (snd.id === soundId) {
                snd.source.stop();
                this.activeSounds.delete(snd);
                break;
            }
        }
    }
    /* ── Music ──────────────────────────────────────────────────── */
    /** Play background music with optional crossfade. */
    playMusic(id, options = {}) {
        var _a, _b, _c;
        if (!this.ctx)
            return;
        const buffer = this.bufferCache.get(id);
        if (!buffer)
            return;
        const fadeIn = (_a = options.fadeIn) !== null && _a !== void 0 ? _a : 1;
        // Fade out current
        if (this.activeMusic) {
            this.fadeOutMusic(1);
        }
        const channelGain = this.channelGains.get('music');
        if (!channelGain)
            return;
        const source = this.ctx.createBufferSource();
        source.buffer = buffer;
        source.loop = (_b = options.loop) !== null && _b !== void 0 ? _b : true;
        const volNode = this.ctx.createGain();
        volNode.gain.value = 0;
        volNode.gain.linearRampToValueAtTime((_c = options.volume) !== null && _c !== void 0 ? _c : 1, this.ctx.currentTime + fadeIn);
        source.connect(volNode);
        volNode.connect(channelGain);
        source.start();
        this.activeMusic = { id, source, gain: volNode };
        this.events.emit('music:play', id);
    }
    /** Stop music with fade out. */
    stopMusic(fadeOut = 1) {
        this.fadeOutMusic(fadeOut);
    }
    fadeOutMusic(fadeOut) {
        if (!this.activeMusic || !this.ctx)
            return;
        const { source, gain } = this.activeMusic;
        gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + fadeOut);
        setTimeout(() => {
            try {
                source.stop();
            }
            catch ( /* already stopped */_a) { /* already stopped */ }
        }, fadeOut * 1000 + 50);
        this.activeMusic = null;
    }
    /* ── Volume Control ─────────────────────────────────────────── */
    setVolume(channel, volume) {
        volume = Math.max(0, Math.min(1, volume));
        this.volumes[channel] = volume;
        if (channel === 'master') {
            if (this.masterGain)
                this.masterGain.gain.value = volume;
        }
        else {
            const gain = this.channelGains.get(channel);
            if (gain)
                gain.gain.value = volume;
        }
    }
    getVolume(channel) {
        return this.volumes[channel];
    }
    get muted() {
        return this._muted;
    }
    set muted(value) {
        this._muted = value;
        if (this.masterGain) {
            this.masterGain.gain.value = value ? 0 : this.volumes.master;
        }
    }
    /* ── Spatial Audio ──────────────────────────────────────────── */
    setListenerPosition(pos) {
        this._listenerPos = pos;
    }
    /* ── Utility ────────────────────────────────────────────────── */
    /** Stop all sounds and music, properly disconnecting audio nodes. */
    stopAll() {
        for (const snd of this.activeSounds) {
            try {
                snd.source.stop();
            }
            catch ( /* already stopped */_a) { /* already stopped */ }
            try {
                snd.source.disconnect();
            }
            catch ( /* already disconnected */_b) { /* already disconnected */ }
            try {
                snd.gain.disconnect();
            }
            catch ( /* already disconnected */_c) { /* already disconnected */ }
        }
        this.activeSounds.clear();
        this.pool.length = 0;
        this.fadeOutMusic(0);
    }
    get activeSoundCount() {
        return this.activeSounds.size;
    }
}
let nextSoundId = 1;

exports.AudioManager = AudioManager;
//# sourceMappingURL=AudioManager.js.map
