import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
import { isElectronApp } from '../utils/assetPath';

interface AudioContextType {
    masterVolume: number;
    musicVolume: number;
    sfxVolume: number;
    isMuted: boolean;
    setMasterVolume: (val: number) => void;
    setMusicVolume: (val: number) => void;
    setSfxVolume: (val: number) => void;
    setIsMuted: (val: boolean) => void;
    playSfx: (path: string, fallbackPath?: string) => void;
    audioContext: AudioContext | null;
    getAudioBuffer: (path: string) => Promise<AudioBuffer | null>;
}

const AudioContext = createContext<AudioContextType | undefined>(undefined);

// Cache for decoded audio buffers to prevent re-fetching/re-decoding
const bufferCache: Record<string, AudioBuffer> = {};

export const AudioProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
    // Volume State
    const [masterVolume, setMasterVolumeState] = useState(() => {
        const saved = localStorage.getItem('audio_masterVolume');
        return saved ? parseFloat(saved) : 1.0;
    });
    const [musicVolume, setMusicVolumeState] = useState(() => {
        const saved = localStorage.getItem('audio_musicVolume');
        return saved ? parseFloat(saved) : 0.5;
    });
    const [sfxVolume, setSfxVolumeState] = useState(() => {
        const saved = localStorage.getItem('audio_sfxVolume');
        return saved ? parseFloat(saved) : 0.8;
    });
    const [isMuted, setIsMutedState] = useState(() => {
        const saved = localStorage.getItem('audio_isMuted');
        return saved ? JSON.parse(saved) : false;
    });

    // Web Audio API State
    const [audioContext, setAudioContext] = useState<AudioContext | null>(null);
    const audioContextRef = useRef<AudioContext | null>(null); // Ref for immediate access in loops/events
    const sfxGainNodeRef = useRef<GainNode | null>(null);

    // Initialize AudioContext on mount
    useEffect(() => {
        const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
        const ctx = new AudioContextClass();
        audioContextRef.current = ctx;
        setAudioContext(ctx);

        // Create a dedicated GainNode for SFX
        const sfxGain = ctx.createGain();
        sfxGain.connect(ctx.destination);
        sfxGainNodeRef.current = sfxGain;

        return () => {
            if (ctx.state !== 'closed') {
                ctx.close();
            }
        };
    }, []);

    // Global "Unlock" Listener for Autoplay Policy
    useEffect(() => {
        const unlockAudio = () => {
            const ctx = audioContextRef.current;
            if (ctx && ctx.state === 'suspended') {
                ctx.resume().then(() => {
                    console.log('AudioContext resumed via user interaction');
                }).catch(e => console.error('Failed to resume AudioContext:', e));
            }
            // Once resumed, we can generally remove these listeners, 
            // but Chrome sometimes needs multiple checks if it suspends again.
            // Usually one accepted interaction is enough for the session.
        };

        const events = ['click', 'touchstart', 'keydown', 'mousedown'];
        events.forEach(e => document.addEventListener(e, unlockAudio, { passive: true }));

        return () => {
            events.forEach(e => document.removeEventListener(e, unlockAudio));
        };
    }, []);

    // Update SFX Gain when volumes change
    useEffect(() => {
        if (sfxGainNodeRef.current && audioContextRef.current) {
            const effectiveSfxVol = isMuted ? 0 : masterVolume * sfxVolume;
            const currentTime = audioContextRef.current.currentTime;
            sfxGainNodeRef.current.gain.setTargetAtTime(effectiveSfxVol, currentTime, 0.1);
        }
    }, [masterVolume, sfxVolume, isMuted]);

    // Volume Setters with Persistence
    const setMasterVolume = (val: number) => {
        setMasterVolumeState(val);
        localStorage.setItem('audio_masterVolume', val.toString());
    };

    const setMusicVolume = (val: number) => {
        setMusicVolumeState(val);
        localStorage.setItem('audio_musicVolume', val.toString());
    };

    const setSfxVolume = (val: number) => {
        setSfxVolumeState(val);
        localStorage.setItem('audio_sfxVolume', val.toString());
    };

    const setIsMuted = (val: boolean) => {
        setIsMutedState(val);
        localStorage.setItem('audio_isMuted', JSON.stringify(val));
    };

    // Helper: Resolve Path
    const resolvePath = useCallback((p: string) => {
        if (p.startsWith('http') || p.startsWith('file')) return p;
        const cleanPath = p.startsWith('/') ? p.slice(1) : p;
        return isElectronApp() ? `./${cleanPath}` : `/${cleanPath}`;
    }, []);

    // Helper: Fetch and Decode Audio
    const getAudioBuffer = useCallback(async (path: string): Promise<AudioBuffer | null> => {
        const ctx = audioContextRef.current;
        if (!ctx) return null;

        const resolvedPath = resolvePath(path);

        // Check cache
        if (bufferCache[resolvedPath]) {
            return bufferCache[resolvedPath];
        }

        try {
            const response = await fetch(resolvedPath);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const arrayBuffer = await response.arrayBuffer();
            const decodedBuffer = await ctx.decodeAudioData(arrayBuffer);

            // Store in cache
            bufferCache[resolvedPath] = decodedBuffer;
            return decodedBuffer;
        } catch (error) {
            console.error(`Failed to load audio: ${path}`, error);
            return null;
        }
    }, [resolvePath]);

    // Play SFX
    const playSfx = useCallback(async (path: string, fallbackPath?: string) => {
        // Early exit if essentially muted
        if (isMuted || (masterVolume * sfxVolume) <= 0) return;

        const ctx = audioContextRef.current;
        const sfxGain = sfxGainNodeRef.current;

        if (!ctx || !sfxGain) return;

        // Ensure context is running
        if (ctx.state === 'suspended') {
            try {
                await ctx.resume();
            } catch (e) {
                // If this fails (e.g. no user gesture yet), we can't play
                console.warn('AudioContext suspended, cannot play SFX');
                return;
            }
        }

        let buffer = await getAudioBuffer(path);

        if (!buffer && fallbackPath) {
            console.warn(`Primary SFX failed: ${path}, trying fallback: ${fallbackPath}`);
            buffer = await getAudioBuffer(fallbackPath);
        }

        if (buffer) {
            const source = ctx.createBufferSource();
            source.buffer = buffer;
            source.connect(sfxGain);
            source.start(0);
        }
    }, [isMuted, masterVolume, sfxVolume, getAudioBuffer]);

    return (
        <AudioContext.Provider value={{
            masterVolume,
            musicVolume,
            sfxVolume,
            isMuted,
            setMasterVolume,
            setMusicVolume,
            setSfxVolume,
            setIsMuted,
            playSfx,
            audioContext,
            getAudioBuffer
        }}>
            {children}
        </AudioContext.Provider>
    );
};

export const useAudio = () => {
    const context = useContext(AudioContext);
    if (context === undefined) {
        throw new Error('useAudio must be used within an AudioProvider');
    }
    return context;
};
