/**
 * AudioManager — фоновый звук сцены через Web Audio API.
 *
 * Все звуки генерируются ПРОЦЕДУРНО — никаких mp3-файлов не нужно.
 *
 * Каналы:
 *   ambient — длительный фон (ветер/лес/вода/пещера) через шум + фильтры
 *   music   — короткие мелодии-петли через oscillator (минорный/мажорный лад)
 *
 * Воспроизводится только в Play-режиме.
 */

export const AMBIENT_PRESETS = [
    { id: 'none',   name: 'Без звука' },
    { id: 'forest', name: 'Лес' },
    { id: 'wind',   name: 'Ветер' },
    { id: 'water',  name: 'Вода' },
    { id: 'cave',   name: 'Пещера' },
];

export const MUSIC_PRESETS = [
    { id: 'none',    name: 'Без музыки' },
    { id: 'calm',    name: 'Спокойная' },
    { id: 'happy',   name: 'Весёлая' },
    { id: 'epic',    name: 'Эпическая' },
    { id: 'mystery', name: 'Загадочная' },
];

// Ноты в Hz (4-я октава для калькуляций)
const NOTES = {
    C: 261.63, 'C#': 277.18, D: 293.66, 'D#': 311.13,
    E: 329.63, F: 349.23, 'F#': 369.99, G: 392.00,
    'G#': 415.30, A: 440.00, 'A#': 466.16, B: 493.88,
};

function n(note, octave = 4) {
    return NOTES[note] * Math.pow(2, octave - 4);
}

// Простые мелодии — массив [нота, октава, длительность в "тиках" по 0.25с]
const MELODIES = {
    calm: {
        bpm: 60,
        notes: [
            ['C',5,2],['E',5,2],['G',5,2],['E',5,2],
            ['F',5,2],['A',5,2],['G',5,4],
            ['D',5,2],['F',5,2],['A',5,2],['F',5,2],
            ['E',5,2],['G',5,2],['C',5,4],
        ],
    },
    happy: {
        bpm: 120,
        notes: [
            ['C',5,1],['E',5,1],['G',5,1],['C',6,1],
            ['G',5,1],['E',5,1],['C',5,2],
            ['F',5,1],['A',5,1],['C',6,1],['F',6,1],
            ['C',6,1],['A',5,1],['F',5,2],
        ],
    },
    epic: {
        bpm: 80,
        notes: [
            ['A',3,2],['E',4,2],['A',4,2],['C',5,2],
            ['B',4,2],['G',4,2],['E',4,4],
            ['F',4,2],['A',4,2],['C',5,2],['E',5,2],
            ['D',5,2],['B',4,2],['A',4,4],
        ],
    },
    mystery: {
        bpm: 70,
        notes: [
            ['A',4,3],['C',5,1],['E',5,2],['F',5,2],
            ['E',5,2],['D',5,2],['C',5,4],
            ['G',4,3],['B',4,1],['D',5,2],['F',5,2],
            ['E',5,2],['C',5,2],['A',4,4],
        ],
    },
};

export class AudioManager {
    constructor() {
        this.ambientId = 'none';
        this.ambientVolume = 0.3;
        this.musicId = 'none';
        this.musicVolume = 0.25;
        this._ctx = null;
        this._ambientNodes = [];
        this._musicTimer = null;
        this._musicNodes = [];
        this._playing = false;
    }

    setAmbient({ preset, volume } = {}) {
        if (preset !== undefined) this.ambientId = preset;
        if (volume !== undefined) this.ambientVolume = Math.max(0, Math.min(1, volume));
        if (this._playing) this._restartAmbient();
    }

    setMusic({ preset, volume } = {}) {
        if (preset !== undefined) this.musicId = preset;
        if (volume !== undefined) this.musicVolume = Math.max(0, Math.min(1, volume));
        if (this._playing) this._restartMusic();
    }

    start() {
        this._ensureContext();
        if (!this._ctx) return;
        // Браузеры требуют user-gesture перед воспроизведением.
        // Кнопка Play даёт его — resume() работает.
        if (this._ctx.state === 'suspended') {
            this._ctx.resume().catch(() => {});
        }
        this._playing = true;
        this._restartAmbient();
        this._restartMusic();
    }

    stop() {
        this._playing = false;
        this._stopAmbient();
        this._stopMusic();
    }

    _ensureContext() {
        if (this._ctx) return;
        try {
            const Ctx = window.AudioContext || window.webkitAudioContext;
            if (!Ctx) return;
            this._ctx = new Ctx();
        } catch (e) { /* ignore */ }
    }

    // ============ AMBIENT ============

