'use strict';

var React = require('react');

/* ────────────────────────────────────────────────────────────────
   Audio Bridge — Integration between @nice2dev/ui-audio and
   the game engine audio system
   
   Bridges the standalone ui-audio package's components and hooks
   into the game engine's AudioManager/AudioAdvanced system with:
   - Spatial audio positioning (2D/3D)
   - Game-state aware audio (pause/resume on menu, focus loss)
   - Audio pools for frequent SFX
   - Music crossfade tied to scene transitions
   - Adaptive audio based on gameplay events
──────────────────────────────────────────────────────────────── */
const DEFAULT_SPATIAL = {
    model: '3d-hrtf',
    maxDistance: 100,
    refDistance: 1,
    rolloffFactor: 1,
    coneInnerAngle: 360,
    coneOuterAngle: 360,
    coneOuterGain: 0,
};
/* ══════════════════════════════════════════════════════════════
   AUDIO BUS MIXER
   ══════════════════════════════════════════════════════════════ */
const DEFAULT_BUS_VOLUMES = {
    master: 1,
    music: 0.7,
    sfx: 1,
    voice: 1,
    ambient: 0.5,
    ui: 0.8,
};
class AudioBusMixer {
    constructor(eventBus) {
        this.buses = new Map();
        this.eventBus = eventBus;
        for (const [type, volume] of Object.entries(DEFAULT_BUS_VOLUMES)) {
            this.buses.set(type, {
                type: type,
                volume,
                muted: false,
                effects: [],
            });
        }
    }
    getBus(type) {
        return this.buses.get(type);
    }
    setVolume(type, volume) {
        const bus = this.buses.get(type);
        if (bus) {
            bus.volume = Math.max(0, Math.min(1, volume));
            this.eventBus.emit('audio:bus-volume', { bus: type, volume: bus.volume });
        }
    }
    setMuted(type, muted) {
        const bus = this.buses.get(type);
        if (bus) {
            bus.muted = muted;
            this.eventBus.emit('audio:bus-mute', { bus: type, muted });
        }
    }
    /** Get effective volume considering master and mute */
    getEffectiveVolume(type) {
        const bus = this.buses.get(type);
        if (!bus || bus.muted)
            return 0;
        const master = this.buses.get('master');
        if (!master || master.muted)
            return 0;
        return bus.volume * master.volume;
    }
    addEffect(type, effect) {
        const bus = this.buses.get(type);
        if (bus) {
            bus.effects.push(effect);
        }
    }
    removeEffect(type, effectType) {
        const bus = this.buses.get(type);
        if (bus) {
            bus.effects = bus.effects.filter(e => e.type !== effectType);
        }
    }
    getAllBuses() {
        return this.buses;
    }
    snapshot() {
        const result = {};
        for (const [type, bus] of this.buses) {
            result[type] = bus.muted ? 0 : bus.volume;
        }
        return result;
    }
}
class SFXPool {
    constructor(config) {
        this.sounds = [];
        this.nextId = 0;
        this.lastPlayedAt = 0;
        this.config = config;
        for (let i = 0; i < config.maxInstances; i++) {
            this.sounds.push({ id: i, playing: false, volume: 1, startedAt: 0 });
        }
    }
    play(volume = 1, position) {
        const now = Date.now();
        if (this.config.cooldownMs && now - this.lastPlayedAt < this.config.cooldownMs) {
            return null;
        }
        // Find a free slot
        let slot = this.sounds.find(s => !s.playing);
        if (!slot) {
            // All busy — apply strategy
            switch (this.config.strategy) {
                case 'round-robin':
                    slot = this.sounds[this.nextId % this.sounds.length];
                    break;
                case 'steal-oldest':
                    slot = this.sounds.reduce((a, b) => a.startedAt < b.startedAt ? a : b);
                    break;
                case 'steal-quietest':
                    slot = this.sounds.reduce((a, b) => a.volume < b.volume ? a : b);
                    break;
                case 'reject':
                    return null;
            }
        }
        if (!slot)
            return null;
        slot.playing = true;
        slot.volume = volume;
        slot.startedAt = now;
        this.lastPlayedAt = now;
        this.nextId++;
        return slot.id;
    }
    stop(id) {
        const s = this.sounds.find(ss => ss.id === id);
        if (s)
            s.playing = false;
    }
    stopAll() {
        for (const s of this.sounds)
            s.playing = false;
    }
    getActiveCount() {
        return this.sounds.filter(s => s.playing).length;
    }
    getConfig() {
        return this.config;
    }
}
/* ══════════════════════════════════════════════════════════════
   MUSIC MANAGER
   ══════════════════════════════════════════════════════════════ */
