/**
 * VMO AI Voice Panel - Jarvis-Style Floating Chat
 * Full voice conversation with AI-powered speech recognition and text-to-speech
 */

class VMOVoicePanel {
    constructor() {
        this.panel = document.getElementById('vmo-ai-voice');
        this.chatHistory = document.getElementById('vmo-voice-chat-history');
        this.statusText = document.getElementById('vmo-voice-status');
        this.collapseBtn = document.getElementById('vmo-voice-collapse');
        this.textBtn = document.getElementById('vmo-voice-text-btn');
        this.micBtn = document.getElementById('vmo-voice-mic-btn');
        this.uploadBtn = document.getElementById('vmo-voice-upload-btn'); // ✅ NEW: Upload button
        // ✅ REMOVED: this.speechBtn (button deleted - was duplicate)
        this.startBtn = document.getElementById('vmo-voice-start-btn');
    this.listeningModal = document.getElementById('vmo-listening-modal');
        this.listeningTranscript = document.getElementById('vmo-listening-transcript');
        this.listeningText = document.querySelector('#vmo-listening-modal .vmo-listening-text');
    // Optional close button inside listening modal
    this.listeningCloseBtn = document.getElementById('vmo-listening-close');
    // Optional settings button in header
    this.settingsBtn = document.getElementById('vmo-voice-settings');
        
        this.isCollapsed = false;
        this.isActive = false;
        this.isListening = false;
    this.isSpeaking = false;
    this.isProcessing = false; // suppresses auto-listen while awaiting TTS
        this._autoRestartEnabled = true;
        this.messages = [];
        
        // Speech Recognition (Web Speech API for listening to user)
        this.recognition = null;
        // Determine a sensible default language from page or browser
        this.defaultLang = (document && document.documentElement && document.documentElement.lang)
            ? document.documentElement.lang
            : (navigator.language || 'en-US');
        this.initSpeechRecognition();
        
        // OpenAI TTS for AI voice responses
        this.currentAudio = null;
        this.isMuted = !!window.vmoMuteTTS;
        
        this.init();

        // Listen for global mic revoke requests
        try {
            window.addEventListener('vmo:mic-revoke', (e) => {
                const from = e?.detail?.from;
                // If this module owns the mic, stop recognition immediately
                if (from === 'voice-panel' || (!from && this.isListening)) {
                    try { this.recognition && this.recognition.stop(); } catch(_) {}
                }
            });
        } catch(_) {}

        // Privacy: allow other modules (consultation) to hard-stop voice completely
        try {
            window.addEventListener('vmo:voice-hard-stop', (e) => {
                const from = e?.detail?.from;
                if (from === 'consultation') {
                    this.endVoiceSession();
                }
            });
        } catch (_) {}
    }
    
    initSpeechRecognition() {
        // Check if browser supports Web Speech API
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        if (SpeechRecognition) {
            this.recognition = new SpeechRecognition();
            // Continuous mode to allow silence-based gating
            this.recognition.continuous = true;
            this.recognition.interimResults = true;
            // Use site/browser language as default
            try {
                this.recognition.lang = this.defaultLang || 'en-US';
            } catch (_) {
                this.recognition.lang = 'en-US';
            }
            this.recognition.maxAlternatives = 1;

            // Silence gating (ms) – Short enough to feel responsive, long enough
            // to not cut off mid-sentence pauses. 2.5s is the sweet spot.
            this.silenceTimeoutMs = 2500;
            this._silenceTimer = null;
            this._transcriptBuffer = '';
            this._interimText = '';
            
            // ✅ SELF-LOOP PREVENTION: Track when TTS started to ignore any speech during playback
            this._ttsStartedAt = 0;
            this._ttsMinDurationMs = 500; // Minimum time to ignore speech after TTS starts
            this._ttsEndCooldownMs = 1500; // Extra cooldown after TTS ends before accepting speech
            this._ttsEndedAt = 0;
            
            this.recognition.onstart = () => {
                console.log('🎤 Speech recognition started');
                this.isListening = true;
                this.updateStatus('Listening...');
                this.showListeningModal('listening');
                // Claim mic ownership
                try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.requestMic('voice-panel'); } catch(_) {}
            };
            
            this.recognition.onresult = (event) => {
                // ✅ CRITICAL SELF-LOOP PREVENTION: Ignore ALL speech while AI is speaking
                // This prevents the AI from hearing its own TTS output
                if (this.isSpeaking || this.isProcessing) {
                    console.log('🔇 Ignoring speech input - AI is speaking or processing');
                    return;
                }
                
                // ✅ COOLDOWN CHECK: Also ignore speech shortly after TTS ends
                // TTS audio may still be resonating/echoing
                if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < this._ttsEndCooldownMs)) {
                    console.log('🔇 Ignoring speech input - TTS cooldown period');
                    return;
                }
                
