// Papeles Para Todos - Sistema de audio sintetizado (Web Audio API)
// SFX + banda sonora procedural estilo marcha burocrática (Papers, Please-esque).

const MUSIC_TEMPO_BPM = 72;
const MUSIC_BEATS_PER_BAR = 4;
const MUSIC_BARS = 8;
const MUSIC_LOOP_BEATS = MUSIC_BARS * MUSIC_BEATS_PER_BAR;

// Progresión en Re menor: Dm - Dm - Bb - Bb - Gm - Gm - A(V) - Dm
// bass/fifth = registro bajo (~oct 2-3), chord = tríada en oct 4
const MUSIC_PROGRESSION = [
    { bass: 73.42, fifth: 110.00, chord: [293.66, 349.23, 440.00] }, // Dm: D F A
    { bass: 73.42, fifth: 110.00, chord: [293.66, 349.23, 440.00] },
    { bass: 58.27, fifth: 87.31,  chord: [233.08, 293.66, 349.23] }, // Bb: Bb D F
    { bass: 58.27, fifth: 87.31,  chord: [233.08, 293.66, 349.23] },
    { bass: 49.00, fifth: 73.42,  chord: [196.00, 233.08, 293.66] }, // Gm: G Bb D
    { bass: 49.00, fifth: 73.42,  chord: [196.00, 233.08, 293.66] },
    { bass: 55.00, fifth: 82.41,  chord: [220.00, 277.18, 329.63] }, // A:  A C# E
    { bass: 73.42, fifth: 110.00, chord: [293.66, 349.23, 440.00] }  // Dm
];