class MusicManager {
    constructor(eventBus) {
        this.tracks = new Map();
        this.currentTrackId = null;
        this.adaptiveRules = [];
        this.intensity = 0.5;
        this.eventBus = eventBus;
    }
    registerTrack(track) {
        this.tracks.set(track.id, track);
    }
    unregisterTrack(id) {
        this.tracks.delete(id);
    }
    getTrack(id) {
        return this.tracks.get(id);
    }
    getCurrentTrackId() {
        return this.currentTrackId;
    }
    /** Start playing or crossfade to a new track */
    play(trackId, crossfadeDuration = 1000) {
        const track = this.tracks.get(trackId);
        if (!track)
            return null;
        const config = {
            fromTrackId: this.currentTrackId,
            toTrackId: trackId,
            duration: crossfadeDuration,
            curve: 'equal-power',
        };
        this.currentTrackId = trackId;
        this.eventBus.emit('audio:music-crossfade', config);
        return config;
    }
    stop(fadeOutMs = 500) {
        if (this.currentTrackId) {
            this.eventBus.emit('audio:music-stop', { trackId: this.currentTrackId, fadeOutMs });
            this.currentTrackId = null;
        }
    }
    /** Adaptive audio rules — respond to game events */
    addRule(rule) {
        this.adaptiveRules.push(rule);
        this.adaptiveRules.sort((a, b) => b.priority - a.priority);
    }
    removeRule(id) {
        this.adaptiveRules = this.adaptiveRules.filter(r => r.id !== id);
    }
    /** Process a game event and apply matching adaptive rules */
    processGameEvent(eventName) {
        var _a;
        const rule = this.adaptiveRules.find(r => r.triggerEvent === eventName);
        if (!rule)
            return null;
        switch (rule.action) {
            case 'switch-track':
                if (rule.targetTrackId)
                    this.play(rule.targetTrackId, (_a = rule.fadeDuration) !== null && _a !== void 0 ? _a : 1000);
                break;
            case 'set-intensity':
                if (rule.intensity !== undefined)
                    this.intensity = rule.intensity;
                this.eventBus.emit('audio:music-intensity', { intensity: this.intensity });
                break;
            case 'add-layer':
            case 'remove-layer':
                this.eventBus.emit(`audio:music-${rule.action}`, { layerId: rule.layerId });
                break;
            case 'play-stinger':
                if (rule.stingerSrc) {
                    this.eventBus.emit('audio:play-stinger', { src: rule.stingerSrc });
                }
                break;
        }
        return rule;
    }
    getIntensity() {
        return this.intensity;
    }
    setIntensity(value) {
        this.intensity = Math.max(0, Math.min(1, value));
        this.eventBus.emit('audio:music-intensity', { intensity: this.intensity });
    }
    getRules() {
        return this.adaptiveRules;
    }
    getAllTracks() {
        return Array.from(this.tracks.values());
    }
    /** Find tracks matching tags */
    findByTags(tags) {
        return this.getAllTracks().filter(t => tags.some(tag => t.tags.includes(tag)));
    }
}
/* ══════════════════════════════════════════════════════════════
   GAME AUDIO BRIDGE
   ══════════════════════════════════════════════════════════════ */
/** Main bridge class connecting @nice2dev/ui-audio features to the game engine */
class GameAudioBridge {
    constructor(eventBus) {
        this.pools = new Map();
        this.paused = false;
        this.snapshot = null;
        this.eventBus = eventBus;
        this.mixer = new AudioBusMixer(eventBus);
        this.music = new MusicManager(eventBus);
        this.setupGameStateHandlers();
    }
    setupGameStateHandlers() {
        // Pause audio when game pauses
        this.eventBus.on('game:pause', () => this.pauseAll());
        this.eventBus.on('game:resume', () => this.resumeAll());
        // Handle scene transitions
        this.eventBus.on('scene:transition', (data) => {
            if (data.musicTrackId) {
                this.music.play(data.musicTrackId, 2000);
            }
        });
        // Forward game events to adaptive audio
        this.eventBus.on('game:event', (data) => {
            this.music.processGameEvent(data.name);
        });
    }
    /** Register an SFX pool for frequent sounds */
    registerPool(config) {
        this.pools.set(config.id, new SFXPool(config));
    }
    /** Play a pooled SFX */
    playSFX(poolId, volume = 1, position) {
        const pool = this.pools.get(poolId);
        return pool ? pool.play(volume, position) : null;
    }
    /** Stop a pooled SFX instance */
    stopSFX(poolId, instanceId) {
        var _a;
        (_a = this.pools.get(poolId)) === null || _a === void 0 ? void 0 : _a.stop(instanceId);
    }
    /** Pause all audio (game menu, alt-tab, etc.) */
    pauseAll() {
        if (this.paused)
            return;
        this.paused = true;
        this.snapshot = this.createSnapshot();
        for (const pool of this.pools.values())
            pool.stopAll();
        this.eventBus.emit('audio:pause-all', {});
    }
    /** Resume all audio */
    resumeAll() {
        if (!this.paused)
            return;
        this.paused = false;
        this.eventBus.emit('audio:resume-all', { snapshot: this.snapshot });
        this.snapshot = null;
    }
    isPaused() {
        return this.paused;
    }
    /** Create a snapshot of current audio state for save/restore */
    createSnapshot() {
        const activeSounds = [];
        for (const [poolId, pool] of this.pools) {
            if (pool.getActiveCount() > 0) {
                activeSounds.push({
                    id: poolId,
                    src: pool.getConfig().src,
                    bus: pool.getConfig().bus,
                    volume: 1,
                    playing: true,
                });
            }
        }
        return {
            timestamp: Date.now(),
            busVolumes: this.mixer.snapshot(),
            activeSounds,
            currentMusicTrack: this.music.getCurrentTrackId(),
            musicPosition: 0,
        };
    }
    /** Restore audio from a snapshot */
    restoreSnapshot(snap) {
        for (const [bus, vol] of Object.entries(snap.busVolumes)) {
            this.mixer.setVolume(bus, vol);
        }
        if (snap.currentMusicTrack) {
            this.music.play(snap.currentMusicTrack, 0);
        }
    }
    /** Process a game audio event */
    processEvent(event) {
        var _a;
        switch (event.type) {
            case 'set-bus-volume':
                if (event.bus !== undefined && event.volume !== undefined) {
                    this.mixer.setVolume(event.bus, event.volume);
                }
                break;
            case 'crossfade':
                if (event.sourceId) {
                    this.music.play(event.sourceId, (_a = event.fadeDuration) !== null && _a !== void 0 ? _a : 1000);
                }
                break;
            default:
                this.eventBus.emit(`audio:${event.type}`, event);
        }
    }
    getPool(id) {
        return this.pools.get(id);
    }
    getAllPools() {
        return new Map(this.pools);
    }
    destroy() {
        for (const pool of this.pools.values())
            pool.stopAll();
        this.pools.clear();
        this.music.stop(0);
    }
}
/* ══════════════════════════════════════════════════════════════
   REACT HOOK & COMPONENT
   ══════════════════════════════════════════════════════════════ */
