const chatWindow = /** @type {any} */ (document.getElementById('chat-window'));
const chatForm = /** @type {any} */ (document.getElementById('chat-form'));
const statusText = /** @type {any} */ (document.getElementById('connection-status'));
let isAskingFeelings = false;

let wsAcoustic = null;
let wsVisual = null;

function scrollToBottom() {
    chatWindow.scrollTop = chatWindow.scrollHeight;
}

function saveChatToLocal() {
    const messages = [];
    const messageNodes = chatWindow.querySelectorAll('.message');
    const startIdx = Math.max(0, messageNodes.length - 50);
    for (let i = startIdx; i < messageNodes.length; i++) {
        const node = messageNodes[i];
        messages.push({
            className: node.className,
            innerHTML: node.innerHTML
        });
    }
    localStorage.setItem('sdo_chat_history', JSON.stringify(messages));
}

function loadChatFromLocal() {
    const history = localStorage.getItem('sdo_chat_history');
    if (history) {
        try {
            const messages = JSON.parse(history);
            messages.forEach(msg => {
                const msgDiv = document.createElement('div');
                msgDiv.className = msg.className;
                msgDiv.innerHTML = msg.innerHTML;
                chatWindow.appendChild(msgDiv);
            });
            scrollToBottom();
        } catch(e) {}
    }
}

// Load history immediately
loadChatFromLocal();

function appendMessage(text, type, extraClass = '') {
    const msgDiv = document.createElement('div');
    msgDiv.className = `message ${type} ${extraClass}`;
    
    const bubble = document.createElement('div');
    bubble.className = 'bubble';
    
    // Matrix typing effect simulation
    bubble.textContent = `> ${text}`;
    
    msgDiv.appendChild(bubble);
    chatWindow.appendChild(msgDiv);
    scrollToBottom();
    saveChatToLocal();
}

