import { useCallback, useRef, useState } from "react";
const SAMPLE_RATE = 24000;
const MIME_TYPE = `audio/pcm;rate=${SAMPLE_RATE}`;
var chat_audio_input_AudioInputState = /*#__PURE__*/ function(AudioInputState) {
    AudioInputState["Inactive"] = "inactive";
    AudioInputState["Starting"] = "starting";
    AudioInputState["Active"] = "active";
    AudioInputState["Stopping"] = "stopping";
    return AudioInputState;
}(chat_audio_input_AudioInputState || {});
const useAudioInput = ({ handleAudioInputData, handleAudioInputStart, handleAudioInputEnd } = {})=>{
    const [error, setError] = useState(null);
    const mediaStreamRef = useRef(null);
    const audioContextRef = useRef(null);
    const workletNodeRef = useRef(null);
    const sourceRef = useRef(null);
    const stateRef = useRef(chat_audio_input_AudioInputState.Inactive);
    const sequenceNumberRef = useRef(0);
    const encoderWorkerRef = useRef(null);
    const pendingEncodesRef = useRef(new Map());
    const encodeIdCounterRef = useRef(0);
    const initEncoderWorker = useCallback(()=>{
        if (!encoderWorkerRef.current) {
            encoderWorkerRef.current = new Worker(getAudioEncoderUrl());
            encoderWorkerRef.current.onmessage = (event)=>{
                const { id, base64Data, error: eventError } = event.data;
                const pending = pendingEncodesRef.current.get(id);
                if (pending) {
                    if (eventError) pending.reject(new Error(eventError));
                    else pending.resolve(base64Data);
                    pendingEncodesRef.current.delete(id);
                }
            };
            encoderWorkerRef.current.onerror = (eventError)=>{
                console.error('[AudioInput] Encoder worker error:', eventError);
            };
        }
        return encoderWorkerRef.current;
    }, []);
    const cleanup = useCallback(()=>{
        if (workletNodeRef.current) {
            workletNodeRef.current.disconnect();
            workletNodeRef.current.port.close();
            workletNodeRef.current = null;
        }
        if (sourceRef.current) {
            sourceRef.current.disconnect();
            sourceRef.current = null;
        }
        if (audioContextRef.current) {
            audioContextRef.current.close();
            audioContextRef.current = null;
        }
        if (mediaStreamRef.current) {
            mediaStreamRef.current.getTracks().forEach((track)=>track.stop());
            mediaStreamRef.current = null;
        }
        if (encoderWorkerRef.current) {
            encoderWorkerRef.current.terminate();
            encoderWorkerRef.current = null;
        }
        pendingEncodesRef.current.clear();
    }, []);
    const startAudioInput = useCallback(async (automaticActivityDetectionEnabled)=>{
        if (stateRef.current !== chat_audio_input_AudioInputState.Inactive) return false;
        stateRef.current = chat_audio_input_AudioInputState.Starting;
        sequenceNumberRef.current = 0;
        setError(null);
        try {
            const stream = await navigator.mediaDevices.getUserMedia({
                audio: {
                    channelCount: 1,
                    sampleRate: SAMPLE_RATE,
                    echoCancellation: true,
                    noiseSuppression: true
                }
            });
            if (stateRef.current !== chat_audio_input_AudioInputState.Starting) {
                cleanup();
                return false;
            }
            mediaStreamRef.current = stream;
            audioContextRef.current = new AudioContext({
                sampleRate: SAMPLE_RATE
            });
            await audioContextRef.current.audioWorklet.addModule(getAudioProcessorUrl());
            if (stateRef.current !== chat_audio_input_AudioInputState.Starting || !audioContextRef.current) {
                cleanup();
                return false;
            }
            workletNodeRef.current = new AudioWorkletNode(audioContextRef.current, 'audio-processor');
            if (stateRef.current !== chat_audio_input_AudioInputState.Starting) {
                cleanup();
                return false;
            }
            const encoderWorker = initEncoderWorker();
            workletNodeRef.current.port.onmessage = async (event)=>{
                if (stateRef.current !== chat_audio_input_AudioInputState.Active) return;
                if ('audio' === event.data.type) {
                    const inputData = event.data.buffer;
                    const encodeId = `encode-${encodeIdCounterRef.current++}`;
                    try {
                        const base64Data = await new Promise((resolve, reject)=>{
                            pendingEncodesRef.current.set(encodeId, {
                                resolve,
                                reject
                            });
                            encoderWorker.postMessage({
                                id: encodeId,
                                floatData: inputData
                            }, [
                                inputData.buffer
                            ]);
                            setTimeout(()=>{
                                if (pendingEncodesRef.current.has(encodeId)) {
                                    pendingEncodesRef.current.delete(encodeId);
                                    reject(new Error('Audio encode timeout'));
                                }
                            }, 5000);
                        });
                        handleAudioInputData?.(MIME_TYPE, base64Data, sequenceNumberRef.current++);
                    } catch (e) {
                        console.error('[AudioInput] Error encoding audio:', e);
                    }
                }
            };
            sourceRef.current = audioContextRef.current.createMediaStreamSource(stream);
            stateRef.current = chat_audio_input_AudioInputState.Active;
            handleAudioInputStart?.(automaticActivityDetectionEnabled);
            sourceRef.current.connect(workletNodeRef.current);
            workletNodeRef.current.connect(audioContextRef.current.destination);
            return true;
        } catch (err) {
            console.error('Error starting audio input:', err);
            setError(err instanceof Error ? err.message : 'Failed to start audio input');
            stateRef.current = chat_audio_input_AudioInputState.Inactive;
            cleanup();
            return false;
        }
    }, [
        handleAudioInputData,
        handleAudioInputStart,
        cleanup,
        initEncoderWorker
    ]);
    const stopAudioInput = useCallback(()=>{
        const currentState = stateRef.current;
        if (currentState === chat_audio_input_AudioInputState.Inactive) return;
        if (currentState === chat_audio_input_AudioInputState.Stopping) return;
        stateRef.current = chat_audio_input_AudioInputState.Stopping;
        cleanup();
        stateRef.current = chat_audio_input_AudioInputState.Inactive;
        if (currentState == chat_audio_input_AudioInputState.Active) handleAudioInputEnd?.(sequenceNumberRef.current);
    }, [
        cleanup,
        handleAudioInputEnd
    ]);
    return {
        startAudioInput,
        stopAudioInput,
        audioInputError: error
    };
};
const AUDIO_PROCESSOR_CODE = `
// Audio worklet processor for capturing microphone input
class AudioProcessor extends AudioWorkletProcessor {
    constructor() {
        super();
        this.bufferSize = 4096;
        this.buffer = new Float32Array(this.bufferSize);
        this.bufferIndex = 0;
    }

    process(inputs, outputs, parameters) {
        const input = inputs[0];
        if (!input?.[0]) {
            return true;
        }

        const inputChannel = input[0];

        // Buffer the input data
        for (let i = 0; i < inputChannel.length; i++) {
            this.buffer[this.bufferIndex++] = inputChannel[i];

            // When buffer is full, send it to the main thread
            if (this.bufferIndex >= this.bufferSize) {
                this.port.postMessage({
                    type: 'audio',
                    buffer: this.buffer.slice(0, this.bufferSize),
                });
                this.bufferIndex = 0;
            }
        }

        return true;
    }
}

registerProcessor('audio-processor', AudioProcessor);
`;
let audioProcessorUrl = null;
const getAudioProcessorUrl = ()=>{
    if (!audioProcessorUrl) {
        const blob = new Blob([
            AUDIO_PROCESSOR_CODE
        ], {
            type: "application/javascript"
        });
        audioProcessorUrl = URL.createObjectURL(blob);
    }
    return audioProcessorUrl;
};
const AUDIO_ENCODER_CODE = `
// Web Worker for audio encoding operations
// Handles Float32 to PCM conversion and base64 encoding off the main thread

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

    try {
        // Convert Float32Array to 16-bit PCM
        const pcmData = new Int16Array(floatData.length);
        for (let i = 0; i < floatData.length; i++) {
            const s = Math.max(-1, Math.min(1, floatData[i]));
            pcmData[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
        }

        // Convert to base64 more efficiently
        const uint8Array = new Uint8Array(pcmData.buffer);
        const chunkSize = 0x8000; // 32KB chunks
        let binary = '';

        // Process in chunks to avoid call stack issues with large buffers
        for (let i = 0; i < uint8Array.length; i += chunkSize) {
            const chunk = uint8Array.subarray(i, Math.min(i + chunkSize, uint8Array.length));
            binary += String.fromCharCode.apply(null, Array.from(chunk));
        }

        const base64Data = btoa(binary);

        // Send the result back to the main thread
        self.postMessage({
            id,
            base64Data,
        });

    } catch (error) {
        // Send error response
        self.postMessage({
            id,
            base64Data: '',
            error: error.message || 'Unknown error',
        });
    }
});
`;
let audioEncoderUrl = null;
const getAudioEncoderUrl = ()=>{
    if (!audioEncoderUrl) {
        const blob = new Blob([
            AUDIO_ENCODER_CODE
        ], {
            type: "application/javascript"
        });
        audioEncoderUrl = URL.createObjectURL(blob);
    }
    return audioEncoderUrl;
};
export { useAudioInput };