function useGameAudio(eventBus) {
    const bridgeRef = React.useRef(null);
    const bridge = React.useMemo(() => {
        if (!bridgeRef.current) {
            bridgeRef.current = new GameAudioBridge(eventBus);
        }
        return bridgeRef.current;
    }, [eventBus]);
    React.useEffect(() => {
        return () => {
            bridge.destroy();
            bridgeRef.current = null;
        };
    }, [bridge]);
    const playSFX = React.useCallback((poolId, volume, position) => bridge.playSFX(poolId, volume, position), [bridge]);
    const playMusic = React.useCallback((trackId, crossfadeDuration) => bridge.music.play(trackId, crossfadeDuration), [bridge]);
    const stopMusic = React.useCallback((fadeOutMs) => bridge.music.stop(fadeOutMs), [bridge]);
    const setVolume = React.useCallback((bus, volume) => bridge.mixer.setVolume(bus, volume), [bridge]);
    return {
        bridge,
        playSFX,
        playMusic,
        stopMusic,
        setVolume,
        pauseAll: () => bridge.pauseAll(),
        resumeAll: () => bridge.resumeAll(),
        registerPool: (config) => bridge.registerPool(config),
        registerTrack: (track) => bridge.music.registerTrack(track),
    };
}
const GameAudioSettings = ({ bridge, className, style, }) => {
    const [volumes, setVolumes] = React.useState(() => bridge.mixer.snapshot());
    const handleVolumeChange = React.useCallback((bus, value) => {
        bridge.mixer.setVolume(bus, value);
        setVolumes(prev => ({ ...prev, [bus]: value }));
    }, [bridge]);
    const busOrder = ['master', 'music', 'sfx', 'voice', 'ambient', 'ui'];
    return React.createElement('div', {
        className: `nice-game-audio-settings ${className !== null && className !== void 0 ? className : ''}`.trim(),
        style: { display: 'flex', flexDirection: 'column', gap: 8, padding: 16, ...style },
    }, busOrder.map(bus => {
        var _a, _b;
        return React.createElement('div', {
            key: bus,
            style: { display: 'flex', alignItems: 'center', gap: 8 },
        }, React.createElement('label', {
            style: { width: 80, textTransform: 'capitalize', fontSize: 14 },
        }, bus), React.createElement('input', {
            type: 'range',
            min: 0,
            max: 1,
            step: 0.01,
            value: (_a = volumes[bus]) !== null && _a !== void 0 ? _a : 1,
            onChange: (e) => handleVolumeChange(bus, parseFloat(e.target.value)),
            style: { flex: 1 },
        }), React.createElement('span', {
            style: { width: 40, textAlign: 'right', fontSize: 12, fontVariantNumeric: 'tabular-nums' },
        }, `${Math.round(((_b = volumes[bus]) !== null && _b !== void 0 ? _b : 1) * 100)}%`));
    }));
};
GameAudioSettings.displayName = 'GameAudioSettings';

exports.AudioBusMixer = AudioBusMixer;
exports.DEFAULT_SPATIAL = DEFAULT_SPATIAL;
exports.GameAudioBridge = GameAudioBridge;
exports.GameAudioSettings = GameAudioSettings;
exports.MusicManager = MusicManager;
exports.SFXPool = SFXPool;
exports.useGameAudio = useGameAudio;
//# sourceMappingURL=AudioBridge.js.map