function setupSSE() {
    const eventSource = new EventSource('/api/stream');

    eventSource.onopen = () => {
        statusText.textContent = 'LINK ESTABLISHED';
        statusText.style.color = '#00ffcc';
    };

    eventSource.onerror = () => {
        statusText.textContent = 'LINK SEVERED. RETRYING...';
        statusText.style.color = '#ff0000';
        eventSource.close();
        setTimeout(setupSSE, 3000);
    };

    eventSource.addEventListener('log', (e) => {
        try {
            const data = JSON.parse(e.data);
            const msg = data.message;
            
            if (!msg || msg.trim() === '') return;

            // Show high-level internal monologue and social grounding in the main chat UI
            if (msg.includes('[Social Grounding]') || msg.includes('[Internal Monologue]')) {
                let highlightClass = msg.includes('[Internal Monologue]') ? 'highlight-memory' : 'highlight-attention';
                
                appendMessage(msg, 'agi-message', highlightClass);
            } else {
                // Route all OTHER system logs to the Internal Monologue terminal
                const terminal = /** @type {any} */ (document.getElementById('internal-monologue'));
                if (terminal) {
                    const p = document.createElement('p');
                    p.textContent = msg;
                    terminal.appendChild(p);
                    
                    // Keep max 500 lines to prevent DOM bloat
                    if (terminal.childNodes.length > 500) {
                        terminal.removeChild(terminal.firstChild);
                    }
                    
                    // Auto-scroll to bottom
                    terminal.scrollTop = terminal.scrollHeight;
                }
            }
        } catch (err) {
            console.error("Failed to parse SSE data", err);
        }
    });

    eventSource.addEventListener('status', (e) => {
        try {
            const data = JSON.parse(e.data);
            statusText.textContent = data.message;
            if (data.color) {
                statusText.style.color = data.color;
            } else {
                statusText.style.color = '#00ffcc';
            }
            
            // Handle the UI Thinking Status window
            const thinkingStatus = /** @type {any} */ (document.getElementById('thinking-status'));
            const thinkingText = /** @type {any} */ (document.getElementById('thinking-text'));
            if (thinkingStatus && thinkingText) {
                if (data.message.includes('Thinking') || data.message.includes('RESEARCHING')) {
                    thinkingStatus.style.display = 'flex';
                    thinkingText.textContent = data.message;
                } else if (data.message === 'ONLINE' || data.message === 'LINK ESTABLISHED') {
                    thinkingStatus.style.display = 'none';
                }
            }
            
            // Re-enable Sleep Button if waking up
            if (data.message.includes('LINK ESTABLISHED')) {
                const sleepBtn = /** @type {any} */ (document.getElementById('sleep-btn'));
                if (sleepBtn && sleepBtn.disabled) {
                    sleepBtn.disabled = false;
                    sleepBtn.classList.remove('active');
                    const span = sleepBtn.querySelector('span');
                    if (span) span.textContent = 'Sleep';
                }
            }
        } catch (err) {
            console.error("Failed to parse status data", err);
        }
    });

    eventSource.addEventListener('lot', (e) => {
        try {
            const data = JSON.parse(e.data);
            const panel = /** @type {any} */ (document.getElementById('lot-panel'));
            if (panel) {
                if (data.memory && data.memory.length > 0) {
                    panel.innerHTML = data.memory.map((step, index) => 
                        `<div><span style="color:var(--accent-blue);">[${index}]</span> ${step}</div>`
                    ).join('');
                } else {
                    panel.innerHTML = `<span style="color: var(--text-tertiary); font-style: italic;">Scratchpad empty...</span>`;
                }
            }
        } catch (err) {
            console.error("Failed to parse LoT data", err);
        }
    });

    eventSource.addEventListener('working_memory', (e) => {
        try {
            const data = JSON.parse(e.data);
            const panel = /** @type {any} */ (document.getElementById('working-memory-panel'));
            if (panel) {
                if (data.memory && data.memory.length > 0) {
                    panel.innerHTML = data.memory.map((step, index) => 
                        `<div><span style="color:var(--accent-green);">[${index}]</span> ${step}</div>`
                    ).join('');
                } else {
                    panel.innerHTML = `<span style="color: var(--text-tertiary); font-style: italic;">Memory empty...</span>`;
                }
            }
        } catch (err) {
            console.error("Failed to parse Working Memory data", err);
        }
    });

    let currentGenerationBubble = null;
    let synthAudioCtx = null;
    
    let tractNode = null;
    // Unlock AudioContext on user interaction to comply with Autoplay Policy
    document.addEventListener('click', async () => {
        if (!synthAudioCtx) {
            synthAudioCtx = new (window.AudioContext || /** @type {any} */ (window).webkitAudioContext)();
            try {
                await synthAudioCtx.audioWorklet.addModule('/worklets/VocalTractProcessor.js');
                tractNode = new AudioWorkletNode(synthAudioCtx, 'vocal-tract-processor');
                tractNode.connect(synthAudioCtx.destination);
            } catch (e) {
                console.error("Failed to load VocalTractProcessor", e);
            }
        } else if (synthAudioCtx.state === 'suspended') {
            synthAudioCtx.resume();
        }
    });
    
    function playAlienPhoneme(phonemeId, volumeStr = 'MID', pitchStr = 'MID') {
        if (!synthAudioCtx) {
            synthAudioCtx = new (window.AudioContext || /** @type {any} */ (window).webkitAudioContext)();
        }
        if (synthAudioCtx.state === 'suspended') {
            synthAudioCtx.resume();
        }
        
        const osc = synthAudioCtx.createOscillator();
        const gainNode = synthAudioCtx.createGain();
        
        // Map 0-63 to interesting frequencies (between 200Hz and 1460Hz)
        let baseFreq = 200 + (phonemeId * 20);
        if (pitchStr === 'HIGH') baseFreq *= 1.5;
        if (pitchStr === 'LOW') baseFreq *= 0.5;
        
        osc.frequency.setValueAtTime(baseFreq, synthAudioCtx.currentTime);
        
        // Vary the waveform based on phoneme ID for texture
        const waves = ['sine', 'square', 'triangle', 'sawtooth'];
        osc.type = waves[phonemeId % 4];
        
        let maxGain = 0.1;
        if (volumeStr === 'LOUD') maxGain = 0.3;
        if (volumeStr === 'QUIET') maxGain = 0.02;

        // Envelope: quick attack, quick release (babbling effect)
        gainNode.gain.setValueAtTime(0, synthAudioCtx.currentTime);
        gainNode.gain.linearRampToValueAtTime(maxGain, synthAudioCtx.currentTime + 0.02);
        gainNode.gain.exponentialRampToValueAtTime(0.001, synthAudioCtx.currentTime + 0.1);
        
        osc.connect(gainNode);
        gainNode.connect(synthAudioCtx.destination);
        
        osc.start();
        osc.stop(synthAudioCtx.currentTime + 0.1);
    }

    function playMotorBabble(tongueIndex, tongueDiameter, pitch, intensity) {
        if (!synthAudioCtx || !tractNode) return;
        if (synthAudioCtx.state === 'suspended') synthAudioCtx.resume();

        const t = synthAudioCtx.currentTime;
        const rampTime = 0.15; // Smooth physical glide
        
        tractNode.parameters.get('tongueIndex').linearRampToValueAtTime(tongueIndex, t + rampTime);
        tractNode.parameters.get('tongueDiameter').linearRampToValueAtTime(tongueDiameter, t + rampTime);
        tractNode.parameters.get('glottisPitch').linearRampToValueAtTime(pitch, t + rampTime);
        
        // Intensity goes up then down
        tractNode.parameters.get('intensity').cancelScheduledValues(t);
        tractNode.parameters.get('intensity').setValueAtTime(0, t);
        tractNode.parameters.get('intensity').linearRampToValueAtTime(intensity, t + rampTime/2);
        tractNode.parameters.get('intensity').linearRampToValueAtTime(0, t + rampTime + 0.1);
    }

    let bubbleTimeout = null;

    eventSource.addEventListener('generation', (e) => {
        try {
            const thinkingStatus = /** @type {any} */ (document.getElementById('thinking-status'));
            if (thinkingStatus) thinkingStatus.style.display = 'none';
            
            const data = JSON.parse(e.data);
            if (data.type === 'token') {
                if (bubbleTimeout) clearTimeout(bubbleTimeout);
                bubbleTimeout = setTimeout(() => {
                    if (currentGenerationBubble) {
                        currentGenerationBubble = null;
                        if (isAskingFeelings) isAskingFeelings = false;
                        saveChatToLocal();
                    }
                }, 5000);

                // Check if the token is a physical motor coordinate
                const tongueMatch = data.text.match(/\[?\s*tongue[_\s]*(\d+)_([\d\.]+)_(\d+)_([\d\.]+)\s*\]?/i);

                if (tongueMatch) {
                    const tIndex = parseInt(tongueMatch[1]);
                    const tDiam = parseFloat(tongueMatch[2]);
                    const pitch = parseInt(tongueMatch[3]);
                    const intensity = parseFloat(tongueMatch[4]);
                    playMotorBabble(tIndex, tDiam, pitch, intensity);
                    
                    if (data.isLast && currentGenerationBubble) {
                        currentGenerationBubble = null;
                    }
                    return; // Completely hide spoken motor hashes from the chat UI
                }

                if (!currentGenerationBubble) {
                    const msgDiv = document.createElement('div');
                    msgDiv.className = `message agi-message highlight-generative`;
                    currentGenerationBubble = document.createElement('div');
                    currentGenerationBubble.className = 'bubble';
                    currentGenerationBubble.textContent = `> `;
                    msgDiv.appendChild(currentGenerationBubble);
                    chatWindow.appendChild(msgDiv);
                }

                // Check if the token is an alien phoneme
                const phonemeMatch = data.text.match(/\[?\s*phoneme[_\s]*(\d+)(?:_([a-z]+)_([a-z]+))?\s*\]?/i);

                if (phonemeMatch) {
                    const phonemeId = parseInt(phonemeMatch[1]);
                    const volumeStr = phonemeMatch[2] ? phonemeMatch[2].toUpperCase() : 'MID';
                    const pitchStr = phonemeMatch[3] ? phonemeMatch[3].toUpperCase() : 'MID';
                    playAlienPhoneme(phonemeId, volumeStr, pitchStr);
                    currentGenerationBubble.textContent += data.text + ' ';
                } else {
                    currentGenerationBubble.textContent += data.text + ' ';
                }
                
                // If this is an answer to how it feels, we also want to display it in the Self-Reported State
                if (isAskingFeelings) {
                    const stateBox = /** @type {any} */ (document.getElementById('self-reported-state'));
                    if (stateBox) {
                        if (stateBox.textContent === '...') {
                            stateBox.textContent = '';
                        }
                        stateBox.textContent += data.text + ' ';
                    }
                }
                
                if (data.isLast) {
                    if (data.source) {
                        const sourceLink = document.createElement('a');
                        sourceLink.href = data.source;
                        sourceLink.target = "_blank";
                        sourceLink.textContent = ` [Source]`;
                        sourceLink.style.color = '#00ffcc';
                        sourceLink.style.textDecoration = 'none';
                        sourceLink.style.fontSize = '0.8em';
                        currentGenerationBubble.appendChild(sourceLink);
                    }
                    currentGenerationBubble = null;
                    saveChatToLocal();
                    if (isAskingFeelings) {
                        isAskingFeelings = false;
                    }
                }
                scrollToBottom();
            } else if (data.type === 'end') {
                currentGenerationBubble = null;
                saveChatToLocal();
                if (isAskingFeelings) {
                    isAskingFeelings = false;
                }
            }
        } catch (err) {
            console.error("Failed to parse generation data", err);
        }
    });


    eventSource.addEventListener('metrics', (e) => {
        try {
            const data = JSON.parse(e.data);
            const cpuEl = /** @type {any} */ (document.getElementById('cpu-val'));
            const ramEl = /** @type {any} */ (document.getElementById('ram-val'));
            const iqEl = /** @type {any} */ (document.getElementById('iq-val'));
            const dopEl = /** @type {any} */ (document.getElementById('dop-val'));
            const serEl = /** @type {any} */ (document.getElementById('ser-val'));
            const oxyEl = /** @type {any} */ (document.getElementById('oxy-val'));
            const corEl = /** @type {any} */ (document.getElementById('cor-val'));
            const melEl = /** @type {any} */ (document.getElementById('mel-val'));
            const adeEl = /** @type {any} */ (document.getElementById('ade-val'));
            const endEl = /** @type {any} */ (document.getElementById('end-val'));
            if (cpuEl && ramEl) {
                cpuEl.textContent = data.cpu + '%';
                ramEl.textContent = data.ram + ' MB';
            }
            if (iqEl && data.iq !== undefined) {
                iqEl.textContent = data.iq;
            }
            if (dopEl) {
                dopEl.textContent = data.dopamine;
                /** @type {any} */ (document.getElementById('dop-fill')).style.width = (parseFloat(data.dopamine) * 100) + '%';
            }
            if (serEl) {
                serEl.textContent = data.serotonin;
                /** @type {any} */ (document.getElementById('ser-fill')).style.width = (parseFloat(data.serotonin) * 100) + '%';
            }
            if (oxyEl) {
                oxyEl.textContent = data.oxytocin;
                /** @type {any} */ (document.getElementById('oxy-fill')).style.width = (parseFloat(data.oxytocin) * 100) + '%';
            }
            if (corEl) {
                corEl.textContent = data.cortisol;
                /** @type {any} */ (document.getElementById('cor-fill')).style.width = (parseFloat(data.cortisol) * 100) + '%';
            }
            if (melEl) {
                const mel = data.melatonin || "0.00";
                melEl.textContent = mel;
                /** @type {any} */ (document.getElementById('mel-fill')).style.width = (parseFloat(mel) * 100) + '%';
            }
            if (adeEl) {
                const ade = data.adenosine || "0.00";
                adeEl.textContent = ade;
                /** @type {any} */ (document.getElementById('ade-fill')).style.width = (parseFloat(ade) * 100) + '%';
            }
            if (endEl) {
                const end = data.endorphins || "0.00";
                endEl.textContent = end;
                /** @type {any} */ (document.getElementById('end-fill')).style.width = (parseFloat(end) * 100) + '%';
            }
            
            const atpEl = /** @type {any} */ (document.getElementById('atp-val'));
            if (atpEl && data.atp !== undefined) {
                atpEl.textContent = data.atp + '%';
                /** @type {any} */ (document.getElementById('atp-fill')).style.width = data.atp + '%';
            }

            const gatingEl = /** @type {any} */ (document.getElementById('gating-val'));
            const gatingSlider = /** @type {any} */ (document.getElementById('gating-slider'));
            if (gatingEl && data.gatingThreshold !== undefined) {
                gatingEl.textContent = data.gatingThreshold;
                if (gatingSlider && !isDraggingGatingSlider) {
                    gatingSlider.value = data.gatingThreshold;
                }
            }
        } catch (err) {
            console.error("Failed to parse metrics data", err);
        }
    });
}

