
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { LiveServerMessage, LiveSession, Blob } from '@google/genai';
import { connectLive } from '../services/geminiService';
import { decode, encode, decodeAudioData } from '../utils';
import { Spinner } from '../components/Spinner';

type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';

const LiveConversationModule: React.FC = () => {
    const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
    const [userTranscription, setUserTranscription] = useState('');
    const [modelTranscription, setModelTranscription] = useState('');
    const [history, setHistory] = useState<{ user: string, model: string }[]>([]);

    const sessionRef = useRef<LiveSession | null>(null);
    const inputAudioContextRef = useRef<AudioContext | null>(null);
    const outputAudioContextRef = useRef<AudioContext | null>(null);
    const scriptProcessorRef = useRef<ScriptProcessorNode | null>(null);
    const mediaStreamSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);
    const nextStartTimeRef = useRef<number>(0);
    const audioSourcesRef = useRef<Set<AudioBufferSourceNode>>(new Set());
    const sessionPromiseRef = useRef<Promise<LiveSession> | null>(null);

    const cleanup = useCallback(() => {
        if (sessionRef.current) {
            sessionRef.current.close();
            sessionRef.current = null;
        }
        if (scriptProcessorRef.current) {
            scriptProcessorRef.current.disconnect();
            scriptProcessorRef.current = null;
        }
        if (mediaStreamSourceRef.current) {
            mediaStreamSourceRef.current.disconnect();
            mediaStreamSourceRef.current = null;
        }
        if (inputAudioContextRef.current && inputAudioContextRef.current.state !== 'closed') {
            inputAudioContextRef.current.close();
        }
        if (outputAudioContextRef.current && outputAudioContextRef.current.state !== 'closed') {
            outputAudioContextRef.current.close();
        }
         audioSourcesRef.current.forEach(source => source.stop());
        audioSourcesRef.current.clear();
        nextStartTimeRef.current = 0;
        setConnectionState('disconnected');
    }, []);

    const startConversation = useCallback(async () => {
        setConnectionState('connecting');
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            
            inputAudioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 16000 });
            outputAudioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 24000 });
            
            const callbacks = {
                onopen: () => {
                    setConnectionState('connected');
                    const source = inputAudioContextRef.current!.createMediaStreamSource(stream);
                    mediaStreamSourceRef.current = source;
                    const scriptProcessor = inputAudioContextRef.current!.createScriptProcessor(4096, 1, 1);
                    scriptProcessorRef.current = scriptProcessor;

                    scriptProcessor.onaudioprocess = (audioProcessingEvent) => {
                        const inputData = audioProcessingEvent.inputBuffer.getChannelData(0);
                        const pcmBlob: Blob = {
                            data: encode(new Uint8Array(new Int16Array(inputData.map(x => x * 32768)).buffer)),
                            mimeType: 'audio/pcm;rate=16000',
                        };
                        sessionPromiseRef.current?.then((session) => {
                           session.sendRealtimeInput({ media: pcmBlob });
                        });
                    };
                    source.connect(scriptProcessor);
                    scriptProcessor.connect(inputAudioContextRef.current!.destination);
                },
                onmessage: async (message: LiveServerMessage) => {
                    if (message.serverContent?.inputTranscription) {
                        setUserTranscription(prev => prev + message.serverContent.inputTranscription.text);
                    }
                    if (message.serverContent?.outputTranscription) {
                        setModelTranscription(prev => prev + message.serverContent.outputTranscription.text);
                    }
                    if (message.serverContent?.turnComplete) {
                        setHistory(prev => [...prev, { user: userTranscription, model: modelTranscription }]);
                        setUserTranscription('');
                        setModelTranscription('');
                    }

                    const base64Audio = message.serverContent?.modelTurn?.parts[0]?.inlineData?.data;
                    if (base64Audio && outputAudioContextRef.current) {
                        nextStartTimeRef.current = Math.max(nextStartTimeRef.current, outputAudioContextRef.current.currentTime);
                        const audioBuffer = await decodeAudioData(decode(base64Audio), outputAudioContextRef.current, 24000, 1);
                        const source = outputAudioContextRef.current.createBufferSource();
                        source.buffer = audioBuffer;
                        source.connect(outputAudioContextRef.current.destination);
                        source.addEventListener('ended', () => audioSourcesRef.current.delete(source));
                        source.start(nextStartTimeRef.current);
                        nextStartTimeRef.current += audioBuffer.duration;
                        audioSourcesRef.current.add(source);
                    }
                },
                onerror: (e: ErrorEvent) => {
                    console.error('Live API Error:', e);
                    setConnectionState('error');
                    cleanup();
                },
                onclose: (e: CloseEvent) => {
                    console.log('Live API Closed:', e);
                    cleanup();
                },
            };
            
            sessionPromiseRef.current = connectLive(callbacks);
            sessionRef.current = await sessionPromiseRef.current;

        } catch (error) {
            console.error('Failed to start conversation:', error);
            setConnectionState('error');
        }
    }, [cleanup, userTranscription, modelTranscription]);
    
    useEffect(() => {
        return () => cleanup();
    }, [cleanup]);

    return (
        <div className="flex flex-col h-full w-full max-w-4xl mx-auto">
            <h2 className="text-2xl font-bold text-cyan-300 mb-4 text-center">Live Conversation</h2>
            <div className="flex items-center justify-center mb-4">
                {connectionState === 'disconnected' && <button onClick={startConversation} className="bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded">Start Conversation</button>}
                {connectionState === 'connecting' && <Spinner text="Connecting..." />}
                {connectionState === 'connected' && <button onClick={cleanup} className="bg-red-600 hover:bg-red-500 text-white font-bold py-2 px-4 rounded">End Conversation</button>}
                {connectionState === 'error' && <p className="text-red-400">Connection error. Please try again.</p>}
            </div>
            
            <div className="flex-grow overflow-y-auto p-4 bg-gray-800/50 rounded-lg border border-cyan-500/10 h-[50vh]">
                 {history.map((turn, index) => (
                    <div key={index}>
                        <p className="text-cyan-300"><strong>You:</strong> {turn.user}</p>
                        <p className="text-purple-300 mb-4"><strong>Codex:</strong> {turn.model}</p>
                    </div>
                ))}
                {userTranscription && <p className="text-cyan-300"><strong>You:</strong> {userTranscription}...</p>}
                {modelTranscription && <p className="text-purple-300"><strong>Codex:</strong> {modelTranscription}...</p>}
                 {connectionState === 'connected' && history.length === 0 && !userTranscription && <p className="text-gray-400">Listening... Speak into your microphone.</p>}
            </div>
        </div>
    );
};

export default LiveConversationModule;