    _restartAmbient() {
        this._stopAmbient();
        if (this.ambientId === 'none' || !this._ctx) return;
        const ctx = this._ctx;
        const dest = ctx.destination;

        // Главный gain — общая громкость канала
        const masterGain = ctx.createGain();
        masterGain.gain.value = this.ambientVolume * 0.5;
        masterGain.connect(dest);

        if (this.ambientId === 'wind') {
            // Розовый шум через белый шум + фильтр + медленная LFO для амплитуды
            const noise = this._createWhiteNoise();
            const filt = ctx.createBiquadFilter();
            filt.type = 'lowpass';
            filt.frequency.value = 800;
            filt.Q.value = 0.8;
            const lfo = ctx.createOscillator();
            const lfoGain = ctx.createGain();
            lfo.frequency.value = 0.15;
            lfoGain.gain.value = 0.3;
            lfo.connect(lfoGain);
            const winGain = ctx.createGain();
            winGain.gain.value = 0.5;
            lfoGain.connect(winGain.gain);
            noise.connect(filt);
            filt.connect(winGain);
            winGain.connect(masterGain);
            noise.start();
            lfo.start();
            this._ambientNodes.push(noise, lfo, filt, lfoGain, winGain);
        } else if (this.ambientId === 'forest') {
            // Шум листьев + случайные «птицы» (короткие чирикания)
            const noise = this._createWhiteNoise();
            const filt = ctx.createBiquadFilter();
            filt.type = 'highpass';
            filt.frequency.value = 1500;
            const ng = ctx.createGain();
            ng.gain.value = 0.15;
            noise.connect(filt);
            filt.connect(ng);
            ng.connect(masterGain);
            noise.start();
            this._ambientNodes.push(noise, filt, ng);
            // Птицы каждые 3-7 секунд
            this._scheduleBirds(masterGain);
        } else if (this.ambientId === 'water') {
            // Журчание — фильтрованный шум с медленным сдвигом частоты
            const noise = this._createWhiteNoise();
            const filt = ctx.createBiquadFilter();
            filt.type = 'bandpass';
            filt.frequency.value = 1200;
            filt.Q.value = 1.2;
            const lfo = ctx.createOscillator();
            const lfoGain = ctx.createGain();
            lfo.frequency.value = 0.5;
            lfoGain.gain.value = 400;
            lfo.connect(lfoGain);
            lfoGain.connect(filt.frequency);
            const wg = ctx.createGain();
            wg.gain.value = 0.6;
            noise.connect(filt);
            filt.connect(wg);
            wg.connect(masterGain);
            noise.start();
            lfo.start();
            this._ambientNodes.push(noise, lfo, filt, lfoGain, wg);
        } else if (this.ambientId === 'cave') {
            // Низкий гул + редкие «капли»
            const osc1 = ctx.createOscillator();
            osc1.type = 'sine';
            osc1.frequency.value = 60;
            const osc2 = ctx.createOscillator();
            osc2.type = 'sine';
            osc2.frequency.value = 90;
            const g = ctx.createGain();
            g.gain.value = 0.4;
            osc1.connect(g);
            osc2.connect(g);
            g.connect(masterGain);
            osc1.start();
            osc2.start();
            this._ambientNodes.push(osc1, osc2, g);
            this._scheduleDrips(masterGain);
        }
        this._ambientMaster = masterGain;
    }

    _stopAmbient() {
        for (const n of this._ambientNodes) {
            try { n.stop?.(); } catch (e) {}
            try { n.disconnect?.(); } catch (e) {}
        }
        this._ambientNodes = [];
        if (this._birdsTimer) { clearTimeout(this._birdsTimer); this._birdsTimer = null; }
        if (this._dripsTimer) { clearTimeout(this._dripsTimer); this._dripsTimer = null; }
        if (this._ambientMaster) { try { this._ambientMaster.disconnect(); } catch (e) {} this._ambientMaster = null; }
    }

    _createWhiteNoise() {
        const ctx = this._ctx;
        const bufSize = 2 * ctx.sampleRate;
        const buf = ctx.createBuffer(1, bufSize, ctx.sampleRate);
        const data = buf.getChannelData(0);
        for (let i = 0; i < bufSize; i++) data[i] = Math.random() * 2 - 1;
        const src = ctx.createBufferSource();
        src.buffer = buf;
        src.loop = true;
        return src;
    }