// --- Sensory Gating Logic ---
let isDraggingGatingSlider = false;
const gatingSlider = /** @type {any} */ (document.getElementById('gating-slider'));
const gatingAutoBtn = /** @type {any} */ (document.getElementById('gating-auto-btn'));

if (gatingSlider) {
    gatingSlider.addEventListener('mousedown', () => isDraggingGatingSlider = true);
    gatingSlider.addEventListener('touchstart', () => isDraggingGatingSlider = true);
    
    const resetDrag = () => { isDraggingGatingSlider = false; };
    gatingSlider.addEventListener('mouseup', resetDrag);
    gatingSlider.addEventListener('mouseleave', resetDrag);
    gatingSlider.addEventListener('touchend', resetDrag);
    
    gatingSlider.addEventListener('change', (e) => {
        isDraggingGatingSlider = false;
        fetch('/api/thalamus/override', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ threshold: /** @type {any} */ (e.target).value })
        }).catch(console.error);
    });
}

if (gatingAutoBtn) {
    gatingAutoBtn.addEventListener('click', () => {
        fetch('/api/thalamus/override', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ threshold: null })
        }).catch(console.error);
    });
}

// --- Input Textarea Logic ---
const inputEl = /** @type {any} */ (document.getElementById('input-text'));
const usernameInput = /** @type {any} */ (document.getElementById('username-input'));

