import React, {
    createContext,
    useContext,
    useEffect,
    useMemo,
    useRef,
    useState,
} from "react";

const WebAudioContext = createContext({
    context: null,
    globalGainNode: null,
    audiosStoreRef: { current: [] },
});

/**
 *
 * @param {Object} props
 */
export const AudioProvider = ({ children, volumn = 0 }) => {
    const audiosStoreRef = useRef([]);
    const [context, setContext] = useState(null);
    const [globalGainNode, setGlobalGainNode] = useState(null);

    useEffect(() => {
        if (!("AudioContext" in window || "webkitAudioContext" in window)) {
            console.error("Browser does not support AudioContext");
            return;
        }

        const _context = new window.AudioContext();

        const _globalGainNode = _context.createGain();
        _globalGainNode.gain.value = 1;
        _globalGainNode.connect(_context.destination);

        setContext(_context);
        setGlobalGainNode(_globalGainNode);
    }, []);

    useEffect(() => {
        if (!globalGainNode) return;
        globalGainNode.gain.value = volumn;
    }, [volumn, globalGainNode]);

    return (
        <WebAudioContext.Provider
            value={{ context, globalGainNode, audiosStoreRef }}
        >
            {children}
        </WebAudioContext.Provider>
    );
};

/**
 * @param {string[]} audioSrcs 聲音 src 列表
 */
function useAudios(audioSrcs = []) {
    const { context, globalGainNode, audiosStoreRef } =
        useContext(WebAudioContext);

    const audios = useMemo(() => {
        audioSrcs.forEach((audioSrc) => {
            if (
                context &&
                !audiosStoreRef.current.find(
                    (audioStore) => audioStore.src === audioSrc
                )
            ) {
                const newAudioStoreItem = new AudioStoreItem(
                    context,
                    audioSrc,
                    globalGainNode
                );
                audiosStoreRef.current = [
                    ...audiosStoreRef.current,
                    newAudioStoreItem,
                ];
            }
        });
        return audioSrcs.map((audioSrc) =>
            audiosStoreRef.current.find(
                (audioStore) => audioStore.src === audioSrc
            )
        );
    }, [audioSrcs]); // eslint-disable-line react-hooks/exhaustive-deps

    return audios;
}

class AudioStoreItem {
    constructor(context, audioSrc, outputNode = null) {
        this.src = audioSrc;
        this.__context = context;  
        this.__isPlaying = false;      

        fetch(audioSrc)
            .then((response) => {
                const contentType = response.headers.get("content-type");
                if (!response.ok) {
                    throw new Error(
                        `[AudioStoreItem] Audio fetch failed: src=${audioSrc}, status=${response.status}, statusText=${response.statusText}, contentType=${contentType ?? "unknown"}`
                    );
                }
                return response.arrayBuffer();
            })
            .then((arrayBuffer) => {
                this.__audioBuffer = this.__context.decodeAudioData(
                    arrayBuffer,
                    (audioBuffer) => {
                        this.__audioBuffer = audioBuffer;
                        const sourceGainNode = context.createGain();
                        sourceGainNode.gain.value = 1;
                        sourceGainNode.connect(
                            outputNode ?? context.destination
                        );
                        this.__gainNode = sourceGainNode;
                    },
                    (error) => {
                        console.error(
                            `[AudioStoreItem] Audio decode failed: src=${audioSrc}`,
                            error
                        );
                    }
                );
            })
            .catch((error) => {
                console.error(`[AudioStoreItem] Audio load failed: src=${audioSrc}`, error);
            });
    }

    async play({ loop = false, volume = 1, ramp = false } = {}) {
        if (!this.__audioBuffer) return;
        if (this.__loop) this.stop();

        if (this.__context.state !== 'running') {
            await this.__context.resume();
        }

        this.__isPlaying = true;

        const source = this.__context.createBufferSource();
        source.buffer = this.__audioBuffer;

        if (ramp) {
            source.connect(this.__gainNode);
            // Fade in volume over 1 seconds
            this.__gainNode.gain.setValueAtTime(0, this.__context.currentTime);
            this.__gainNode.gain.linearRampToValueAtTime(volume, this.__context.currentTime + 1);
        } else {
            source.connect(this.__gainNode);
        }

        source.loop = loop;
        source.start();

        this.__loop = loop;
        this.__bufferSource = source;
    }

    stop({ ramp = false } = {}) {
        if (!this.__gainNode || !this.__isPlaying) return;
        
        if (ramp) {
            this.__gainNode.gain.setValueAtTime(this.__gainNode.gain.value, this.__context.currentTime);
            this.__gainNode.gain.linearRampToValueAtTime(0, this.__context.currentTime + 1);
        } else {
            this.__gainNode.gain.value = 0;
        }

        // Schedule the actual stop after fade out
        setTimeout(() => {
            if (this.__bufferSource) {
                this.__bufferSource.stop();
                this.__isPlaying = false;

            }
        }, 500);
    }

    setVolume(volume) {
        if (!this.__gainNode) return;
        this.__gainNode.gain.value = volume;

    }
}

export default useAudios;