    _scheduleBirds(dest) {
        if (!this._playing) return;
        const ctx = this._ctx;
        const delay = 2 + Math.random() * 5;
        this._birdsTimer = setTimeout(() => {
            if (!this._playing) return;
            const t = ctx.currentTime;
            const baseFreq = 1500 + Math.random() * 1500;
            for (let i = 0; i < 3; i++) {
                const osc = ctx.createOscillator();
                osc.type = 'sine';
                osc.frequency.setValueAtTime(baseFreq * (1 + i * 0.1), t + i * 0.08);
                const g = ctx.createGain();
                g.gain.setValueAtTime(0, t + i * 0.08);
                g.gain.linearRampToValueAtTime(0.15, t + i * 0.08 + 0.02);
                g.gain.linearRampToValueAtTime(0, t + i * 0.08 + 0.1);
                osc.connect(g);
                g.connect(dest);
                osc.start(t + i * 0.08);
                osc.stop(t + i * 0.08 + 0.12);
            }
            this._scheduleBirds(dest);
        }, delay * 1000);
    }

    _scheduleDrips(dest) {
        if (!this._playing) return;
        const ctx = this._ctx;
        const delay = 1.5 + Math.random() * 4;
        this._dripsTimer = setTimeout(() => {
            if (!this._playing) return;
            const t = ctx.currentTime;
            const osc = ctx.createOscillator();
            osc.type = 'sine';
            osc.frequency.setValueAtTime(800 + Math.random() * 400, t);
            osc.frequency.exponentialRampToValueAtTime(200, t + 0.3);
            const g = ctx.createGain();
            g.gain.setValueAtTime(0, t);
            g.gain.linearRampToValueAtTime(0.2, t + 0.01);
            g.gain.exponentialRampToValueAtTime(0.001, t + 0.4);
            osc.connect(g);
            g.connect(dest);
            osc.start(t);
            osc.stop(t + 0.5);
            this._scheduleDrips(dest);
        }, delay * 1000);
    }

    // ============ MUSIC ============

    _restartMusic() {
        this._stopMusic();
        if (this.musicId === 'none' || !MELODIES[this.musicId] || !this._ctx) return;
        const ctx = this._ctx;
        const masterGain = ctx.createGain();
        masterGain.gain.value = this.musicVolume * 0.4;
        masterGain.connect(ctx.destination);
        this._musicMaster = masterGain;
        this._playMelodyLoop();
    }

    _stopMusic() {
        if (this._musicTimer) { clearTimeout(this._musicTimer); this._musicTimer = null; }
        for (const n of this._musicNodes) {
            try { n.stop?.(); } catch (e) {}
            try { n.disconnect?.(); } catch (e) {}
        }
        this._musicNodes = [];
        if (this._musicMaster) { try { this._musicMaster.disconnect(); } catch (e) {} this._musicMaster = null; }
    }

    _playMelodyLoop() {
        if (!this._playing) return;
        const melody = MELODIES[this.musicId];
        if (!melody) return;
        const ctx = this._ctx;
        const tickDur = 60 / melody.bpm / 4;     // 16-я нота
        let t = ctx.currentTime + 0.05;
        let totalTime = 0;
        for (const [note, octave, ticks] of melody.notes) {
            const dur = ticks * tickDur;
            const freq = n(note, octave);
            this._scheduleNote(freq, t, dur * 0.95);
            t += dur;
            totalTime += dur;
        }
        // Перезапускаем мелодию по окончании
        this._musicTimer = setTimeout(() => this._playMelodyLoop(), (totalTime + 0.5) * 1000);
    }

    _scheduleNote(freq, startTime, duration) {
        const ctx = this._ctx;
        const osc = ctx.createOscillator();
        osc.type = 'triangle';      // мягкий тон
        osc.frequency.value = freq;
        const g = ctx.createGain();
        // ADSR-огибающая
        g.gain.setValueAtTime(0, startTime);
        g.gain.linearRampToValueAtTime(0.4, startTime + 0.02);
        g.gain.linearRampToValueAtTime(0.2, startTime + 0.1);
        g.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
        osc.connect(g);
        g.connect(this._musicMaster);
        osc.start(startTime);
        osc.stop(startTime + duration + 0.05);
        this._musicNodes.push(osc, g);
        // Чистим старые ноды чтобы не накапливались
        if (this._musicNodes.length > 200) {
            this._musicNodes.splice(0, 100);
        }
    }

    // ============ STATE ============

    serialize() {
        return {
            ambientId: this.ambientId,
            ambientVolume: this.ambientVolume,
            musicId: this.musicId,
            musicVolume: this.musicVolume,
        };
    }

    load(data) {
        if (!data) return;
        if (typeof data.ambientId === 'string') this.ambientId = data.ambientId;
        if (typeof data.ambientVolume === 'number') this.ambientVolume = data.ambientVolume;
        if (typeof data.musicId === 'string') this.musicId = data.musicId;
        if (typeof data.musicVolume === 'number') this.musicVolume = data.musicVolume;
    }

    dispose() {
        this.stop();
        if (this._ctx) {
            try { this._ctx.close(); } catch (e) {}
            this._ctx = null;
        }
    }
}