if (usernameInput) {
    const savedName = localStorage.getItem('agi_username');
    if (savedName) usernameInput.value = savedName;

    usernameInput.addEventListener('input', (e) => {
        localStorage.setItem('agi_username', /** @type {any} */ (e.target).value.trim());
    });
}

let inputHistory = JSON.parse(localStorage.getItem('agi_input_history') || '[]');
let historyIndex = -1;
let currentUnsubmittedText = "";

// Auto-resize logic
inputEl.addEventListener('input', function() {
    /** @type {any} */ (this).style.height = '52px'; // Reset to base height to allow shrinking
    /** @type {any} */ (this).style.height = (/** @type {any} */ (this).scrollHeight) + 'px';
});

// Enter to submit logic and history navigation
inputEl.addEventListener('keydown', function(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        chatForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
    } else if (e.key === 'ArrowUp') {
        if (inputHistory.length > 0) {
            e.preventDefault();
            if (historyIndex === -1) {
                currentUnsubmittedText = this.value;
                historyIndex = inputHistory.length - 1;
            } else if (historyIndex > 0) {
                historyIndex--;
            }
            this.value = inputHistory[historyIndex];
            /** @type {any} */ (this).style.height = '52px';
            /** @type {any} */ (this).style.height = (/** @type {any} */ (this).scrollHeight) + 'px';
        }
    } else if (e.key === 'ArrowDown') {
        if (historyIndex !== -1) {
            e.preventDefault();
            if (historyIndex < inputHistory.length - 1) {
                historyIndex++;
                this.value = inputHistory[historyIndex];
            } else {
                historyIndex = -1;
                this.value = currentUnsubmittedText;
            }
            /** @type {any} */ (this).style.height = '52px';
            /** @type {any} */ (this).style.height = (/** @type {any} */ (this).scrollHeight) + 'px';
        }
    }
});

