/**
 * Voice AI — Gemini 3.1 Flash Live Integration
 * Real-time bidirectional audio via WebSocket
 *
 * Usage:
 *   VoiceAI.init({ apiKey, onTranscript, onResponse, onStateChange });
 *   VoiceAI.toggle(); // start/stop
 */
window.VoiceAI = (function() {
    'use strict';

    let ws = null;
    let mediaStream = null;
    let audioContext = null;
    let processor = null;
    let isListening = false;
    let config = {};
    let audioQueue = [];
    let isPlaying = false;

    const MODEL = 'gemini-3.1-flash-live-preview';
    const WS_URL = 'wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent';

    function init(opts) {
        config = {
            apiKey: opts.apiKey || '',
            systemPrompt: opts.systemPrompt || 'You are a helpful fire department assistant. Keep answers brief and conversational.',
            onTranscript: opts.onTranscript || (() => {}),
            onResponse: opts.onResponse || (() => {}),
            onStateChange: opts.onStateChange || (() => {}),
            onError: opts.onError || ((e) => console.error('VoiceAI:', e)),
        };
    }

    async function start() {
        if (isListening) return;

        try {
            // Get microphone access
            mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
            });

            // Connect WebSocket
            const url = `${WS_URL}?key=${config.apiKey}`;
            ws = new WebSocket(url);

            ws.onopen = () => {
                // Send setup message
                ws.send(JSON.stringify({
                    setup: {
                        model: `models/${MODEL}`,
                        generationConfig: {
                            responseModalities: ['AUDIO', 'TEXT'],
                            speechConfig: {
                                voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Kore' } }
                            }
                        },
                        systemInstruction: {
                            parts: [{ text: config.systemPrompt }]
                        }
                    }
                }));

                isListening = true;
                config.onStateChange('listening');
                startAudioCapture();
            };

            ws.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);

                    // Setup complete
                    if (data.setupComplete) return;

                    // Server content (response)
                    if (data.serverContent) {
                        const parts = data.serverContent.modelTurn?.parts || [];
                        for (const part of parts) {
                            if (part.text) {
                                config.onResponse(part.text);
                            }
                            if (part.inlineData) {
                                playAudio(part.inlineData.data, part.inlineData.mimeType);
                            }
                        }

                        // Check if turn is complete
                        if (data.serverContent.turnComplete) {
                            config.onStateChange('listening');
                        }
                    }
                } catch (e) {
                    // Binary audio data or parse error — skip
                }
            };

            ws.onerror = (e) => {
                config.onError('WebSocket error');
                stop();
            };

            ws.onclose = () => {
                if (isListening) stop();
            };

        } catch (e) {
            config.onError(e.message || 'Microphone access denied');
            stop();
        }
    }

    function startAudioCapture() {
        if (!mediaStream) return;

        audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
        const source = audioContext.createMediaStreamSource(mediaStream);

        // Use ScriptProcessor for audio chunks
        processor = audioContext.createScriptProcessor(4096, 1, 1);
        processor.onaudioprocess = (e) => {
            if (!ws || ws.readyState !== WebSocket.OPEN) return;

            const inputData = e.inputBuffer.getChannelData(0);
            // Convert float32 to int16 PCM
            const pcm = new Int16Array(inputData.length);
            for (let i = 0; i < inputData.length; i++) {
                const s = Math.max(-1, Math.min(1, inputData[i]));
                pcm[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
            }

            // Send audio chunk
            const base64 = btoa(String.fromCharCode(...new Uint8Array(pcm.buffer)));
            ws.send(JSON.stringify({
                realtimeInput: {
                    mediaChunks: [{
                        mimeType: 'audio/pcm;rate=16000',
                        data: base64
                    }]
                }
            }));
        };

        source.connect(processor);
        processor.connect(audioContext.destination);
    }

    function playAudio(base64Data, mimeType) {
        audioQueue.push({ data: base64Data, mime: mimeType });
        if (!isPlaying) playNext();
    }

    async function playNext() {
        if (audioQueue.length === 0) { isPlaying = false; return; }
        isPlaying = true;
        config.onStateChange('speaking');

        const item = audioQueue.shift();
        try {
            const binary = atob(item.data);
            const bytes = new Uint8Array(binary.length);
            for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);

            const playCtx = new (window.AudioContext || window.webkitAudioContext)();
            const buffer = await playCtx.decodeAudioData(bytes.buffer);
            const source = playCtx.createBufferSource();
            source.buffer = buffer;
            source.connect(playCtx.destination);
            source.onended = () => { playCtx.close(); playNext(); };
            source.start();
        } catch (e) {
            // Audio decode failed — try next
            playNext();
        }
    }

    function stop() {
        isListening = false;
        config.onStateChange('idle');

        if (processor) { processor.disconnect(); processor = null; }
        if (audioContext) { audioContext.close().catch(() => {}); audioContext = null; }
        if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; }
        if (ws) { ws.close(); ws = null; }
        audioQueue = [];
        isPlaying = false;
    }

    function toggle() {
        if (isListening) stop(); else start();
    }

    function isActive() { return isListening; }

    return { init, start, stop, toggle, isActive };
})();