                // ✅ Check coordinator for global TTS state (with stale-state safety)
                if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking) {
                    // Safety: if no audio is ACTUALLY playing, the flag is stale — reset it
                    const anyAudioPlaying = Array.from(document.querySelectorAll('audio')).some(a => !a.paused && !a.ended);
                    const synthSpeaking = window.speechSynthesis && window.speechSynthesis.speaking;
                    if (!anyAudioPlaying && !synthSpeaking && !this.currentAudio) {
                        console.warn('⚠️ Coordinator isSpeaking was stale — resetting');
                        window.VMOAudioCoordinator.isSpeaking = false;
                        if (window.VMOAudioCoordinator.onTTSStop) window.VMOAudioCoordinator.onTTSStop();
                    } else {
                        console.log('🔇 Ignoring speech input - VMOAudioCoordinator reports TTS active');
                        return;
                    }
                }
                
                // Only handle the current result; interim updates should not be permanently appended
                const res = event.results && event.results[event.resultIndex];
                if (!res) return;
                const t = (res[0] && res[0].transcript) ? res[0].transcript : '';

                // ✅ BARGE-IN: If AI is streaming, abort the stream so user can interrupt
                if (t.trim() && this._streamController) {
                    console.log('🗣️ Barge-in: user spoke while AI was streaming — aborting stream');
                    try { this._streamController.abort(); } catch(_) {}
                }

                if (res.isFinal) {
                    this._transcriptBuffer += t;
                    this._interimText = '';
                } else {
                    this._interimText = t;
                }

                // ✅ LIVE-TO-INPUT: Show transcription directly in the chat input box
                const fullText = (this._transcriptBuffer + (this._interimText || ''));
                this._populateInputBox(fullText);

                // ✅ LIVE TRANSCRIPT: Show user's words in real-time in the listening modal
                // (Same approach as vmo-intelligent-consultation.js uses)
                if (fullText.trim()) {
                    this.updateLiveTranscript(fullText, 'user');
                }

                // Reset silence timer — behavior depends on mode
                if (this._silenceTimer) clearTimeout(this._silenceTimer);
                const delay = res.isFinal ? Math.min(this.silenceTimeoutMs, 2500) : this.silenceTimeoutMs;
                this._silenceTimer = setTimeout(() => {
                    const finalText = (this._transcriptBuffer + (this._interimText || '')).trim();
                    this._transcriptBuffer = '';
                    this._interimText = '';
                    this.clearLiveTranscript();
                    if (finalText) {
                        try { this.recognition.stop(); } catch(_) {}
                        if (this.isActive) {
                            // Voice conversation mode: auto-send (listen → think → respond → listen)
                            this.sendMessage(finalText);
                        } else {
                            // Speech-to-text mode: populate input for user review & manual send
                            this._populateInputBox(finalText);
                            this.hideListeningModal();
                            this.updateStatus('Ready — review & send');
                        }
                    }
                }, delay);
            };
            
            this.recognition.onerror = (event) => {
                console.error('Speech recognition error:', event.error);
                this.isListening = false;
                // Keep modal visible during active voice session to avoid flicker
                if (!this.isActive) this.hideListeningModal();
                if (event.error === 'no-speech') {
                    // Friendly retry for no-speech timeouts
                    this.updateStatus('Didn\'t catch that. Listening…');
                    if (this.isActive && this._autoRestartEnabled && !this.isSpeaking && !this.isProcessing) {
                        setTimeout(() => this.startVoiceInput(), 500);
                    }
                } else if (event.error === 'aborted') {
                    // Ignorable (often happens on quick restarts)
                    if (this.isActive && this._autoRestartEnabled && !this.isSpeaking && !this.isProcessing) {
                        setTimeout(() => this.startVoiceInput(), 300);
                    }
                } else if (event.error === 'not-allowed' || event.error === 'service-not-allowed') {
                    this.updateStatus('Mic permission blocked. Please allow microphone access.');
                    alert('Microphone permission is blocked. Please allow mic access in your browser settings and reload.');
                } else {
                    this.updateStatus('Error: ' + event.error);
                    if (this.isActive && this._autoRestartEnabled && !this.isSpeaking && !this.isProcessing) {
                        setTimeout(() => this.startVoiceInput(), 800);
                    }
                }
            };
            
            this.recognition.onend = () => {
                console.log('🎤 Speech recognition ended');
                this.isListening = false;
                // Keep modal visible during active voice session so UI remains stable
                if (!this.isActive) this.hideListeningModal();
                // Release mic ownership
                try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic('voice-panel'); } catch(_) {}
                // Auto-restart if session active and we aren't speaking
                if (this.isActive && this._autoRestartEnabled && !this.isSpeaking && !this.isProcessing) {
                    setTimeout(() => this.startVoiceInput(), 300);
                }
            };

            // Diagnostics: track audio lifecycle
            this.recognition.onaudiostart = () => console.log('🎤 Audio capturing started');
            this.recognition.onaudioend = () => console.log('🎤 Audio capturing ended');
            this.recognition.onsoundstart = () => console.log('🎤 Sound detected');
            this.recognition.onsoundend = () => console.log('🎤 Sound ended');
            
            console.log('✅ Speech recognition initialized');
        } else {
            console.warn('⚠️ Web Speech API not supported in this browser');
        }
    }

    // Warm up the microphone to trigger permission prompt and stabilize input
    async micWarmup() {
        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) return;
        try {
            const stream = await navigator.mediaDevices.getUserMedia({
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                }
            });
            // Immediately stop tracks; this is only to unlock permissions and wake audio path
            setTimeout(() => stream.getTracks().forEach(t => t.stop()), 100);
        } catch (err) {
            console.warn('Mic warmup failed or permission denied:', err);
        }
    }
    
    async init() {
        console.log('🎤 VMO Voice Panel: Initializing...');
        
        // Set up event listeners
        this.collapseBtn?.addEventListener('click', () => this.toggleCollapse());
        this.textBtn?.addEventListener('click', () => this.activateTextChat());
        this.micBtn?.addEventListener('click', () => this.startVoiceInput());
        this.uploadBtn?.addEventListener('click', () => this.openFileUpload()); // ✅ NEW: Upload button handler
        // ✅ REMOVED: this.speechBtn (button deleted - was duplicate)
        this.startBtn?.addEventListener('click', () => this.startVoiceSession());
        // Close button inside listening modal ends the appropriate session
        this.listeningCloseBtn?.addEventListener('click', (e) => {
            e.stopPropagation();
            const owner = (this.listeningModal && this.listeningModal.dataset && this.listeningModal.dataset.owner) || 'voice-panel';
            if (owner === 'consultation') {
                // Consultation will handle this in its own handler; avoid double-processing
                return;
            }
            this.endVoiceSession();
        });
        // Settings button opens voice selection popover
        this.settingsBtn?.addEventListener('click', (e) => {
            e.stopPropagation();
            this.openSettingsPopover();
        });
        // Enable dragging the panel by its header
        this.enableDrag();
        // Load saved preferred Jarvis voice
        try {
            const saved = localStorage.getItem('vmoJarvisVoice');
            if (saved) window.vmoSelectedVoice = saved;
            else if (!window.vmoSelectedVoice) window.vmoSelectedVoice = 'ash';
        } catch(_) { if (!window.vmoSelectedVoice) window.vmoSelectedVoice = 'ash'; }
        
        // Load chat history from database first, then show greeting if first time
        await this.loadChatHistory();
        
        // Enable buttons
    if (this.textBtn) this.textBtn.disabled = false;
    if (this.micBtn) this.micBtn.disabled = false;
    if (this.startBtn) this.startBtn.disabled = false;
        
        console.log('✅ VMO Voice Panel: Ready');
        // Mute toggle (Jarvis only; consultation intercepts when it owns the modal)
        // ✅ FIX: Mute now explicitly controls the Microphone (User Input)
        this.muteBtn?.addEventListener('click', (e) => {
            e.stopPropagation();
            const owner = (this.listeningModal && this.listeningModal.dataset && this.listeningModal.dataset.owner) || 'voice-panel';
            if (owner === 'consultation') return; // consultation owns this click
            
            // Toggle global mic mute state
            window.vmoMicMuted = !window.vmoMicMuted;
            this.isMutes = window.vmoMicMuted; // Keep local state in sync
            
            this.applyMute();
            
            if (window.vmoMicMuted) {
                console.log('🔇 Jarvis Mute: Microphone muted by user');
                this.updateStatus('Microphone Muted');
                try { if (this.recognition) this.recognition.stop(); } catch(_) {}
            } else {
                console.log('🔊 Jarvis Unmute: Microphone unmuted by user');
                this.updateStatus('Microphone Active');
                // Don't auto-start immediately; let the user click Speak or rely on restart logic if active
                if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                    this.startVoiceInput(false);
                }
            }
        });
        
        // ═══════════════════════════════════════════════════════════════════════════════
        // ✅ BARGE-IN ZONE: Clicking the circular animation or modal body stops AI speech
        // This gives users a large target to tap/click to interrupt
        // ═══════════════════════════════════════════════════════════════════════════════
        const modalContent = this.listeningModal?.querySelector('.vmo-listening-content');
        if (modalContent) {
            modalContent.addEventListener('click', (e) => {
                // Don't trigger on button clicks (they have their own handlers)
                if (e.target.tagName === 'BUTTON') return;
                
                // Only trigger if AI is speaking
                if (this.isSpeaking) {
                    console.log('🔊 BARGE-IN via modal tap');
                    this.stopAllSpeaking();
                    this.startVoiceInput(true); // Manual barge-in
                }
            });
        }
        
        // ✅ KEYBOARD BARGE-IN: Press Escape to stop AI speech and start listening
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape' && this.isActive && this.isSpeaking) {
                console.log('🔊 BARGE-IN via Escape key');
                e.preventDefault();
                e.stopPropagation();
                this.stopAllSpeaking();
                this.startVoiceInput(true); // Manual barge-in
            }
        });
        
        // ✅ GLOBAL BARGE-IN EVENT: Other code can trigger this
        window.addEventListener('vmo:voice-barge-in', () => {
            if (this.isSpeaking) {
                console.log('🔊 BARGE-IN via global event');
                this.stopAllSpeaking();
                this.startVoiceInput(true);
            }
        });
    }
    
    /**
     * ═══════════════════════════════════════════════════════════════════════════════
     * FORCE-STOP ALL SPEAKING - Guaranteed barge-in support
     * This method immediately stops all audio output from any source
     * ═══════════════════════════════════════════════════════════════════════════════
     */
    stopAllSpeaking() {
        console.log('🛑 FORCE STOPPING ALL AUDIO');
        
        // 1. Stop our own audio element
        if (this.currentAudio) {
            try { 
                this.currentAudio.pause(); 
                this.currentAudio.currentTime = 0;
                this.currentAudio.src = '';
            } catch(_) {}
            this.currentAudio = null;
        }
        
        // 2. Stop browser speech synthesis
        try {
            if (window.speechSynthesis) {
                window.speechSynthesis.cancel();
            }
        } catch(_) {}
        
        // 3. Stop any global audio via coordinator
        try {
            if (window.VMOAudioCoordinator) {
                if (window.VMOAudioCoordinator.stopAllTTS) {
                    window.VMOAudioCoordinator.stopAllTTS('barge-in', 'voice-panel');
                }
                if (window.VMOAudioCoordinator.currentAudio) {
                    try { window.VMOAudioCoordinator.currentAudio.pause(); } catch(_) {}
                }
                window.VMOAudioCoordinator.isSpeaking = false;
                if (window.VMOAudioCoordinator.onTTSStop) {
                    window.VMOAudioCoordinator.onTTSStop();
                }
            }
        } catch(_) {}
        
        // 4. Stop global vmoCurrentAudio
        try {
            if (window.vmoCurrentAudio) {
                window.vmoCurrentAudio.pause();
                window.vmoCurrentAudio = null;
            }
        } catch(_) {}
        
        // 5. Nuclear option: pause ALL audio elements on page
        try {
            document.querySelectorAll('audio').forEach(audio => {
                try { audio.pause(); } catch(_) {}
            });
        } catch(_) {}
        
        // Reset state
        this.isSpeaking = false;
        this.isProcessing = false;
        this._ttsEndedAt = Date.now();
        this.clearLiveTranscript();
        this.updateStatus('Ready');
        if (this.isActive) this.setModalPhase('listening');
    }

    applyMute() {
        // Sync button label/state (Jarvis context)
        if (this.muteBtn) {
            this.muteBtn.textContent = window.vmoMicMuted ? 'Unmute Mic' : 'Mute Mic';
            // Optional: visual indication
            this.muteBtn.style.color = window.vmoMicMuted ? '#ff4444' : '#00ffff';
            this.muteBtn.style.borderColor = window.vmoMicMuted ? '#ff4444' : '#00ffff';
        }
        
        // Notify listeners (no owner tag here; consultation ignores these)
        try { window.dispatchEvent(new CustomEvent('vmo:mute-changed', { detail: { muted: !!window.vmoMicMuted } })); } catch(_) {}
    }
    
    toggleCollapse() {
        this.isCollapsed = !this.isCollapsed;
        this.panel?.classList.toggle('collapsed');
        this.collapseBtn.textContent = this.isCollapsed ? '+' : '−';
    }
    
    async loadChatHistory() {
        console.log('📚 Loading chat history from database...');
        
        try {
            const response = await fetch(vmo_ajax.ajaxurl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams({
                    action: 'vmo_load_chat_history',
                    chat_type: 'jarvis', // Only load Jarvis conversations
                    security: vmo_ajax.nonce
                })
            });
            
            const data = await response.json();
            
            if (data.success && data.data.messages) {
                const messages = data.data.messages;
                console.log(`✅ Loaded ${messages.length} messages from database`);
                
                // Clear any existing messages
                this.messages = [];
                
                // Add all messages to chat history
                messages.forEach(msg => {
                    const sender = msg.role === 'user' ? 'user' : 'ai';
                    this.addMessage(msg.content || '', sender, false); // false = don't save to DB (already there)
                    // Normalize internal message shape to { text, sender }
                    if (typeof msg.content === 'string' && msg.content.trim() !== '') {
                        this.messages.push({
                            text: msg.content,
                            sender: sender,
                            time: new Date()
                        });
                    }
                });
                
                // If no messages, show greeting
                if (messages.length === 0) {
                    await this.loadGreeting();
                }
                
                this.updateStatus('Ready');
            } else {
                console.error('Failed to load chat history:', data);
                await this.loadGreeting(); // Fallback to greeting
            }
        } catch (error) {
            console.error('Error loading chat history:', error);
            await this.loadGreeting(); // Fallback to greeting
        }
    }
    
    async loadGreeting() {
        // Just show a simple greeting without making an API call
        this.addMessage('Hello! I\'m VMO, your AI business assistant. Click the text button to start chatting, or use voice input!', 'ai', false);
        this.updateStatus('Ready');
    }
    
    activateTextChat() {
        // Focus on the text input area if it exists, or create one
        let textInput = document.getElementById('vmo-voice-text-input');
        
        if (!textInput) {
            // Create text input area dynamically
            const inputContainer = document.createElement('div');
            inputContainer.className = 'vmo-voice-text-input-container';
            inputContainer.innerHTML = `
                <textarea 
                    id="vmo-voice-text-input" 
                    class="vmo-voice-text-input" 
                    placeholder="Type your message to VMO..."
                    rows="3"
                ></textarea>
                <button id="vmo-voice-send-text" class="vmo-voice-send-btn" title="Send Message">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
                    </svg>
                </button>
            `;
            
            // Insert before the input controls
            const inputControls = document.querySelector('.vmo-voice-input-controls');
            if (inputControls) {
                inputControls.parentNode.insertBefore(inputContainer, inputControls);
            }
            
            textInput = document.getElementById('vmo-voice-text-input');
            const sendBtn = document.getElementById('vmo-voice-send-text');
            
            // Bind send button
            sendBtn?.addEventListener('click', () => {
                const message = textInput.value.trim();
                if (message) {
                    this.sendMessage(message);
                    textInput.value = '';
                }
            });
            
            // Bind Enter key (Shift+Enter for new line)
            textInput?.addEventListener('keypress', (e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    const message = textInput.value.trim();
                    if (message) {
                        this.sendMessage(message);
                        textInput.value = '';
                    }
                }
            });
        }
        
        // Focus the input
        textInput?.focus();
    }
    
    /**
     * ✅ Populate the Jarvis chat input box with transcribed text.
     * Creates the input if it doesn't exist, updates it live during dictation.
     */
    _populateInputBox(text) {
        let textInput = document.getElementById('vmo-voice-text-input');
        if (!textInput) {
            this.activateTextChat();
            textInput = document.getElementById('vmo-voice-text-input');
        }
        if (textInput) {
            textInput.value = text;
            textInput.focus();
            // Auto-resize: scroll to bottom of textarea so latest text is visible
            textInput.scrollTop = textInput.scrollHeight;
        }
    }
    
    // ✅ NEW: Open file upload dialog for Jarvis AI chat
    openFileUpload() {
        console.log('📎 Opening file upload for Jarvis AI chat...');
        
        // Create a hidden file input if it doesn't exist
        let fileInput = document.getElementById('vmo-voice-file-input');
        if (!fileInput) {
            fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.id = 'vmo-voice-file-input';
            fileInput.accept = 'image/*,.pdf,.doc,.docx,.txt';
            fileInput.multiple = true;
            fileInput.style.display = 'none';
            document.body.appendChild(fileInput);
            
            // Handle file selection
            fileInput.addEventListener('change', async (e) => {
                const files = Array.from(e.target.files);
                if (files.length === 0) return;
                
                this.updateStatus(`Uploading ${files.length} file(s)...`);
                
                try {
                    // Upload each file
                    for (const file of files) {
                        await this.uploadFile(file);
                    }
                    this.updateStatus('Files uploaded successfully!');
                    this.addMessage(`📎 Uploaded ${files.length} file(s). You can now reference them in your message.`, 'system', false);
                } catch (error) {
                    console.error('File upload error:', error);
                    this.updateStatus('File upload failed');
                    this.addMessage('⚠️ File upload failed. Please try again.', 'system', false);
                }
                
                // Clear the input for next use
                fileInput.value = '';
            });
        }
        
        // Trigger file picker
        fileInput.click();
    }
    
    // ✅ NEW: Upload a file to the server
    async uploadFile(file) {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('action', 'vmo_upload_consultation_media');
        
        // Get nonce from various sources
        const nonce = window.vmo_builder_nonce || 
                      window.vmoBuilderNonce || 
                      (window.vmo_ajax && window.vmo_ajax.nonce) ||
                      (window.vmoAjax && window.vmoAjax.nonce) || '';
        formData.append('security', nonce);
        
        // Get ajax URL
        const ajaxUrl = (window.vmo_ajax && window.vmo_ajax.ajaxurl) || 
                        (window.vmoAjax && window.vmoAjax.url) ||
                        (window.ajaxurl) ||
                        '/wp-admin/admin-ajax.php';
        
        const response = await fetch(ajaxUrl, {
            method: 'POST',
            body: formData
        });
        
        const result = await response.json();
        
        if (result.success && result.data) {
            console.log('✅ File uploaded:', result.data.filename);
            
            // Store the uploaded file info
            if (!this.uploadedFiles) this.uploadedFiles = [];
            this.uploadedFiles.push({
                url: result.data.url,
                filename: result.data.filename,
                type: file.type
            });
            
            return result.data;
        } else {
            throw new Error(result.data?.message || 'Upload failed');
        }
    }
    
    async sendMessage(text) {
        // Add user message to chat
        this.addMessage(text, 'user');
        
    // Update status
        this.updateStatus('VMO is thinking...');
        // If in voice session, reflect phase in modal
    if (this.isActive) this.setModalPhase('thinking');
    // Prevent premature auto-listen while we wait for AI + TTS
    this.isProcessing = true;

        // ✅ SAFETY WATCHDOG: Auto-reset isProcessing after 30s to prevent permanent freeze
        const processingWatchdog = setTimeout(() => {
            if (this.isProcessing) {
                console.warn('⚠️ Processing watchdog fired — resetting isProcessing after 30s');
                this.isProcessing = false;
                this.updateStatus('Request timed out');
                this.addMessage('Sorry, the request took too long. Please try again.', 'ai');
                if (this.isActive && !this.isSpeaking) {
                    setTimeout(() => this.startVoiceInput(false), 500);
                }
            }
        }, 30000);
        
        try {
            // Build conversation history for context (normalize shapes and filter out empties)
            const chatHistory = this.messages
                .map(msg => {
                    const content = typeof msg.text === 'string' ? msg.text : (typeof msg.content === 'string' ? msg.content : '');
                    const role = (msg.sender === 'user' || msg.role === 'user') ? 'user' : 'assistant';
                    if (!content || typeof content !== 'string' || content.trim() === '') return null;
                    return { role, content: content.trim() };
                })
                .filter(Boolean);
            
            // ✅ STREAMING + INTERRUPTION: Use AbortController for both timeout and barge-in
            if (this._streamController) { try { this._streamController.abort(); } catch(_){} }
            this._streamController = new AbortController();
            const fetchTimeout = setTimeout(() => {
                if (this._streamController) this._streamController.abort();
            }, 60000); // 60s timeout for streaming

            const params = new URLSearchParams();
            params.set('action', 'vmo_conversation_chat');
            const owner = (this.listeningModal && this.listeningModal.dataset && this.listeningModal.dataset.owner) || '';
            const chatType = owner === 'consultation' ? 'consultant' : 'jarvis';
            params.set('chat_type', chatType);
            const sid = (typeof window !== 'undefined' && window.vmoCurrentConsultationSessionId) ? String(window.vmoCurrentConsultationSessionId) : '';
            if (sid) params.set('session_id', sid);
            try {
                const lang = (this.recognition && this.recognition.lang) ? this.recognition.lang : (this.defaultLang || navigator.language || '');
                if (lang) params.set('lang', lang);
            } catch (_) {}
            params.set('chat', JSON.stringify(chatHistory));
            params.set('security', vmo_ajax.nonce);
            params.set('stream', '1'); // ✅ Request SSE streaming

            const response = await fetch(vmo_ajax.ajaxurl, {
                method: 'POST',
                signal: this._streamController.signal,
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: params
            });
            
            clearTimeout(fetchTimeout);

            const contentType = response.headers.get('Content-Type') || '';

            if (contentType.includes('text/event-stream')) {
                // ══ STREAMING PATH ══
                // Create placeholder AI message bubble and fill it token-by-token
                const msgEl = this._addStreamBubble();
                let fullText = '';
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let sseBuffer = '';

                try {
                    while (true) {
                        const { done, value } = await reader.read();
                        if (done) break;
                        sseBuffer += decoder.decode(value, { stream: true });
                        const lines = sseBuffer.split('\n');
                        sseBuffer = lines.pop(); // keep incomplete line
                        for (const line of lines) {
                            const trimmed = line.trim();
                            if (!trimmed || !trimmed.startsWith('data: ')) continue;
                            try {
                                const evt = JSON.parse(trimmed.substring(6));
                                if (evt.t) {
                                    fullText += evt.t;
                                    if (msgEl) msgEl.textContent = fullText;
                                    if (this.chatHistory) this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
                                } else if (evt.error) {
                                    throw new Error(evt.error);
                                }
                                // evt.done handled after loop
                            } catch (pe) {
                                if (pe.message && !pe.message.includes('JSON')) throw pe;
                            }
                        }
                    }
                } catch (streamErr) {
                    if (streamErr.name === 'AbortError') {
                        // Interrupted by user barge-in — keep partial text
                        console.log('🔇 Stream interrupted by user (barge-in)');
                        this._partialAIResponse = fullText;
                    } else {
                        throw streamErr;
                    }
                }

                if (fullText) {
                    this.messages.push({ text: fullText, sender: 'ai', time: new Date() });
                }
                if (this.isActive && fullText) {
                    if (this.recognition && this.isListening) {
                        try { this.recognition.stop(); this.isListening = false; } catch(_) {}
                    }
                    await this.speakWithOpenAI(fullText);
                } else {
                    this.updateStatus('Ready');
                    this.isProcessing = false;
                }
            } else {
                // ══ FALLBACK: non-streaming JSON response ══
                const data = await response.json();
                if (data.success && data.data && data.data.response) {
                    this.addMessage(data.data.response, 'ai');
                    if (this.isActive) {
                        if (this.recognition && this.isListening) {
                            try { this.recognition.stop(); this.isListening = false; } catch(_) {}
                        }
                        await this.speakWithOpenAI(data.data.response);
                    } else {
                        this.updateStatus('Ready');
                        this.isProcessing = false;
                    }
                } else {
                    let errorMsg = 'Sorry, ';
                    if (data.message) errorMsg += data.message;
                    else if (data.data && data.data.message) errorMsg += data.data.message;
                    else errorMsg += 'the AI service returned an error.';
                    this.addMessage(errorMsg, 'ai');
                    this.updateStatus('Error');
                    this.isProcessing = false;
                    if (this.isActive) {
                        setTimeout(() => {
                            if (this.isActive && !this.isSpeaking && !this.isProcessing) this.startVoiceInput(false);
                        }, 1500);
                    }
                }
            }
        } catch (error) {
            console.error('Failed to send message:', error);
            const isTimeout = error.name === 'AbortError';
            if (isTimeout && this._partialAIResponse) {
                // Barge-in: partial response already rendered, don't show error
                this.isProcessing = false;
            } else {
                this.addMessage(isTimeout ? 'Sorry, the request timed out. Please try again.' : 'Sorry, I couldn\'t process that. Please check your connection.', 'ai');
                this.updateStatus(isTimeout ? 'Request timed out' : 'Connection error');
                this.isProcessing = false;
            }
            if (this.isActive) {
                setTimeout(() => {
                    if (this.isActive && !this.isSpeaking && !this.isProcessing) this.startVoiceInput(false);
                }, 1500);
            }
        } finally {
            clearTimeout(processingWatchdog);
            this._streamController = null;
        }
    }
    
    async startVoiceInput(isManualBargeIn = true) {
        // ✅ MUTE GUARD: If Muted, BLOCK all auto-restarts immediately.
        // This ensures the AI doesn't start listening again after it finishes speaking if the user is muted.
        if (window.vmoMicMuted && !isManualBargeIn) {
            console.log('🔇 Auto-restart blocked - Microphone is muted');
            return;
        }

        if (!this.recognition) {
            alert('Speech recognition is not supported in your browser. Please use Chrome, Edge, or Safari.');
            return;
        }
        
        // ✅ SELF-LOOP PREVENTION: Only block AUTOMATIC restarts, not manual barge-in
        // Manual barge-in (user clicking mic) should ALWAYS work
        if (!isManualBargeIn) {
            // This is an automatic restart after TTS - apply cooldown checks
            if (this.isSpeaking) {
                console.log('🔇 Auto-restart blocked - AI is speaking');
                return;
            }
            
            if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < (this._ttsEndCooldownMs || 1500))) {
                console.log('🔇 Auto-restart blocked - TTS cooldown period');
                return;
            }
            
            if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking) {
                console.log('🔇 Auto-restart blocked - VMOAudioCoordinator reports TTS active');
                return;
            }
        }
        
        if (this.isListening) {
            // Stop listening
            this.recognition.stop();
            this.isListening = false;
            this.hideListeningModal();
            this.updateStatus('Stopped listening');
        } else {
            // ═══════════════════════════════════════════════════════════════════════════════
            // ✅ GUARANTEED BARGE-IN: Force-stop ALL audio immediately
            // This ensures the user can ALWAYS interrupt the AI
            // ═══════════════════════════════════════════════════════════════════════════════
            console.log('🔊 BARGE-IN: Forcing stop of all audio');
            
            // 1. Stop our own audio element
            if (this.currentAudio) {
                try { 
                    this.currentAudio.pause(); 
                    this.currentAudio.currentTime = 0;
                    this.currentAudio.src = '';
                } catch(_) {}
                this.currentAudio = null;
            }
            
            // 2. Stop browser speech synthesis (fallback TTS)
            try {
                if (window.speechSynthesis) {
                    window.speechSynthesis.cancel();
                }
            } catch(_) {}
            
            // 3. Stop any global audio via coordinator
            try {
                if (window.VMOAudioCoordinator) {
                    if (window.VMOAudioCoordinator.stopAllTTS) {
                        window.VMOAudioCoordinator.stopAllTTS('barge-in', 'voice-panel');
                    }
                    // Also try to stop any audio elements it tracks
                    if (window.VMOAudioCoordinator.currentAudio) {
                        try { window.VMOAudioCoordinator.currentAudio.pause(); } catch(_) {}
                    }
                }
            } catch(_) {}
            
            // 4. Stop global vmoCurrentAudio if exists
            try {
                if (window.vmoCurrentAudio) {
                    window.vmoCurrentAudio.pause();
                    window.vmoCurrentAudio = null;
                }
            } catch(_) {}
            
            // 5. Force all audio elements on page to pause (nuclear option)
            try {
                document.querySelectorAll('audio').forEach(audio => {
                    try { audio.pause(); } catch(_) {}
                });
            } catch(_) {}
            
            // Clear state
            this.isSpeaking = false;
            this.isProcessing = false;
            this._ttsEndedAt = Date.now();
            this.clearLiveTranscript();
            
            // ═══════════════════════════════════════════════════════════════════════════════
            // Now request mic - but DON'T let coordinator block us on barge-in
            // ═══════════════════════════════════════════════════════════════════════════════
            if (window.VMOAudioCoordinator) {
                // Force-request mic with barge-in flag
                try {
                    // First release any existing mic claims
                    window.VMOAudioCoordinator.releaseMic('voice-panel');
                    window.VMOAudioCoordinator.releaseMic('consultation');
                } catch(_) {}
                
                // Mark coordinator as not speaking (we just stopped everything)
                try {
                    if (window.VMOAudioCoordinator.onTTSStop) {
                        window.VMOAudioCoordinator.onTTSStop();
                    }
                    window.VMOAudioCoordinator.isSpeaking = false;
                } catch(_) {}
                
                // Now request mic - should always succeed after clearing state
                const ok = window.VMOAudioCoordinator.requestMic('voice-panel', { bargeIn: true });
                if (!ok) {
                    // If still blocked, just proceed anyway for barge-in
                    console.warn('⚠️ Coordinator blocked mic request, but proceeding with barge-in anyway');
                }
            }
            
            // Start listening
            try {
                await this.micWarmup();
                this.recognition.start();
            } catch (error) {
                console.error('Failed to start recognition:', error);
                this.updateStatus('Error starting voice input');
                try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic('voice-panel'); } catch(_) {}
            }
        }
    }
    
    processSpeechInput(transcript) {
        console.log('📝 Transcript:', transcript);
        
        // ✅ RACE CONDITION FIX: Immediately flag processing to prevent double-submissions
        if (this.isActive && this.isProcessing) return;

        // ✅ SELF-LOOP PREVENTION: Don't process speech if AI is speaking or processing
        if (this.isSpeaking || this.isProcessing) {
            console.log('🔇 Ignoring speech input in processSpeechInput - AI is speaking or processing');
            return;
        }
        
        // ✅ COOLDOWN CHECK: Don't process speech too soon after TTS ends
        if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < (this._ttsEndCooldownMs || 1500))) {
            console.log('🔇 Ignoring speech input in processSpeechInput - TTS cooldown period');
            return;
        }
        
        // Keep modal visible during active voice session to avoid flicker
        if (!this.isActive) this.hideListeningModal();
        
        if (transcript.trim()) {
            // ✅ ALWAYS populate the input box — user reviews, edits, and clicks Send
            this._populateInputBox(transcript.trim());
        }
    }
    
    async speakWithOpenAI(text) {
        // Stop any current audio
        if (this.currentAudio) {
            try { this.currentAudio.pause(); } catch(_) {}
            this.currentAudio = null;
        }
        
        // ✅ CRITICAL SELF-LOOP PREVENTION: Force-stop recognition before TTS
        // Also abort any pending recognition to ensure clean state
        if (this.recognition) {
            try {
                this.recognition.abort(); // More aggressive than stop()
                this.isListening = false;
            } catch(_) {}
            try {
                this.recognition.stop();
                this.isListening = false;
            } catch(_) {}
        }
        
        // Keep modal visible during active voice session (speaking phase follows)
        if (!this.isActive) this.hideListeningModal();
        
        // Clear any pending silence timers and transcript buffers
        if (this._silenceTimer) {
            clearTimeout(this._silenceTimer);
            this._silenceTimer = null;
        }
        this._transcriptBuffer = '';
        this._interimText = '';
        
        // ✅ SELF-LOOP PREVENTION: Mark TTS start time
        this._ttsStartedAt = Date.now();
        this._ttsEndedAt = 0; // Reset end time
        
    this.isSpeaking = true;
    // Transition guard complete: moving into Speaking
    this.isProcessing = false;
    this.updateStatus('VMO is speaking...');
    // Update modal to Speaking phase
    if (this.isActive) this.setModalPhase('speaking');

    // ✅ SAFETY WATCHDOG: Force-reset isSpeaking after 60s max
    // Prevents permanent freeze if audio never fires onended/onerror
    if (this._speakingWatchdog) clearTimeout(this._speakingWatchdog);
    this._speakingWatchdog = setTimeout(() => {
        if (this.isSpeaking) {
            console.warn('⚠️ Speaking watchdog fired — resetting isSpeaking after 60s');
            this.isSpeaking = false;
            this._ttsEndedAt = Date.now();
            this.clearLiveTranscript();
            try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); } catch(_) {}
            this.updateStatus('Ready');
            if (this.isActive) {
                this.setModalPhase('listening');
                setTimeout(() => {
                    if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                        this.startVoiceInput(false);
                    }
                }, 500);
            }
        }
    }, 60000);
    
        // ✅ LIVE TRANSCRIPT: Show AI text while speaking (will be cleared when done)
        // Show first ~100 chars with ellipsis if longer
        const displayText = text.length > 100 ? text.substring(0, 100) + '...' : text;
        this.updateLiveTranscript(displayText, 'ai');
        this._currentSpeakingText = text; // Store full text for progressive reveal
        
        // Notify coordinator that TTS is starting
        try { 
            window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStart('voice-panel'); 
        } catch(_) {}
        
        try {
            // ✅ DOUBLE-CHECK: Ensure we are ABSOLUTELY not listening while speaking
            if (this.recognition) {
                try { this.recognition.abort(); } catch(_) {}
                try { this.recognition.stop(); } catch(_) {}
                this.isListening = false;
            }
            if (this._silenceTimer) { try { clearTimeout(this._silenceTimer); } catch(_) {} this._silenceTimer = null; }

            // Ensure the TTS client is available; retry briefly if still loading
            const ensureTts = () => new Promise((resolve, reject) => {
                if (typeof window.vmoSpeak === 'function') return resolve();
                const start = Date.now();
                const timer = setInterval(() => {
                    if (typeof window.vmoSpeak === 'function') {
                        clearInterval(timer);
                        resolve();
                    } else if (Date.now() - start > 3000) { // wait up to 3s
                        clearInterval(timer);
                        reject(new Error('TTS client not available'));
                    }
                }, 150);
            });

            await ensureTts();

            if (typeof window.vmoSpeak !== 'function') {
                throw new Error('TTS system not loaded');
            }

            // Use the REST API TTS
            const voice = window.vmoSelectedVoice || 'ash';
            this.currentAudio = await window.vmoSpeak(text, voice);
            // Apply global mute preference
            if (this.currentAudio) {
                try {
                    this.currentAudio.muted = !!window.vmoMuteTTS;
                    this.currentAudio.volume = window.vmoMuteTTS ? 0 : 1;
                } catch(_) {}
            }

            if (!this.currentAudio) {
                throw new Error('TTS failed to generate audio');
            }

            // Set up event handlers
            this.currentAudio.onended = () => {
                console.log('🔊 TTS playback ended');
                this.isSpeaking = false;
                if (this._speakingWatchdog) { clearTimeout(this._speakingWatchdog); this._speakingWatchdog = null; }
                
                // ✅ SELF-LOOP PREVENTION: Mark TTS end time for cooldown
                this._ttsEndedAt = Date.now();
                
                // ✅ LIVE TRANSCRIPT: Clear when AI finishes speaking
                this.clearLiveTranscript();
                this._currentSpeakingText = null;
                
                // Notify coordinator that TTS has stopped
                try { 
                    window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); 
                } catch(_) {}
                
                this.updateStatus('Ready');
                if (this.isActive) this.setModalPhase('listening');

                // If voice session is active, automatically start listening again
                // ✅ INCREASED DELAY: Wait longer before restarting to ensure TTS audio has fully stopped
                if (this.isActive) {
                    const cooldownDelay = this._ttsEndCooldownMs || 1500;
                    setTimeout(() => {
                        // Double-check we're still in active mode and not speaking
                        if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                            console.log('🎤 Auto-restarting voice input after TTS (with cooldown)');
                            this.startVoiceInput(false); // false = automatic restart, not manual barge-in
                        }
                    }, cooldownDelay);
                }
            };

            this.currentAudio.onerror = (error) => {
                console.error('❌ Audio playback error:', error);
                this.isSpeaking = false;
                if (this._speakingWatchdog) { clearTimeout(this._speakingWatchdog); this._speakingWatchdog = null; }
                
                // ✅ SELF-LOOP PREVENTION: Mark TTS end time for cooldown
                this._ttsEndedAt = Date.now();
                
                // ✅ LIVE TRANSCRIPT: Clear on error too
                this.clearLiveTranscript();
                this._currentSpeakingText = null;
                
                // Notify coordinator that TTS has stopped
                try { 
                    window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); 
                } catch(_) {}
                
                this.updateStatus('Audio error');
                if (this.isActive) this.setModalPhase('listening');
                
                // Still resume listening on error if in active mode
                // ✅ INCREASED DELAY: Use cooldown period
                if (this.isActive) {
                    const cooldownDelay = this._ttsEndCooldownMs || 1500;
                    setTimeout(() => {
                        if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                            this.startVoiceInput(false); // false = automatic restart
                        }
                    }, cooldownDelay);
                }
            };
            
        } catch (error) {
            console.error('❌ TTS error:', error);
            if (this._speakingWatchdog) { clearTimeout(this._speakingWatchdog); this._speakingWatchdog = null; }
            // Notify coordinator that TTS has stopped (even on error)
            try { 
                window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); 
            } catch(_) {}
            
            // Fallback to browser speech synthesis if available
            if ('speechSynthesis' in window) {
                try {
                    const utter = new SpeechSynthesisUtterance(text);
                    const voices = window.speechSynthesis.getVoices();
                    // Try to match current recognition/browser language
                    const langPref = (this.recognition && this.recognition.lang) ? this.recognition.lang : (this.defaultLang || navigator.language || 'en-US');
                    utter.lang = langPref;
                    const preferred = voices.find(v => v.lang === langPref)
                        || voices.find(v => v.lang && v.lang.toLowerCase().startsWith((langPref || 'en').toLowerCase().split('-')[0] + '-'))
                        || voices.find(v => v.name.includes('Samantha') || (v.lang && v.lang.startsWith('en-')));
                    if (preferred) utter.voice = preferred;
                    utter.onend = () => {
                        console.log('🔊 Browser TTS ended');
                        this.isSpeaking = false;
                        
                        // ✅ SELF-LOOP PREVENTION: Mark TTS end time for cooldown
                        this._ttsEndedAt = Date.now();
                        
                        // ✅ Clear transcript on browser TTS end
                        this.clearLiveTranscript();
                        
                        try { 
                            window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); 
                        } catch(_) {}
                        this.updateStatus('Ready');
                        
                        // ✅ FIX: Use cooldown delay and pass false for auto-restart
                        if (this.isActive) {
                            const cooldownDelay = this._ttsEndCooldownMs || 1500;
                            setTimeout(() => {
                                if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                                    this.startVoiceInput(false); // false = automatic restart
                                }
                            }, cooldownDelay);
                        }
                    };
                    utter.onerror = () => {
                        console.error('❌ Browser TTS error');
                        this.isSpeaking = false;
                        
                        // ✅ SELF-LOOP PREVENTION: Mark TTS end time for cooldown
                        this._ttsEndedAt = Date.now();
                        
                        // ✅ Clear transcript on browser TTS error
                        this.clearLiveTranscript();
                        
                        try { 
                            window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop(); 
                        } catch(_) {}
                        this.updateStatus('Audio error');
                        
                        // ✅ FIX: Use cooldown delay and pass false for auto-restart
                        if (this.isActive) {
                            const cooldownDelay = this._ttsEndCooldownMs || 1500;
                            setTimeout(() => {
                                if (this.isActive && !this.isSpeaking && !this.isProcessing) {
                                    this.startVoiceInput(false); // false = automatic restart
                                }
                            }, cooldownDelay);
                        }
                    };
                    window.speechSynthesis.speak(utter);
                    return;
                } catch (e) {
                    console.warn('⚠️  Browser TTS fallback failed:', e);
                }
            }
            this.isSpeaking = false;
            this.updateStatus('Error with voice playback');
            
            // Resume listening even if TTS failed
            if (this.isActive) {
                setTimeout(() => {
                    this.startVoiceInput();
                }, 1000);
            }
        }
        // Sync button label/state
        if (this.muteBtn) {
            this.muteBtn.textContent = this.isMuted ? 'Unmute' : 'Mute';
        }
        // Apply to current audio if any
        if (this.currentAudio) {
            try {
                this.currentAudio.muted = !!this.isMuted;
                this.currentAudio.volume = this.isMuted ? 0 : 1;
            } catch (_) {}
        }
        // Inform other modules to sync if needed
        try { window.dispatchEvent(new CustomEvent('vmo:mute-changed', { detail: { muted: this.isMuted } })); } catch(_) {}
    }
    
    startVoiceSession() {
        if (!this.recognition) {
            alert('Voice chat requires speech recognition support. Please use Chrome, Edge, or Safari.');
            return;
        }
        
        if (!this.isActive) {
            this.isActive = true;
            this._autoRestartEnabled = true;
            this.updateStatus('Voice chat active');
            if (this.startBtn) {
                this.startBtn.innerHTML = '<span class="vmo-voice-start-icon">⏸️</span><span class="vmo-voice-start-text">Stop Voice</span>';
            }
            // Attempt to unlock audio playback gate on some browsers (Safari/iOS)
            try {
                const AC = window.AudioContext || window.webkitAudioContext;
                if (AC) {
                    if (!window._vmoAudioCtx) window._vmoAudioCtx = new AC();
                    window._vmoAudioCtx.resume().catch(() => {});
                }
            } catch(_) {}
            
            // Add system message
            this.addMessage('Voice chat started. Speak naturally!', 'system');
            
            // Keep the listening modal visible throughout the session
            this.showListeningModal('listening');
            // Start listening
            this.startVoiceInput();
        } else {
            this.endVoiceSession();
        }
    }
    
    // (removed duplicate show/hide modal methods; see unified versions below)
    
    addMessage(text, sender, saveToDb = true) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `vmo-voice-message ${sender}`;
        
        const contentDiv = document.createElement('div');
        contentDiv.className = 'vmo-voice-message-content';
        contentDiv.textContent = text;
        
        const timeDiv = document.createElement('div');
        timeDiv.className = 'vmo-voice-message-time';
        timeDiv.textContent = new Date().toLocaleTimeString();
        
        messageDiv.appendChild(contentDiv);
        messageDiv.appendChild(timeDiv);
        
        this.chatHistory?.appendChild(messageDiv);
        
        // Scroll to bottom
        if (this.chatHistory) {
            this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
        }
        
        // Store message (don't store system messages, and only save if requested)
        if (sender !== 'system' && saveToDb) {
            this.messages.push({ text, sender, time: new Date() });
        }
    }
    
    /**
     * Create an empty AI message bubble for streaming and return its content element.
     * Tokens are appended to the returned element as they arrive.
     */
    _addStreamBubble() {
        const messageDiv = document.createElement('div');
        messageDiv.className = 'vmo-voice-message ai';

        const contentDiv = document.createElement('div');
        contentDiv.className = 'vmo-voice-message-content';
        contentDiv.textContent = ''; // starts empty

        const timeDiv = document.createElement('div');
        timeDiv.className = 'vmo-voice-message-time';
        timeDiv.textContent = new Date().toLocaleTimeString();

        messageDiv.appendChild(contentDiv);
        messageDiv.appendChild(timeDiv);

        this.chatHistory?.appendChild(messageDiv);
        if (this.chatHistory) this.chatHistory.scrollTop = this.chatHistory.scrollHeight;

        return contentDiv;
    }

    updateStatus(text) {
        if (this.statusText) {
            this.statusText.textContent = text;
        }
    }
    
    // Phase helper: listening | thinking | speaking
    setModalPhase(phase = 'listening') {
        if (!this.listeningModal) return;
        this.listeningModal.classList.remove('listening', 'thinking', 'speaking');
        this.listeningModal.classList.add(phase);
        if (this.listeningText) {
            if (phase === 'speaking') this.listeningText.textContent = 'Speaking...';
            else if (phase === 'thinking') this.listeningText.textContent = 'Processing...';
            else this.listeningText.textContent = 'Listening...';
        }
    }

    showListeningModal(phaseOrTranscript = '') {
        if (this.listeningModal) {
            // Ensure modal is at top-level to avoid stacking-context traps
            try {
                if (document.body && this.listeningModal.parentElement !== document.body) {
                    document.body.appendChild(this.listeningModal);
                }
            } catch(_) {}
            this.listeningModal.style.display = 'flex';
            // Mark ownership to Jarvis by default
            this.listeningModal.dataset.owner = 'voice-panel';
            // Hide consultation-only upload affordance when Jarvis owns modal
            const uploadBtn = document.getElementById('vmo-listening-upload');
            if (uploadBtn) uploadBtn.style.display = 'none';
            // Accept either a phase string or a transcript string
            const validPhase = ['listening','thinking','speaking'];
            if (typeof phaseOrTranscript === 'string' && validPhase.includes(phaseOrTranscript)) {
                this.setModalPhase(phaseOrTranscript);
            } else {
                // Treat as transcript text and default to listening phase
                this.setModalPhase('listening');
                if (this.listeningTranscript) {
                    this.listeningTranscript.textContent = phaseOrTranscript || '';
                }
            }
        }
    }
    
    hideListeningModal() {
        if (this.listeningModal) {
            this.listeningModal.style.display = 'none';
            if (this.listeningTranscript) {
                this.listeningTranscript.textContent = '';
            }
            // Clear ownership when hidden
            try { delete this.listeningModal.dataset.owner; } catch(_) {}
        }
    }

    /**
     * ✅ LIVE TRANSCRIPT: Update the transcript display in real-time
     * Shows what the user or AI is currently saying in the listening modal
     * @param {string} text - The text to display
     * @param {string} speaker - 'user' or 'ai' to style accordingly
     */
    updateLiveTranscript(text, speaker = 'user') {
        if (!this.listeningTranscript) {
            this.listeningTranscript = document.getElementById('vmo-listening-transcript');
        }
        
        if (this.listeningTranscript && text) {
            // Style based on speaker
            const prefix = speaker === 'ai' ? '🤖 ' : '🎤 ';
            this.listeningTranscript.textContent = prefix + text;
            this.listeningTranscript.style.opacity = '1';
            
            // Add visual distinction for AI vs user
            if (speaker === 'ai') {
                this.listeningTranscript.style.color = '#4CAF50'; // Green for AI
                this.listeningTranscript.style.fontStyle = 'normal';
            } else {
                this.listeningTranscript.style.color = '#fff'; // White for user
                this.listeningTranscript.style.fontStyle = 'italic';
            }
        }
    }

    /**
     * ✅ LIVE TRANSCRIPT: Clear the transcript display
     * Called when speech/message is complete
     */
    clearLiveTranscript() {
        if (!this.listeningTranscript) {
            this.listeningTranscript = document.getElementById('vmo-listening-transcript');
        }
        
        if (this.listeningTranscript) {
            this.listeningTranscript.textContent = '';
            this.listeningTranscript.style.opacity = '0';
        }
        
        // Reset buffers
        this._transcriptBuffer = '';
        this._interimText = '';
    }

    endVoiceSession() {
        // Stop voice chat completely
        this.isActive = false;
        this._autoRestartEnabled = false;
        this.updateStatus('Voice chat stopped');
        if (this.startBtn) {
            this.startBtn.innerHTML = '<span class="vmo-voice-start-icon">🎤</span><span class="vmo-voice-start-text">Start Voice</span>';
        }
        // Stop any ongoing audio
        if (this.currentAudio) {
            try { this.currentAudio.pause(); } catch(_) {}
            this.currentAudio = null;
        }
        // Stop listening
        if (this.isListening && this.recognition) {
            try { this.recognition.stop(); } catch(_) {}
            this.isListening = false;
        }
        // Hide modal explicitly
        this.hideListeningModal();
        // Release mic
        try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic('voice-panel'); } catch(_) {}
    }

    openSettingsPopover() {
        // Build a lightweight popover for selecting the Jarvis voice
        let pop = document.getElementById('vmo-voice-settings-popover');
        if (pop) { pop.remove(); }
        pop = document.createElement('div');
        pop.id = 'vmo-voice-settings-popover';
        pop.style.cssText = 'position:fixed; z-index:999999; background:#111; color:#fff; border:1px solid #333; border-radius:8px; padding:12px; box-shadow:0 8px 24px rgba(0,0,0,0.35); max-width:240px;';
        // Position near settings button
        const btnRect = this.settingsBtn?.getBoundingClientRect();
        const x = (btnRect?.left || 20);
        const y = (btnRect?.bottom || 60) + 8;
        pop.style.left = Math.min(window.innerWidth - 260, Math.max(8, x - 200)) + 'px';
        pop.style.top = Math.min(window.innerHeight - 200, y) + 'px';
        pop.innerHTML = `
          <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
            <strong>Voice Settings</strong>
            <button id="vmo-voice-settings-close" style="background:none;border:none;color:#888;cursor:pointer;font-size:16px;">✕</button>
          </div>
          <div id="vmo-voice-settings-list" style="display:grid;gap:6px;margin-bottom:10px;">
            <div style="color:#aaa;">Loading voices…</div>
          </div>
          <div style="display:flex;gap:8px;justify-content:flex-end;">
            <button id="vmo-voice-save" style="background:#2e7d32;color:#fff;border:none;border-radius:4px;padding:6px 10px;cursor:pointer;">Save</button>
          </div>
        `;
        document.body.appendChild(pop);
        const close = () => { try { pop.remove(); } catch(_) {} };
        pop.querySelector('#vmo-voice-settings-close')?.addEventListener('click', close);

        const listEl = pop.querySelector('#vmo-voice-settings-list');
        const current = window.vmoSelectedVoice || localStorage.getItem('vmoJarvisVoice') || 'ash';
        const renderList = (voices) => {
            if (!voices || !voices.length) {
                listEl.innerHTML = '<div style="color:#aaa;">No voices available.</div>';
                return;
            }
            listEl.innerHTML = '';
            voices.forEach(v => {
                const row = document.createElement('label');
                row.style.cssText = 'display:flex;align-items:center;gap:8px;cursor:pointer;';
                const input = document.createElement('input');
                input.type = 'radio';
                input.name = 'vmo-voice-choice';
                input.value = v.id;
                if (v.id === current) input.checked = true;
                const span = document.createElement('span');
                span.textContent = `${v.name} (${v.id})`;
                row.appendChild(input);
                row.appendChild(span);
                listEl.appendChild(row);
            });
        };

        // Load from API
        (async () => {
            try {
                const voices = (typeof window.vmoGetVoices === 'function') ? await window.vmoGetVoices() : [];
                renderList(voices);
            } catch (e) {
                listEl.innerHTML = '<div style="color:#f99;">Failed to load voices.</div>';
            }
        })();

        pop.querySelector('#vmo-voice-save')?.addEventListener('click', () => {
            const sel = pop.querySelector('input[name="vmo-voice-choice"]:checked');
            const val = sel?.value || 'ash';
            window.vmoSelectedVoice = val;
            try { localStorage.setItem('vmoJarvisVoice', val); } catch(_) {}
            this.addMessage(`Jarvis voice set to: ${val}`, 'system');
            close();
        });
    }

    enableDrag() {
        const header = this.panel?.querySelector('.vmo-voice-header');
        const panel = this.panel;
        if (!header || !panel) return;
        let dragging = false;
        let startX = 0, startY = 0;
        let origLeft = 0, origTop = 0;
        const onMouseMove = (e) => {
            if (!dragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            let left = origLeft + dx;
            let top = origTop + dy;
            // Clamp within viewport
            left = Math.min(window.innerWidth - panel.offsetWidth, Math.max(0, left));
            top = Math.min(window.innerHeight - panel.offsetHeight, Math.max(0, top));
            panel.style.left = left + 'px';
            panel.style.top = top + 'px';
            panel.style.bottom = 'auto';
        };
        const onMouseUp = () => {
            dragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        };
        header.addEventListener('mousedown', (e) => {
            // Ignore clicks on buttons in the header (collapse/settings)
            if (e.target.closest('button')) return;
            dragging = true;
            const rect = panel.getBoundingClientRect();
            startX = e.clientX;
            startY = e.clientY;
            // Compute current left/top from rect
            origLeft = rect.left;
            origTop = rect.top;
            // Ensure fixed positioning
            panel.style.position = 'fixed';
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });
    }
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    if (document.getElementById('vmo-ai-voice')) {
        if (window.vmoVoicePanel && window.vmoVoicePanel instanceof VMOVoicePanel) {
            console.warn('⚠️ VMO Voice Panel already initialized; skipping duplicate init');
            return;
        }
        window.vmoVoicePanel = new VMOVoicePanel();
        console.log('✅ VMO Voice Panel initialized');
    }
});

// Close listening modal on click
document.addEventListener('click', (e) => {
    const modal = document.getElementById('vmo-listening-modal');
    if (modal && e.target === modal) {
        if (window.vmoVoicePanel) {
            // During an active session, ignore outside clicks to prevent accidental close
            if (!window.vmoVoicePanel.isActive) window.vmoVoicePanel.hideListeningModal();
        }
    }
});