chatForm.addEventListener('submit', async (e) => {
    e.preventDefault();
        
    const inputEl = /** @type {any} */ (document.getElementById('input-text'));
    const text = inputEl.value.trim();
    const usernameInput = /** @type {any} */ (document.getElementById('username-input'));
    const username = usernameInput ? usernameInput.value.trim() : null;

    if (!text) return;

    // Save to history
    if (inputHistory.length === 0 || inputHistory[inputHistory.length - 1] !== text) {
        inputHistory.push(text);
        if (inputHistory.length > 50) inputHistory.shift(); // Keep last 50
        localStorage.setItem('agi_input_history', JSON.stringify(inputHistory));
    }
    historyIndex = -1;
    currentUnsubmittedText = "";

    // Clear input and reset height
    inputEl.value = '';
    inputEl.style.height = '52px';

    // Add user message to UI
    appendMessage(text, 'user-message');

    // Populate the Concept Inspector selector with words from the prompt
    const words = text.split(/\s+/).filter(Boolean);
    const recentConceptsSelect = /** @type {any} */ (document.getElementById('recent-concepts-select'));
    if (recentConceptsSelect && words.length > 0) {
        recentConceptsSelect.innerHTML = '<option value="">(Select Concept)</option>';
        const uniqueWords = [...new Set(words.map(w => w.toLowerCase().replace(/^[.,!?"'()[\]{}:;]+|[.,!?"'()[\]{}:;]+$/g, '')))];
        uniqueWords.forEach(w => {
            if (w) {
                const opt = document.createElement('option');
                opt.value = w;
                opt.textContent = w;
                recentConceptsSelect.appendChild(opt);
            }
        });
        
        // Auto-select the first concept
        const firstValidWord = uniqueWords.find(w => w);
        if (firstValidWord) {
            recentConceptsSelect.value = firstValidWord;
            inspectConcept(firstValidWord);
        }
    }

    const tone = 'neutral';

    try {
        const response = await fetch('/api/observe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text, tone, username })
        });
        
        if (!response.ok) {
            appendMessage('SYSTEM ERROR: UPLINK FAILED.', 'system-message');
        }
    } catch (err) {
        appendMessage('CONNECTION LOST.', 'system-message');
    }
});

/** @type {any} */ (document.getElementById('btn-ask-feelings'))?.addEventListener('click', async () => {
    appendMessage("Asking AGI how it feels...", 'system-message');
    isAskingFeelings = true;
    const stateBox = /** @type {any} */ (document.getElementById('self-reported-state'));
    if (stateBox) stateBox.textContent = '...';
    try {
        const usernameInput = /** @type {any} */ (document.getElementById('username-input'));
        const username = usernameInput ? usernameInput.value.trim() : null;

        const response = await fetch('/api/observe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: "How are you feeling right now? What are your emotions?", tone: 'neutral', username })
        });
        
        if (!response.ok) {
            appendMessage('SYSTEM ERROR: UPLINK FAILED.', 'system-message');
        }
    } catch (err) {
        appendMessage('CONNECTION LOST.', 'system-message');
    }
});

const copyBtn = /** @type {any} */ (document.getElementById('copy-btn'));
if (copyBtn) {
    copyBtn.addEventListener('click', async () => {
        const bubbles = document.querySelectorAll('.message .bubble');
        const transcript = Array.from(bubbles).map(b => b.textContent).join('\n');
        try {
            await navigator.clipboard.writeText(transcript);
            // Replace the SVG temporarily with text or just change class
            const originalHTML = copyBtn.innerHTML;
            copyBtn.innerHTML = '<span>COPIED!</span>';
            setTimeout(() => {
                copyBtn.innerHTML = originalHTML;
            }, 2000);
        } catch (err) {
            console.error('Failed to copy transcript: ', err);
            const originalHTML = copyBtn.innerHTML;
            copyBtn.innerHTML = '<span>ERROR</span>';
            setTimeout(() => {
                copyBtn.innerHTML = originalHTML;
            }, 2000);
        }
    });
}

const restartBtn = /** @type {any} */ (document.getElementById('restart-btn'));
if (restartBtn) {
    restartBtn.addEventListener('click', async () => {
        try {
            const originalHTML = restartBtn.innerHTML;
            restartBtn.innerHTML = '<span>RESTARTING...</span>';
            restartBtn.disabled = true;
            
            await fetch('/api/restart', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' }
            });
            
            setTimeout(() => {
                restartBtn.innerHTML = originalHTML;
                restartBtn.disabled = false;
            }, 3000);
        } catch (err) {
            console.error('Failed to restart AGI: ', err);
            const originalHTML = restartBtn.innerHTML;
            restartBtn.innerHTML = '<span>ERROR</span>';
            setTimeout(() => {
                restartBtn.innerHTML = originalHTML;
                restartBtn.disabled = false;
            }, 3000);
        }
    });
}

const sleepBtn = /** @type {any} */ (document.getElementById('sleep-btn'));
if (sleepBtn) {
    sleepBtn.addEventListener('click', async () => {
        try {
            const span = sleepBtn.querySelector('span');
            span.textContent = 'SLEEPING (REM)...';
            sleepBtn.disabled = true;
            sleepBtn.classList.add('active');
            
            await fetch('/api/sleep', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' }
            });
            // We NO LONGER use a hardcoded timeout.
            // The SSE 'status' listener will re-enable this button when the AGI wakes up!
        } catch (err) {
            console.error('Failed to force sleep: ', err);
            const span = sleepBtn.querySelector('span');
            if (span) span.textContent = 'ERROR';
            sleepBtn.disabled = false;
        }
    });
}

setupSSE();

// --- Ambient Hearing Logic ---
const hearingBtn = /** @type {any} */ (document.getElementById('hearing-btn'));
let isHearing = false;
let audioContext = null;
let analyser = null;
let microphone = null;
let animationId = null;
let rollingAverage = 0;
let lastSpikeTime = 0;

// Auto-Teacher variables
let recognition = null;
const SpeechRec = /** @type {any} */ (window).SpeechRecognition || /** @type {any} */ (window).webkitSpeechRecognition;

if (SpeechRec) {
    recognition = new SpeechRec();
    recognition.continuous = true;
    recognition.interimResults = false;
    
    recognition.onresult = function(event) {
        let finalTranscript = '';
        for (let i = event.resultIndex; i < event.results.length; ++i) {
            if (event.results[i].isFinal) {
                finalTranscript += event.results[i][0].transcript;
            }
        }
        if (finalTranscript.trim()) {
            console.log("Auto-Teacher transcribed:", finalTranscript);
            const inputEl = /** @type {any} */ (document.getElementById('input-text'));
            if (inputEl) {
                inputEl.value = finalTranscript.trim();
                // We use submit button dispatch so it runs all the UI logic (adding to chat, clearing box, etc)
                /** @type {any} */ (document.getElementById('chat-form')).dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
            }
        }
    };
    
    // Automatically restart if it stops unexpectedly while ON
    recognition.onend = function() {
        if (isHearing) {
            try { recognition.start(); } catch(e) {}
        }
    }
}

// Utterance buffering
let utteranceFrames = [];
let isSpeaking = false;
let silenceTimer = null;

if (hearingBtn) {
    hearingBtn.addEventListener('click', async () => {
        if (isHearing) {
            isHearing = false;
            const span = hearingBtn.querySelector('span');
            if(span) span.textContent = 'Hearing';
            hearingBtn.classList.remove('active');
            if (audioContext) {
                audioContext.close();
                audioContext = null;
            }
            if (animationId) cancelAnimationFrame(animationId);
            if (recognition) recognition.stop();
            if (wsAcoustic) {
                wsAcoustic.close();
                wsAcoustic = null;
            }
            
            const statusEl = /** @type {any} */ (document.getElementById('acoustic-status'));
            if (statusEl) {
                statusEl.textContent = 'Microphone Disconnected';
                statusEl.style.color = '#ff4a4a';
            }
            
            const canvas = /** @type {any} */ (document.getElementById('waveform-canvas'));
            const canvasCtx = canvas ? canvas.getContext('2d') : null;
            if (canvasCtx && canvas) {
                canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
            }
            
            localStorage.setItem('agi_hearing_active', 'false');
            return;
        }

        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
            audioContext = new (window.AudioContext || /** @type {any} */ (window).webkitAudioContext)();
            analyser = audioContext.createAnalyser();
            microphone = audioContext.createMediaStreamSource(stream);
            microphone.connect(analyser);
            
            analyser.fftSize = 256;
            const bufferLength = analyser.frequencyBinCount;
            const dataArray = new Uint8Array(bufferLength);
            
            isHearing = true;
            localStorage.setItem('agi_hearing_active', 'true');
            hearingBtn.textContent = 'Ambient Hearing: ON';
            hearingBtn.style.backgroundColor = '#10b981'; // Green
            
            const statusEl = /** @type {any} */ (document.getElementById('acoustic-status'));
            if (statusEl) {
                if (SpeechRec) {
                    statusEl.textContent = 'Microphone Active';
                    statusEl.style.color = '#10b981';
                } else {
                    statusEl.innerHTML = 'Microphone Active <span style="color:#ffcc00;font-size:0.8em">(No STT Auto-Teacher)</span>';
                    statusEl.style.color = '#10b981';
                }
            }
            
            const canvas = /** @type {any} */ (document.getElementById('waveform-canvas'));
            const canvasCtx = canvas ? canvas.getContext('2d') : null;
            if (canvas) {
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;
            }
            
            if (recognition) {
                try { recognition.start(); } catch(e) {}
            }
            
            const connectAcousticWS = () => {
                const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                wsAcoustic = new WebSocket(`${protocol}//${window.location.host}/api/ws/acoustic`);
                wsAcoustic.onopen = () => console.log('Acoustic WebSocket Connected');
                wsAcoustic.onerror = (e) => console.error('Acoustic WebSocket Error:', e);
                wsAcoustic.onclose = () => {
                    console.log('Acoustic WebSocket Closed. Reconnecting in 2s...');
                    if (isHearing) setTimeout(connectAcousticWS, 2000);
                };
            };
            connectAcousticWS();

            const floatDataArray = new Float32Array(bufferLength);
            
            const detectAcoustics = () => {
                if (!isHearing) return;
                
                analyser.getByteFrequencyData(dataArray); // For drawing
                analyser.getFloatFrequencyData(floatDataArray); // For backend streaming
                
                // Continuous sensory streaming to backend
                if (wsAcoustic && wsAcoustic.readyState === WebSocket.OPEN) {
                    wsAcoustic.send(floatDataArray.buffer);
                }
                
                // --- Waveform Drawing ---
                if (canvasCtx) {
                    canvasCtx.fillStyle = 'rgba(0, 0, 0, 0.2)';
                    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
                    
                    const barWidth = (canvas.width / bufferLength) * 2.5;
                    let barHeight;
                    let x = 0;
                    
                    for (let i = 0; i < bufferLength; i++) {
                        barHeight = dataArray[i] / 2;
                        canvasCtx.fillStyle = `rgb(50, 200, 255)`; // Default blue
                        canvasCtx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
                        x += barWidth + 1;
                    }
                }
                
                animationId = requestAnimationFrame(detectAcoustics);
            };
            
            detectAcoustics();
            
        } catch (err) {
            console.error('Failed to access microphone:', err);
            hearingBtn.textContent = 'MIC ERROR';
            const statusEl = /** @type {any} */ (document.getElementById('acoustic-status'));
            if (statusEl) {
                statusEl.textContent = 'Microphone Error';
                statusEl.style.color = '#ff4a4a';
            }
            setTimeout(() => {
                hearingBtn.textContent = 'Ambient Hearing: OFF';
            }, 2000);
        }
    });
}

