import { useCallback, useEffect, useRef, useState } from "react";
const useAudioOutput = ({ handleAudioOutputStart, handleAudioOutputEnd } = {})=>{
    const audioContextRef = useRef(null);
    const audioQueueRef = useRef([]);
    const isPlayingRef = useRef(false);
    const nextStartTimeRef = useRef(0);
    const [isOutputAudioActive, setIsOutputAudioActive] = useState(false);
    const activeSourcesRef = useRef(new Set());
    const sequenceNumberRef = useRef(0);
    const decodedBuffersRef = useRef(new Map());
    const isDecodingRef = useRef(new Set());
    const hasStartedRef = useRef(false);
    const workerRef = useRef(null);
    const pendingDecodesRef = useRef(new Map());
    const decodeIdCounterRef = useRef(0);
    const initWorker = useCallback(()=>{
        if (!workerRef.current) {
            workerRef.current = new Worker(getAudioDecoderUrl());
            workerRef.current.onmessage = (event)=>{
                const { id, floatData, error } = event.data;
                const pending = pendingDecodesRef.current.get(id);
                if (pending) {
                    if (error) pending.reject(new Error(error));
                    else pending.resolve(floatData);
                    pendingDecodesRef.current.delete(id);
                }
            };
            workerRef.current.onerror = (error)=>{
                console.error('[AudioPlayer] Worker error:', error);
            };
        }
        return workerRef.current;
    }, []);
    const initAudioContext = useCallback(()=>{
        if (!audioContextRef.current) {
            audioContextRef.current = new AudioContext();
            nextStartTimeRef.current = audioContextRef.current.currentTime;
        }
        return audioContextRef.current;
    }, []);
    const decodeAudioData = useCallback(async (base64Data)=>{
        const audioContext = initAudioContext();
        const worker = initWorker();
        const decodeId = `decode-${decodeIdCounterRef.current++}`;
        const floatData = await new Promise((resolve, reject)=>{
            pendingDecodesRef.current.set(decodeId, {
                resolve,
                reject
            });
            worker.postMessage({
                id: decodeId,
                base64Data
            });
            setTimeout(()=>{
                if (pendingDecodesRef.current.has(decodeId)) {
                    pendingDecodesRef.current.delete(decodeId);
                    reject(new Error('Audio decode timeout'));
                }
            }, 5000);
        });
        const sourceSampleRate = 24000;
        const audioBuffer = audioContext.createBuffer(1, floatData.length, sourceSampleRate);
        audioBuffer.copyToChannel(floatData, 0);
        return audioBuffer;
    }, [
        initAudioContext,
        initWorker
    ]);
    const checkIfPlaybackComplete = useCallback(()=>{
        const audioContext = audioContextRef.current;
        if (!audioContext) return;
        if (0 === activeSourcesRef.current.size && 0 === audioQueueRef.current.length) {
            if (hasStartedRef.current) {
                hasStartedRef.current = false;
                isPlayingRef.current = false;
                setIsOutputAudioActive(false);
                handleAudioOutputEnd?.();
            }
        }
    }, [
        handleAudioOutputEnd
    ]);
    const processQueue = useCallback(async ()=>{
        if (isPlayingRef.current || 0 === audioQueueRef.current.length) return;
        isPlayingRef.current = true;
        const audioContext = initAudioContext();
        const scheduleNextChunks = async ()=>{
            const chunksToProcess = [];
            while(audioQueueRef.current.length > 0){
                const item = audioQueueRef.current.shift();
                if (item) chunksToProcess.push(item);
            }
            if (0 === chunksToProcess.length) {
                isPlayingRef.current = false;
                setTimeout(()=>checkIfPlaybackComplete(), 100);
                return;
            }
            for (const item of chunksToProcess)try {
                let audioBuffer = decodedBuffersRef.current.get(item.data);
                if (!audioBuffer) audioBuffer = await decodeAudioData(item.data);
                decodedBuffersRef.current.delete(item.data);
                isDecodingRef.current.delete(item.data);
                const source = audioContext.createBufferSource();
                source.buffer = audioBuffer;
                source.connect(audioContext.destination);
                activeSourcesRef.current.add(source);
                const currentTime = audioContext.currentTime;
                const startTime = Math.max(currentTime, nextStartTimeRef.current);
                source.start(startTime);
                if (!hasStartedRef.current && startTime <= currentTime + 0.1) {
                    hasStartedRef.current = true;
                    setIsOutputAudioActive(true);
                    handleAudioOutputStart?.();
                }
                source.onended = ()=>{
                    activeSourcesRef.current.delete(source);
                    if (audioQueueRef.current.length > 0 && !isPlayingRef.current) {
                        isPlayingRef.current = true;
                        setTimeout(()=>scheduleNextChunks(), 0);
                    } else checkIfPlaybackComplete();
                };
                nextStartTimeRef.current = startTime + audioBuffer.duration;
            } catch (error) {
                console.error('[AudioPlayer] Error processing audio:', error);
            }
            if (audioQueueRef.current.length > 0) setTimeout(()=>scheduleNextChunks(), 0);
            else isPlayingRef.current = false;
        };
        await scheduleNextChunks();
    }, [
        decodeAudioData,
        initAudioContext,
        handleAudioOutputStart,
        checkIfPlaybackComplete
    ]);
    const queueOutputAudio = useCallback((mimeType, base64Data, sequenceNumber)=>{
        if ('audio/pcm;rate=24000' != mimeType) return void console.error("Unexpected mime type for audio output: {mimeType}");
        if (0 != sequenceNumber && sequenceNumber != sequenceNumberRef.current) console.warn(`[AudioPlayer] sequence number ${sequenceNumber} received when ${sequenceNumberRef.current} was expected.`);
        sequenceNumberRef.current = sequenceNumber + 1;
        audioQueueRef.current.push({
            data: base64Data
        });
        if (!isDecodingRef.current.has(base64Data) && !decodedBuffersRef.current.has(base64Data)) {
            isDecodingRef.current.add(base64Data);
            decodeAudioData(base64Data).then((audioBuffer)=>{
                decodedBuffersRef.current.set(base64Data, audioBuffer);
                isDecodingRef.current.delete(base64Data);
            }).catch((error)=>{
                console.error('[AudioPlayer] Error preloading audio:', error);
                isDecodingRef.current.delete(base64Data);
            });
        }
        if (!isPlayingRef.current) processQueue();
    }, [
        processQueue,
        decodeAudioData
    ]);
    const clearOutputAudioQueue = useCallback(()=>{
        audioQueueRef.current = [];
        isPlayingRef.current = false;
        hasStartedRef.current = false;
        setIsOutputAudioActive(false);
        decodedBuffersRef.current.clear();
        isDecodingRef.current.clear();
        pendingDecodesRef.current.clear();
        if (audioContextRef.current) nextStartTimeRef.current = audioContextRef.current.currentTime;
        activeSourcesRef.current.forEach((source)=>{
            try {
                source.stop();
            } catch (e) {}
        });
        activeSourcesRef.current.clear();
    }, []);
    useEffect(()=>()=>{
            if (audioContextRef.current && 'closed' !== audioContextRef.current.state) audioContextRef.current.close();
            if (workerRef.current) {
                workerRef.current.terminate();
                workerRef.current = null;
            }
            pendingDecodesRef.current.clear();
            if (audioDecoderUrl) {
                URL.revokeObjectURL(audioDecoderUrl);
                audioDecoderUrl = null;
            }
        }, []);
    return {
        queueOutputAudio,
        clearOutputAudioQueue,
        isOutputAudioActive
    };
};
const AUDIO_DECODER_CODE = `
// Web Worker for audio decoding operations
// Handles base64 decoding and PCM to Float32 conversion off the main thread

self.addEventListener('message', (event) => {
    const { id, base64Data } = event.data;

    try {
        // Convert base64 to binary string
        const binaryString = atob(base64Data);

        // Convert to Uint8Array
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }

        // Convert to Int16Array (16-bit PCM)
        const pcmData = new Int16Array(bytes.buffer);

        // Convert to Float32Array
        const floatData = new Float32Array(pcmData.length);
        for (let i = 0; i < pcmData.length; i++) {
            floatData[i] = pcmData[i] / 32768.0; // Convert 16-bit to float (-1 to 1)
        }

        // Send the result back to the main thread
        const response = {
            id,
            floatData,
        };

        // Transfer the ArrayBuffer to avoid copying
        self.postMessage(response, [floatData.buffer]);

    } catch (error) {
        // Send error response
        const response = {
            id,
            floatData: new Float32Array(0),
            error: error.message || 'Unknown error',
        };
        self.postMessage(response);
    }
});
`;
let audioDecoderUrl = null;
const getAudioDecoderUrl = ()=>{
    if (!audioDecoderUrl) {
        const blob = new Blob([
            AUDIO_DECODER_CODE
        ], {
            type: "application/javascript"
        });
        audioDecoderUrl = URL.createObjectURL(blob);
    }
    return audioDecoderUrl;
};
export { useAudioOutput };
