// src/features/voice-assistant/VoiceAssistant.tsx
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { startVoiceSession } from '../../services/aiApiClient';
import { MicrophoneIcon } from '../../components/icons/MicrophoneIcon';
// FIX: Corrected the import path. Types should be imported from the top-level '@google/genai' package.
import type { LiveServerMessage, Blob } from '@google/genai';
import { encode } from '../../utils/audioUtils';
import { logger } from '../../services/logger.client';
import { XMarkIcon } from '../../components/icons/XMarkIcon';

interface VoiceAssistantProps {
  isOpen: boolean;
  onClose: () => void;
}

type VoiceStatus = 'idle' | 'connecting' | 'listening' | 'speaking' | 'error';

// Define a local interface for the session object to provide type safety.
interface LiveSession {
  close: () => void;
  sendRealtimeInput: (input: { media: Blob }) => void;
}

export const VoiceAssistant: React.FC<VoiceAssistantProps> = ({ isOpen, onClose }) => {
    const [status, setStatus] = useState<VoiceStatus>('idle');
    const [userTranscript, setUserTranscript] = useState('');
    const [modelTranscript, setModelTranscript] = useState('');
    const [history, setHistory] = useState<{speaker: 'user' | 'model', text: string}[]>([]);
    
    // Use the local LiveSession interface for the ref type.
    const sessionPromiseRef = useRef<Promise<LiveSession> | null>(null);
    const mediaStreamRef = useRef<MediaStream | null>(null);
    const audioContextRef = useRef<AudioContext | null>(null);
    const scriptProcessorRef = useRef<ScriptProcessorNode | null>(null);
    
    const stopRecording = useCallback(() => {
        if (mediaStreamRef.current) {
            mediaStreamRef.current.getTracks().forEach(track => track.stop());
            mediaStreamRef.current = null;
        }
        if (scriptProcessorRef.current) {
            scriptProcessorRef.current.disconnect();
            scriptProcessorRef.current = null;
        }
        if (audioContextRef.current && audioContextRef.current.state !== 'closed') {
            audioContextRef.current.close();
            audioContextRef.current = null;
        }
    }, []);

    const handleClose = useCallback(() => {
        if (sessionPromiseRef.current) {
            sessionPromiseRef.current.then((session: LiveSession) => session.close());
            sessionPromiseRef.current = null;
        }
        stopRecording();
        setStatus('idle');
        setHistory([]);
        setUserTranscript('');
        setModelTranscript('');
        onClose();
    }, [onClose, stopRecording]);

    const startSession = useCallback(async () => {
        if (status !== 'idle' && status !== 'error') return;
        
        setStatus('connecting');
        setHistory([]);
        setUserTranscript('');
        setModelTranscript('');

        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            mediaStreamRef.current = stream;
            
            const callbacks = {
                onopen: () => {
                    logger.debug('Voice session opened.');
                    setStatus('listening');
                    
                    // Start streaming microphone audio to the model
                    audioContextRef.current = new (window.AudioContext)({ sampleRate: 16000 });
                    const source = audioContextRef.current.createMediaStreamSource(stream);
                    const scriptProcessor = audioContextRef.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: LiveSession) => {
                          session.sendRealtimeInput({ media: pcmBlob });
                        });
                    };
                    source.connect(scriptProcessor);
                    scriptProcessor.connect(audioContextRef.current.destination);
                },
                onmessage: (message: LiveServerMessage) => {
                    // NOTE: This stub doesn't play audio, just displays transcripts.
                    // A full implementation would use the audioUtils to decode and play audio.
                    const serverContent = message.serverContent;
                    if (!serverContent) {
                        return; // Exit if there's no content to process
                    }

                    // Safely access nested properties with optional chaining.
                    if (serverContent.inputTranscription?.text) {
                        setUserTranscript(prev => prev + serverContent.inputTranscription!.text);
                    }
                    if (serverContent.outputTranscription?.text) {
                        setModelTranscript(prev => prev + serverContent.outputTranscription!.text);
                    }
                    if (serverContent.turnComplete) {
                        setHistory(prev => [...prev, 
                            { speaker: 'user', text: userTranscript }, 
                            { speaker: 'model', text: modelTranscript }
                        ]);
                        setUserTranscript('');
                        setModelTranscript('');
                    }
                },
                onerror: (e: ErrorEvent) => {
                    logger.error('Voice session error', { error: e });
                    setStatus('error');
                    stopRecording();
                },
                onclose: () => {
                    logger.debug('Voice session closed.');
                    stopRecording();
                    setStatus('idle');
                },
            };
            
            sessionPromiseRef.current = startVoiceSession(callbacks);

        } catch (e) {
            // We check if the caught object is an instance of Error to safely access its message property.
            // This avoids using 'any' and handles different types of thrown values.
            logger.error("Failed to start voice session", { error: e });
            setStatus('error');
        }

    }, [status, stopRecording, userTranscript, modelTranscript]);


    useEffect(() => {
        // Cleanup on unmount
        return () => {
            handleClose();
        };
    }, [handleClose]);


    if (!isOpen) return null;

    const getStatusText = () => {
        switch (status) {
            case 'idle': return 'Click the mic to start';
            case 'connecting': return 'Connecting...';
            case 'listening': return 'Listening...';
            case 'speaking': return 'Thinking...';
            case 'error': return 'Connection error. Please try again.';
        }
    }

    return (
        <div 
            className="fixed inset-0 bg-black bg-opacity-60 z-50 flex justify-center items-center p-4"
            onClick={handleClose}
        >
            <div 
                className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg relative flex flex-col h-[70vh]"
                onClick={e => e.stopPropagation()}
                role="dialog"
                aria-modal="true"
            >
                <div className="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700">
                    <h2 className="text-xl font-bold text-gray-800 dark:text-white">Voice Assistant</h2>
                    <button onClick={handleClose} className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200" aria-label="Close">
                        <XMarkIcon className="w-6 h-6" />
                    </button>
                </div>

                <div className="grow p-4 overflow-y-auto space-y-4">
                    {history.map((entry, index) => (
                        <div key={index} className={`p-3 rounded-lg max-w-[80%] ${entry.speaker === 'user' ? 'bg-blue-100 dark:bg-blue-900/50 ml-auto' : 'bg-gray-100 dark:bg-gray-700/50'}`}>
                            <p className="text-sm text-gray-800 dark:text-gray-200">{entry.text}</p>
                        </div>
                    ))}
                    {userTranscript && <div className="p-3 rounded-lg max-w-[80%] bg-blue-100 dark:bg-blue-900/50 ml-auto opacity-70"><p className="text-sm text-gray-800 dark:text-gray-200">{userTranscript}</p></div>}
                    {modelTranscript && <div className="p-3 rounded-lg max-w-[80%] bg-gray-100 dark:bg-gray-700/50 opacity-70"><p className="text-sm text-gray-800 dark:text-gray-200">{modelTranscript}</p></div>}
                </div>

                <div className="p-4 border-t border-gray-200 dark:border-gray-700 flex flex-col items-center">
                    <button 
                        onClick={status === 'idle' || status === 'error' ? startSession : handleClose}
                        className={`w-16 h-16 rounded-full flex items-center justify-center transition-colors ${status === 'listening' ? 'bg-red-500 hover:bg-red-600' : 'bg-brand-primary hover:bg-brand-secondary'}`}
                        aria-label={status === 'idle' || status === 'error' ? "Start voice session" : "Stop voice session"}
                    >
                        <MicrophoneIcon className="w-8 h-8 text-white" />
                    </button>
                    <p className="text-sm text-gray-500 dark:text-gray-400 mt-2">{getStatusText()}</p>
                </div>
            </div>
        </div>
    );
};