// --- Cognitive Inspector Logic ---
const inspectorContent = /** @type {any} */ (document.getElementById('inspector-content'));
const recentConceptsSelect = /** @type {any} */ (document.getElementById('recent-concepts-select'));
const inspectorControls = /** @type {any} */ (document.getElementById('inspector-controls'));
const targetLabel = /** @type {any} */ (document.getElementById('inspector-target-label'));
let currentInspectConcept = null;
let network = null;
let selectedNodeId = null;

if (recentConceptsSelect) {
    recentConceptsSelect.addEventListener('change', (e) => {
        if (/** @type {any} */ (e.target).value) {
            inspectConcept(/** @type {any} */ (e.target).value);
        }
    });
}

/** @type {any} */ (document.getElementById('btn-reinforce-edge'))?.addEventListener('click', () => {
    if (currentInspectConcept && selectedNodeId) correctEdge(currentInspectConcept, selectedNodeId, 1);
});
/** @type {any} */ (document.getElementById('btn-sever-edge'))?.addEventListener('click', () => {
    if (currentInspectConcept && selectedNodeId) correctEdge(currentInspectConcept, selectedNodeId, -1);
});

async function inspectConcept(concept) {
    if (concept === currentInspectConcept && network !== null) return;
    currentInspectConcept = concept;
    selectedNodeId = null;
    
    if (inspectorControls) inspectorControls.style.display = 'none';

    if (!inspectorContent) {
        console.warn("inspectorContent DOM element is missing.");
        return;
    }

    try {
        const response = await fetch(`/api/graph/inspect?concept=${encodeURIComponent(concept)}`);
        const data = await response.json();
        
        if (!data.edges || data.edges.length === 0) {
            inspectorContent.innerHTML = `<div class="inspector-empty">No associations found for "${concept}"</div>`;
            if (network) {
                network.destroy();
                network = null;
            }
            return;
        }

        inspectorContent.innerHTML = ''; // Clear previous content

        const nodes = new (/** @type {any} */ (window).vis).DataSet([
            { id: concept, label: concept, shape: 'circle', color: { background: '#10b981', border: '#059669' }, font: { color: '#ffffff', size: 18 } }
        ]);
        
        const edges = new (/** @type {any} */ (window).vis).DataSet();

        data.edges.forEach(edge => {
            nodes.add({
                id: edge.target,
                label: edge.target,
                shape: 'dot',
                size: Math.max(5, Math.min(30, Math.abs(edge.weight) * 5)),
                color: { background: '#3498db', border: '#2980b9' },
                font: { color: '#bdc3c7', size: 12 }
            });
            edges.add({
                from: concept,
                to: edge.target,
                value: Math.max(0.1, edge.weight), // edge thickness
                color: { color: '#2c3e50', highlight: '#3498db' }
            });
        });

        const graphData = { nodes, edges };
        const options = {
            nodes: {
                borderWidth: 2,
                shadow: true
            },
            edges: {
                smooth: { type: 'continuous' }
            },
            physics: {
                barnesHut: {
                    gravitationalConstant: -2000,
                    centralGravity: 0.3,
                    springLength: 95,
                    springConstant: 0.04,
                    damping: 0.09,
                    avoidOverlap: 0.1
                }
            },
            interaction: {
                hover: true
            }
        };

        network = new (/** @type {any} */ (window).vis).Network(inspectorContent, graphData, options);

        network.on("click", function (params) {
            if (params.nodes.length > 0) {
                const nodeId = params.nodes[0];
                if (nodeId !== currentInspectConcept) {
                    selectedNodeId = nodeId;
                    if (inspectorControls) inspectorControls.style.display = 'flex';
                    if (targetLabel) targetLabel.textContent = nodeId;
                } else {
                    selectedNodeId = null;
                    if (inspectorControls) inspectorControls.style.display = 'none';
                }
            } else {
                selectedNodeId = null;
                if (inspectorControls) inspectorControls.style.display = 'none';
            }
        });

        network.on("doubleClick", function (params) {
            if (params.nodes.length > 0) {
                const nodeId = params.nodes[0];
                if (nodeId !== currentInspectConcept) {
                    if (recentConceptsSelect) {
                        // Optionally add it to the dropdown if we want
                        recentConceptsSelect.value = "";
                    }
                    inspectConcept(nodeId);
                }
            }
        });

    } catch (e) {
        console.error("Inspector error:", e);
    }
}