const audioSystem = {
    ctx: null,
    muted: false,

    music: {
        initialized: false,
        masterGain: null,
        musicGain: null,
        sfxGain: null,
        bassGain: null,
        leadGain: null,
        ambienceGain: null,
        droneOsc: null,
        droneOsc2: null,
        noiseSource: null,
        active: false,
        stopFlag: true,
        nextNoteTime: 0,
        step: 0,
        timer: null,
        currentLayer: 'full',
        prefs: {
            musicVolume: 0.5,
            sfxVolume: 0.7,
            musicEnabled: true,
            sfxEnabled: true
        }
    },

    getContext() {
        if (!this.ctx) {
            try {
                const AudioContext = window.AudioContext || window.webkitAudioContext;
                if (AudioContext) this.ctx = new AudioContext();
            } catch (e) {}
        }
        return this.ctx;
    },

    resume() {
        const ctx = this.getContext();
        if (!ctx) return;
        if (ctx.state === 'suspended') ctx.resume().catch(() => {});
    },

    initMusic() {
        if (this.music.initialized) return true;
        const ctx = this.getContext();
        if (!ctx) return false;

        const m = this.music;
        m.masterGain = ctx.createGain();
        m.masterGain.gain.value = 1;
        m.masterGain.connect(ctx.destination);

        m.musicGain = ctx.createGain();
        m.musicGain.gain.value = m.prefs.musicEnabled ? m.prefs.musicVolume : 0;
        m.musicGain.connect(m.masterGain);

        m.sfxGain = ctx.createGain();
        m.sfxGain.gain.value = m.prefs.sfxEnabled ? m.prefs.sfxVolume : 0;
        m.sfxGain.connect(m.masterGain);

        m.bassGain = ctx.createGain();
        m.bassGain.gain.value = 0.22;
        m.bassGain.connect(m.musicGain);

        m.leadGain = ctx.createGain();
        m.leadGain.gain.value = 0.12;
        m.leadGain.connect(m.musicGain);

        m.ambienceGain = ctx.createGain();
        m.ambienceGain.gain.value = 0.35;
        m.ambienceGain.connect(m.musicGain);

        m.initialized = true;
        return true;
    },

    // Ruta de destino para SFX: usa sfxGain si la música está inicializada,
    // si no, destino directo (retrocompat).
    sfxDestination() {
        const ctx = this.ctx;
        if (this.music.initialized && this.music.sfxGain) return this.music.sfxGain;
        return ctx ? ctx.destination : null;
    },

    playTone(freq, type, duration, startTime = 0, vol = 0.1) {
        if (this.muted) return;
        const ctx = this.getContext();
        if (!ctx) return;
        if (ctx.state === 'suspended') this.resume();
        try {
            const osc = ctx.createOscillator();
            const gain = ctx.createGain();
            osc.type = type;
            const t = ctx.currentTime + 0.01 + startTime;
            osc.frequency.setValueAtTime(freq, t);
            gain.gain.setValueAtTime(0, t);
            gain.gain.linearRampToValueAtTime(vol, t + 0.02);
            gain.gain.linearRampToValueAtTime(0.001, t + duration);
            osc.connect(gain);
            gain.connect(this.sfxDestination() || ctx.destination);
            osc.start(t);
            osc.stop(t + duration + 0.05);
        } catch (e) {}
    },

    playNoise(duration, startTime = 0, vol = 0.15) {
        if (this.muted) return;
        const ctx = this.getContext();
        if (!ctx) return;
        if (ctx.state === 'suspended') this.resume();
        try {
            const bufferSize = Math.floor(ctx.sampleRate * duration);
            const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.6;

            const source = ctx.createBufferSource();
            source.buffer = buffer;
            const gain = ctx.createGain();
            const filter = ctx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.value = 1200;

            const t = ctx.currentTime + 0.01 + startTime;
            gain.gain.setValueAtTime(vol, t);
            gain.gain.exponentialRampToValueAtTime(0.001, t + duration);

            source.connect(filter);
            filter.connect(gain);
            gain.connect(this.sfxDestination() || ctx.destination);
            source.start(t);
            source.stop(t + duration + 0.05);
        } catch (e) {}
    },

    playClick() {
        this.playTone(800, 'sine', 0.08, 0, 0.08);
    },

    playSelloAprobar() {
        this.playNoise(0.06, 0, 0.25);
        this.playTone(180, 'triangle', 0.12, 0, 0.15);
        this.playTone(440, 'sine', 0.15, 0.08, 0.05);
    },

    playSelloRechazar() {
        this.playNoise(0.06, 0, 0.25);
        this.playTone(140, 'triangle', 0.12, 0, 0.18);
        this.playTone(110, 'sawtooth', 0.2, 0.05, 0.06);
    },

    playAlert() {
        this.playTone(880, 'triangle', 0.1, 0, 0.08);
        this.playTone(1108, 'triangle', 0.12, 0.1, 0.08);
    },

    playError() {
        this.playTone(150, 'sawtooth', 0.2, 0, 0.1);
    },

    playDing() {
        this.playTone(1200, 'sine', 0.15, 0, 0.08);
        this.playTone(1600, 'sine', 0.2, 0.1, 0.06);
    },

    playFinDia() {
        this.playTone(523, 'sine', 0.15, 0, 0.08);
        this.playTone(659, 'sine', 0.15, 0.1, 0.08);
        this.playTone(784, 'sine', 0.3, 0.2, 0.08);
    },

    // ============ BANDA SONORA PROCEDURAL ============

    startMusic(layer = 'full') {
        if (!this.initMusic()) return;
        this.resume();

        const ctx = this.ctx;
        const m = this.music;

        if (m.active) {
            this.setMusicLayer(layer);
            return;
        }

        // Restaurar ganancia del bus de música por si venía de un fade-out
        try {
            const vol = m.prefs.musicEnabled ? m.prefs.musicVolume : 0;
            m.musicGain.gain.cancelScheduledValues(ctx.currentTime);
            m.musicGain.gain.setValueAtTime(vol, ctx.currentTime);
        } catch (e) {}

        m.active = true;
        m.stopFlag = false;
        m.step = 0;
        m.nextNoteTime = ctx.currentTime + 0.15;

        this.startAmbience();
        this.setMusicLayer(layer, 0);
        this.musicScheduler();
    },

    stopMusic(fadeMs = 1500) {
        const m = this.music;
        if (!m.active) return;
        const ctx = this.ctx;
        if (!ctx) return;

        m.stopFlag = true;
        m.active = false;

        if (m.timer) { clearTimeout(m.timer); m.timer = null; }

        const now = ctx.currentTime;
        const tEnd = now + Math.max(0.05, fadeMs / 1000);

        try {
            m.musicGain.gain.cancelScheduledValues(now);
            m.musicGain.gain.setValueAtTime(m.musicGain.gain.value, now);
            m.musicGain.gain.linearRampToValueAtTime(0.0001, tEnd);
        } catch (e) {}

        setTimeout(() => {
            try { if (m.droneOsc) { m.droneOsc.stop(); m.droneOsc.disconnect(); } } catch (e) {}
            try { if (m.droneOsc2) { m.droneOsc2.stop(); m.droneOsc2.disconnect(); } } catch (e) {}
            try { if (m.noiseSource) { m.noiseSource.stop(); m.noiseSource.disconnect(); } } catch (e) {}
            m.droneOsc = null; m.droneOsc2 = null; m.noiseSource = null;
            try {
                const vol = m.prefs.musicEnabled ? m.prefs.musicVolume : 0;
                m.musicGain.gain.setValueAtTime(vol, ctx.currentTime);
            } catch (e) {}
        }, fadeMs + 80);
    },

    setMusicLayer(layer, fadeMs = 400) {
        if (!this.music.initialized) return;
        const ctx = this.ctx;
        const m = this.music;
        m.currentLayer = layer;
        const t = ctx.currentTime;
        const tEnd = t + fadeMs / 1000;

        let bass = 0, lead = 0, ambience = 0;
        switch (layer) {
            case 'full':    bass = 0.22; lead = 0.12; ambience = 0.35; break;
            case 'reduced': bass = 0.18; lead = 0;    ambience = 0.35; break;
            case 'dim':     bass = 0.08; lead = 0.04; ambience = 0.20; break;
            case 'ambient': bass = 0;    lead = 0;    ambience = 0.28; break;
            default:        bass = 0.22; lead = 0.12; ambience = 0.35;
        }

        const ramp = (node, target) => {
            try {
                node.gain.cancelScheduledValues(t);
                node.gain.setValueAtTime(node.gain.value, t);
                if (fadeMs <= 0) node.gain.setValueAtTime(target, t);
                else node.gain.linearRampToValueAtTime(target, tEnd);
            } catch (e) {}
        };
        ramp(m.bassGain, bass);
        ramp(m.leadGain, lead);
        ramp(m.ambienceGain, ambience);
    },

    startAmbience() {
        const ctx = this.ctx;
        const m = this.music;
        if (!m.ambienceGain) return;

        // Drone sub en D1 + quinta en A1 — constante, opresivo
        const droneOsc1 = ctx.createOscillator();
        droneOsc1.type = 'sine';
        droneOsc1.frequency.value = 36.71;
        const droneOsc2 = ctx.createOscillator();
        droneOsc2.type = 'triangle';
        droneOsc2.frequency.value = 55;

        const droneMix = ctx.createGain();
        droneMix.gain.value = 0.55;
        droneOsc1.connect(droneMix);
        droneOsc2.connect(droneMix);
        droneMix.connect(m.ambienceGain);

        droneOsc1.start();
        droneOsc2.start();
        m.droneOsc = droneOsc1;
        m.droneOsc2 = droneOsc2;

        // Ruido rosa aproximado filtrado — ambiente de oficina apagada
        const bufferSize = 2 * ctx.sampleRate;
        const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
        const data = buffer.getChannelData(0);
        let b0 = 0, b1 = 0, b2 = 0;
        for (let i = 0; i < bufferSize; i++) {
            const white = Math.random() * 2 - 1;
            b0 = 0.99 * b0 + 0.05 * white;
            b1 = 0.95 * b1 + 0.15 * white;
            b2 = 0.85 * b2 + 0.25 * white;
            data[i] = (b0 + b1 + b2) * 0.2;
        }
        const source = ctx.createBufferSource();
        source.buffer = buffer;
        source.loop = true;

        const filter = ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 500;

        const noiseMix = ctx.createGain();
        noiseMix.gain.value = 0.3;

        source.connect(filter);
        filter.connect(noiseMix);
        noiseMix.connect(m.ambienceGain);
        source.start();
        m.noiseSource = source;
    },

    musicScheduler() {
        const m = this.music;
        if (m.stopFlag) return;
        const ctx = this.ctx;
        if (!ctx) return;

        const lookaheadMs = 100;
        const scheduleAheadSec = 0.28;
        const beatSec = 60 / MUSIC_TEMPO_BPM;

        while (m.nextNoteTime < ctx.currentTime + scheduleAheadSec) {
            this.scheduleBeat(m.step, m.nextNoteTime, beatSec);
            m.nextNoteTime += beatSec;
            m.step = (m.step + 1) % MUSIC_LOOP_BEATS;
        }

        m.timer = setTimeout(() => this.musicScheduler(), lookaheadMs);
    },

    scheduleBeat(step, time, beatSec) {
        const barIndex = Math.floor(step / MUSIC_BEATS_PER_BAR);
        const beatInBar = step % MUSIC_BEATS_PER_BAR;
        const chord = MUSIC_PROGRESSION[barIndex];
        if (!chord) return;

        // Bajo marcial: tónica en 1/3, quinta en 2/4
        const bassFreq = (beatInBar % 2 === 0) ? chord.bass : chord.fifth;
        this.scheduleBassNote(bassFreq, time, beatSec * 0.85);

        // Bronces: acorde en beats 1 y 3 (dura ~2 pulsos)
        if (beatInBar === 0 || beatInBar === 2) {
            this.scheduleLeadChord(chord.chord, time, beatSec * 1.9);
        }

        // Click de máquina de escribir muy ocasional, en el último pulso del loop
        if (barIndex === MUSIC_BARS - 1 && beatInBar === 3) {
            this.scheduleTick(time + beatSec * 0.5);
        }
    },

    scheduleBassNote(freq, time, duration) {
        const ctx = this.ctx;
        const m = this.music;
        try {
            const osc = ctx.createOscillator();
            osc.type = 'square';
            osc.frequency.setValueAtTime(freq, time);

            const filter = ctx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.value = 650;
            filter.Q.value = 2;

            const g = ctx.createGain();
            g.gain.setValueAtTime(0, time);
            g.gain.linearRampToValueAtTime(1, time + 0.02);
            g.gain.exponentialRampToValueAtTime(0.4, time + duration * 0.6);
            g.gain.exponentialRampToValueAtTime(0.001, time + duration);

            osc.connect(filter);
            filter.connect(g);
            g.connect(m.bassGain);

            osc.start(time);
            osc.stop(time + duration + 0.05);
        } catch (e) {}
    },

    scheduleLeadChord(freqs, time, duration) {
        const ctx = this.ctx;
        const m = this.music;
        try {
            const filter = ctx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.setValueAtTime(800, time);
            filter.frequency.linearRampToValueAtTime(1400, time + 0.35);
            filter.Q.value = 3;
            filter.connect(m.leadGain);

            const chordGain = ctx.createGain();
            chordGain.gain.setValueAtTime(0, time);
            chordGain.gain.linearRampToValueAtTime(1, time + 0.14);
            chordGain.gain.setValueAtTime(1, time + Math.max(0.2, duration - 0.35));
            chordGain.gain.exponentialRampToValueAtTime(0.001, time + duration);
            chordGain.connect(filter);

            freqs.forEach((f, i) => {
                const osc = ctx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(f, time);
                osc.detune.value = (i - 1) * 6;
                const voiceGain = ctx.createGain();
                voiceGain.gain.value = 1 / Math.max(freqs.length, 1);
                osc.connect(voiceGain);
                voiceGain.connect(chordGain);
                osc.start(time);
                osc.stop(time + duration + 0.1);
            });
        } catch (e) {}
    },

    scheduleTick(time) {
        const ctx = this.ctx;
        const m = this.music;
        try {
            const bufferSize = Math.floor(ctx.sampleRate * 0.03);
            const buf = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
            const data = buf.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
            const src = ctx.createBufferSource();
            src.buffer = buf;
            const g = ctx.createGain();
            g.gain.value = 0.25;
            const filter = ctx.createBiquadFilter();
            filter.type = 'bandpass';
            filter.frequency.value = 2200;
            src.connect(filter);
            filter.connect(g);
            g.connect(m.ambienceGain);
            src.start(time);
            src.stop(time + 0.05);
        } catch (e) {}
    },

    playStingerDespido() {
        if (!this.initMusic()) return;
        const ctx = this.ctx;
        const m = this.music;
        const t = ctx.currentTime + 0.02;
        // Acorde disminuido para impacto: D F Ab B
        const freqs = [146.83, 174.61, 207.65, 246.94];
        freqs.forEach(f => {
            try {
                const osc = ctx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.value = f;
                const g = ctx.createGain();
                g.gain.setValueAtTime(0, t);
                g.gain.linearRampToValueAtTime(0.14, t + 0.05);
                g.gain.exponentialRampToValueAtTime(0.001, t + 1.8);
                osc.connect(g);
                g.connect(m.masterGain || ctx.destination);
                osc.start(t);
                osc.stop(t + 1.9);
            } catch (e) {}
        });
    },

    // ============ PREFERENCIAS ============

    setMusicVolume(vol) {
        const m = this.music;
        const v = Math.max(0, Math.min(1, vol));
        m.prefs.musicVolume = v;
        if (m.musicGain && this.ctx) {
            try {
                const now = this.ctx.currentTime;
                m.musicGain.gain.cancelScheduledValues(now);
                m.musicGain.gain.setTargetAtTime(m.prefs.musicEnabled ? v : 0, now, 0.05);
            } catch (e) {}
        }
    },

    setSfxVolume(vol) {
        const m = this.music;
        const v = Math.max(0, Math.min(1, vol));
        m.prefs.sfxVolume = v;
        if (m.sfxGain && this.ctx) {
            try {
                const now = this.ctx.currentTime;
                m.sfxGain.gain.cancelScheduledValues(now);
                m.sfxGain.gain.setTargetAtTime(m.prefs.sfxEnabled ? v : 0, now, 0.02);
            } catch (e) {}
        }
    },

    setMusicEnabled(enabled) {
        const m = this.music;
        m.prefs.musicEnabled = !!enabled;
        if (m.musicGain && this.ctx) {
            try {
                const now = this.ctx.currentTime;
                m.musicGain.gain.cancelScheduledValues(now);
                m.musicGain.gain.setTargetAtTime(m.prefs.musicEnabled ? m.prefs.musicVolume : 0, now, 0.05);
            } catch (e) {}
        }
    },

    setSfxEnabled(enabled) {
        const m = this.music;
        m.prefs.sfxEnabled = !!enabled;
        if (m.sfxGain && this.ctx) {
            try {
                const now = this.ctx.currentTime;
                m.sfxGain.gain.cancelScheduledValues(now);
                m.sfxGain.gain.setTargetAtTime(m.prefs.sfxEnabled ? m.prefs.sfxVolume : 0, now, 0.02);
            } catch (e) {}
        }
    },

    getPrefs() {
        return Object.assign({}, this.music.prefs);
    },

    applyPrefs(prefs) {
        if (!prefs) return;
        const m = this.music;
        if (typeof prefs.musicVolume === 'number') m.prefs.musicVolume = Math.max(0, Math.min(1, prefs.musicVolume));
        if (typeof prefs.sfxVolume === 'number') m.prefs.sfxVolume = Math.max(0, Math.min(1, prefs.sfxVolume));
        if (typeof prefs.musicEnabled === 'boolean') m.prefs.musicEnabled = prefs.musicEnabled;
        if (typeof prefs.sfxEnabled === 'boolean') m.prefs.sfxEnabled = prefs.sfxEnabled;
        if (m.initialized) {
            this.setMusicVolume(m.prefs.musicVolume);
            this.setSfxVolume(m.prefs.sfxVolume);
            this.setMusicEnabled(m.prefs.musicEnabled);
            this.setSfxEnabled(m.prefs.sfxEnabled);
        }
    },

    toggleMute() {
        this.resume();
        this.muted = !this.muted;
        return this.muted;
    },

    setMuted(muted) {
        this.muted = !!muted;
    }
};