async function correctEdge(source, target, adjustment) {
    try {
        await fetch('/api/graph/correct', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ source, target, adjustment })
        });
        // Refresh inspector
        const tmp = currentInspectConcept;
        currentInspectConcept = null;
        inspectConcept(tmp);
    } catch (e) {
        console.error("Failed to correct edge", e);
    }
}

// --- Visual Cortex Logic ---
const visionBtn = /** @type {any} */ (document.getElementById('vision-btn'));
let isSeeing = false;
let videoStream = null;
let visualAnimId = null;
let lastVisualPixels = null;
let lastSaccadeTime = 0;

if (visionBtn) {
    visionBtn.addEventListener('click', async () => {
        const videoEl = /** @type {any} */ (document.getElementById('webcam-video'));
        const canvasEl = /** @type {any} */ (document.getElementById('retina-canvas'));
        const ctx = canvasEl ? canvasEl.getContext('2d', { willReadFrequently: true }) : null;
        const statusEl = /** @type {any} */ (document.getElementById('visual-status'));

        if (isSeeing) {
            isSeeing = false;
            const span = visionBtn.querySelector('span');
            if(span) span.textContent = 'Vision';
            visionBtn.classList.remove('active');
            if (visualAnimId) cancelAnimationFrame(visualAnimId);
            if (videoStream) {
                videoStream.getTracks().forEach(t => t.stop());
                videoStream = null;
            }
            if (videoEl) videoEl.srcObject = null;
            if (wsVisual) {
                wsVisual.close();
                wsVisual = null;
            }
            if (statusEl) {
                statusEl.textContent = 'Webcam Disconnected';
                statusEl.style.color = '#ff4a4a';
            }
            if (ctx) ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
            localStorage.setItem('agi_vision_active', 'false');
            return;
        }

        try {
            videoStream = await navigator.mediaDevices.getUserMedia({ video: { width: 320, height: 240 }, audio: false });
            if (videoEl) {
                videoEl.srcObject = videoStream;
                await videoEl.play();
            }

            isSeeing = true;
            localStorage.setItem('agi_vision_active', 'true');
            const span = visionBtn.querySelector('span');
            if(span) span.textContent = 'Vision';
            visionBtn.classList.add('active');
            
            if (statusEl) {
                statusEl.textContent = 'Retina Active';
                statusEl.style.color = '#10b981';
            }

            // Connect Visual WebSocket for high-FPS continuous streaming
            const connectVisualWS = () => {
                const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                wsVisual = new WebSocket(`${protocol}//${window.location.host}/api/ws/visual`);
                wsVisual.onopen = () => console.log('Visual WebSocket Connected');
                wsVisual.onerror = (e) => console.error('Visual WebSocket Error:', e);
                wsVisual.onclose = () => {
                    console.log('Visual WebSocket Closed. Reconnecting in 2s...');
                    if (isSeeing) setTimeout(connectVisualWS, 2000);
                };
            };
            connectVisualWS();

            let lastFrameTime = 0;
            const targetFPS = 24;
            const frameInterval = 1000 / targetFPS;

            const processFrame = (currentTime) => {
                if (!isSeeing || !videoEl || !ctx) return;

                visualAnimId = requestAnimationFrame(processFrame);

                if (currentTime - lastFrameTime < frameInterval) return;
                lastFrameTime = currentTime;

                // Draw downsampled video to 53x40 canvas
                ctx.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
                
                const imgData = ctx.getImageData(0, 0, canvasEl.width, canvasEl.height);
                const data = imgData.data;
                
                // Extract RGB pixels (ignore alpha)
                const pixelArray = new Uint8Array(canvasEl.width * canvasEl.height * 3);
                let pIdx = 0;
                for (let i = 0; i < data.length; i += 4) {
                    pixelArray[pIdx++] = data[i];     // R
                    pixelArray[pIdx++] = data[i+1];   // G
                    pixelArray[pIdx++] = data[i+2];   // B
                }

                // Continuous sensory streaming at 24fps
                if (wsVisual && wsVisual.readyState === WebSocket.OPEN) {
                    wsVisual.send(pixelArray.buffer);
                }
            };

            processFrame();

        } catch (err) {
            console.error('Failed to access webcam:', err);
            const span = visionBtn.querySelector('span');
            if(span) span.textContent = 'ERROR';
            if (statusEl) {
                statusEl.textContent = 'Webcam Error';
                statusEl.style.color = '#ff4a4a';
            }
            setTimeout(() => {
                const span = visionBtn.querySelector('span');
                if(span) span.textContent = 'Vision';
                visionBtn.classList.remove('active');
            }, 2000);
        }
    });
}

// Removed Auto-start logic to prevent browser permission deadlocks

// Manual Peer Link Handler
const btnLinkPeer = /** @type {any} */ (document.getElementById('btn-link-peer'));
if (btnLinkPeer) {
    btnLinkPeer.addEventListener('click', async () => {
        const inputEl = /** @type {any} */ (document.getElementById('input-text'));
        const ipPort = inputEl.value.trim();
        if (!ipPort) return;
        
        const originalText = btnLinkPeer.textContent;
        btnLinkPeer.textContent = '...';
        /** @type {any} */ (btnLinkPeer).disabled = true;
        
        try {
            await fetch('/api/add-peer', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ url: ipPort.startsWith('http') ? ipPort : 'http://' + ipPort })
            });
            inputEl.value = '';
            btnLinkPeer.textContent = 'Linked!';
            setTimeout(() => {
                btnLinkPeer.textContent = originalText;
                /** @type {any} */ (btnLinkPeer).disabled = false;
            }, 2000);
        } catch (e) {
            console.error(e);
            btnLinkPeer.textContent = 'Error';
            setTimeout(() => {
                btnLinkPeer.textContent = originalText;
                /** @type {any} */ (btnLinkPeer).disabled = false;
            }, 2000);
        }
    });
}

// --- UI Collapsible Sections Logic ---
document.querySelectorAll('.section-title').forEach(title => {
    title.addEventListener('click', () => {
        title.classList.toggle('collapsed');
        const content = title.nextElementSibling;
        if (content && content.classList.contains('section-content')) {
            content.classList.toggle('collapsed');
        }
    });
});

// --- UI Presence Heartbeat ---
// Let the backend know we are actively looking at the dashboard.
// This allows the SDO to suppress its physical embodiment (keyboard/mouse) so it doesn't fight the user.
setInterval(() => {
    if (document.visibilityState === 'visible') {
        fetch('/api/presence', { method: 'POST' }).catch(e => {});
    }
}, 5000);
