(function () {
    "use strict";

    // DOM elements
    var chatHistory = document.getElementById("chat-history");
    var micBtn = document.getElementById("mic-btn");
    var micLabel = document.getElementById("mic-label");
    var deviceSelect = document.getElementById("device-select");
    var audioPlayer = document.getElementById("audio-player");
    var connectionDot = document.querySelector(".status-dot");
    var connectionText = document.getElementById("connection-text");
    var toastEl = document.getElementById("toast");
    var hamburgerBtn = document.getElementById("hamburger-btn");
    var sidebar = document.getElementById("sidebar");
    var sidebarOverlay = document.getElementById("sidebar-overlay");
    var activeAgentBadge = document.getElementById("active-agent-badge");

    // Streaming state
    var ws = null;
    var audioContext = null;
    var workletNode = null;
    var localStream = null;
    var isStreaming = false;
    var isPaused = false;

    // In-progress streaming AI bubble reference
    var streamingBubble = null;
    var streamingRow = null;

    // When true, ignore incoming audio messages (set on interrupt, cleared on next "speaking" status)
    var audioInterrupted = false;

    // Transport mode: "websocket" or "webrtc"
    var transportMode = "websocket";

    // --- Utilities ---

    function toast(message, type) {
        toastEl.textContent = message;
        toastEl.className = "toast " + type;
        clearTimeout(toastEl._timer);
        toastEl._timer = setTimeout(function () {
            toastEl.classList.add("hidden");
        }, 3000);
    }

    function addMessage(text, role) {
        var row = document.createElement("div");
        row.className = "chat-row " + role;

        var avatar = document.createElement("div");
        avatar.className = "chat-avatar " + role;
        if (role === "user") {
            avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2"/></svg>';
        } else {
            avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 2a4 4 0 014 4v1a4 4 0 01-8 0V6a4 4 0 014-4z" stroke="currentColor" stroke-width="2"/><path d="M8 10v1a4 4 0 008 0v-1M6 18c0-2 2.7-4 6-4s6 2 6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9" cy="7" r="1" fill="currentColor"/><circle cx="15" cy="7" r="1" fill="currentColor"/></svg>';
        }

        var bubble = document.createElement("div");
        bubble.className = "chat-msg " + role;
        bubble.textContent = text;

        row.appendChild(avatar);
        row.appendChild(bubble);
        chatHistory.appendChild(row);
        chatHistory.scrollTop = chatHistory.scrollHeight;
    }

    function setPipelineStage(stage) {
        var stages = document.querySelectorAll(".pipeline-stage");
        stages.forEach(function (el) {
            el.classList.toggle("active", el.dataset.stage === stage);
        });
    }

    function clearPipeline() {
        document.querySelectorAll(".pipeline-stage").forEach(function (el) {
            el.classList.remove("active");
        });
    }

    function showMetrics(data) {
        var parts = [];
        if (data.stt_ms != null) parts.push("STT " + Math.round(data.stt_ms) + "ms");
        if (data.llm_first_token_ms != null) parts.push("LLM first token " + Math.round(data.llm_first_token_ms) + "ms");
        if (data.llm_ms != null) parts.push("LLM " + Math.round(data.llm_ms) + "ms");
        if (data.tts_ms != null) parts.push("TTS " + Math.round(data.tts_ms) + "ms");
        if (data.total_ms != null) parts.push("Total " + Math.round(data.total_ms) + "ms");
        if (parts.length === 0) return;

        var row = document.createElement("div");
        row.className = "chat-row metrics";
        var badge = document.createElement("div");
        badge.className = "metrics-badge";
        badge.textContent = parts.join(" · ");
        row.appendChild(badge);
        chatHistory.appendChild(row);
        chatHistory.scrollTop = chatHistory.scrollHeight;
    }

    function setConnectionStatus(connected) {
        connectionDot.className = "status-dot " + (connected ? "connected" : "disconnected");
        connectionText.textContent = connected ? "Connected" : "Disconnected";
    }

    function showInterimTranscription(text) {
        var existing = chatHistory.querySelector(".chat-row.interim");
        if (existing) {
            existing.querySelector(".chat-msg").textContent = text;
        } else {
            var row = document.createElement("div");
            row.className = "chat-row user interim";
            var avatar = document.createElement("div");
            avatar.className = "chat-avatar user";
            avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2"/></svg>';
            var bubble = document.createElement("div");
            bubble.className = "chat-msg user";
            bubble.textContent = text;
            row.appendChild(avatar);
            row.appendChild(bubble);
            chatHistory.appendChild(row);
        }
        chatHistory.scrollTop = chatHistory.scrollHeight;
    }

    function removeInterimTranscription() {
        var existing = chatHistory.querySelector(".chat-row.interim");
        if (existing) existing.remove();
    }

    function arrayBufferToBase64(buffer) {
        var bytes = new Uint8Array(buffer);
        var binary = "";
        for (var i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return btoa(binary);
    }

    // --- Shared server message handler ---

    function handleServerMessage(data) {
        if (data.type === "status") {
            setPipelineStage(data.state);
            if (data.vad === "speech_start") {
                micBtn.classList.add("hearing");
                micLabel.textContent = "Hearing you...";
            } else if (data.state === "paused") {
                micBtn.classList.remove("hearing");
                micLabel.textContent = "Paused";
            } else if (data.state === "listening") {
                micBtn.classList.remove("hearing");
                if (!isPaused) {
                    micLabel.textContent = "Listening... (say something)";
                }
            } else if (data.state === "transcribing" || data.state === "thinking" || data.state === "speaking") {
                if (data.state === "speaking") audioInterrupted = false;
                micBtn.classList.remove("hearing");
                micLabel.textContent = "Processing...";
            }
        } else if (data.type === "interim_transcription") {
            showInterimTranscription(data.text);
        } else if (data.type === "transcription") {
            removeInterimTranscription();
            addMessage(data.text, "user");
        } else if (data.type === "llm_response") {
            // Non-streaming fallback — full response at once
            addMessage(data.text, "ai");
        } else if (data.type === "llm_response_chunk") {
            // Streaming: append token to in-progress AI bubble
            if (!streamingBubble) {
                streamingRow = document.createElement("div");
                streamingRow.className = "chat-row ai";
                var avatar = document.createElement("div");
                avatar.className = "chat-avatar ai";
                avatar.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 2a4 4 0 014 4v1a4 4 0 01-8 0V6a4 4 0 014-4z" stroke="currentColor" stroke-width="2"/><path d="M8 10v1a4 4 0 008 0v-1M6 18c0-2 2.7-4 6-4s6 2 6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9" cy="7" r="1" fill="currentColor"/><circle cx="15" cy="7" r="1" fill="currentColor"/></svg>';
                streamingBubble = document.createElement("div");
                streamingBubble.className = "chat-msg ai";
                streamingBubble.textContent = "";
                streamingRow.appendChild(avatar);
                streamingRow.appendChild(streamingBubble);
                chatHistory.appendChild(streamingRow);
            }
            streamingBubble.textContent += data.text;
            chatHistory.scrollTop = chatHistory.scrollHeight;
        } else if (data.type === "llm_response_done") {
            // Finalize the streaming bubble
            streamingBubble = null;
            streamingRow = null;
        } else if (data.type === "interrupt") {
            // Block further audio from this generation
            audioInterrupted = true;
            // Clear any in-progress streaming bubble
            streamingBubble = null;
            streamingRow = null;
            if (transportMode === "websocket") {
                stopPlayback();
            } else {
                // WebRTC: mute remote audio briefly, send playback_done
                if (window.WebRTCClient) {
                    window.WebRTCClient.muteRemoteAudio(300);
                    window.WebRTCClient.sendPlaybackDone();
                }
            }
            micLabel.textContent = "Interrupted — listening...";
            micLabel.classList.add("interrupted");
            setTimeout(function () {
                micLabel.classList.remove("interrupted");
            }, 1200);
        } else if (data.type === "audio") {
            // Ignore audio that arrives after an interrupt (stale from completed generation)
            if (audioInterrupted) return;
            // WebSocket audio (base64 WAV)
            queueAudio(data.audio);
        } else if (data.type === "metrics") {
            // Display pipeline timing metrics
            showMetrics(data);
        } else if (data.type === "playback_complete") {
            // WebRTC: server finished sending TTS frames
            // Send playback_done after a short buffer for audio to finish playing
            if (window.WebRTCClient) {
                setTimeout(function () {
                    window.WebRTCClient.sendPlaybackDone();
                }, 200);
            }
        }
    }

    // --- Clear chat ---

    document.getElementById("clear-chat-btn").addEventListener("click", function () {
        chatHistory.innerHTML = "";
        streamingBubble = null;
        streamingRow = null;
        audioInterrupted = false;
        // Clear audio queue
        audioQueue = [];
        isPlayingAudio = false;
        audioPlayer.pause();
        audioPlayer.src = "";
        toast("Conversation cleared", "success");
    });

    // --- Sidebar toggle (mobile) ---

    hamburgerBtn.addEventListener("click", function () {
        sidebar.classList.toggle("open");
        sidebarOverlay.classList.toggle("hidden", !sidebar.classList.contains("open"));
    });

    sidebarOverlay.addEventListener("click", function () {
        sidebar.classList.remove("open");
        sidebarOverlay.classList.add("hidden");
    });

    // --- Audio device enumeration ---

    async function enumerateDevices() {
        try {
            var devices = await navigator.mediaDevices.enumerateDevices();
            deviceSelect.innerHTML = "";
            var audioInputs = devices.filter(function (d) { return d.kind === "audioinput"; });
            audioInputs.forEach(function (d) {
                var opt = document.createElement("option");
                opt.value = d.deviceId;
                opt.textContent = d.label || "Microphone " + (deviceSelect.options.length + 1);
                deviceSelect.appendChild(opt);
            });
        } catch (e) {
            var opt = document.createElement("option");
            opt.textContent = "Default Mic";
            deviceSelect.appendChild(opt);
        }
    }

    // --- WebSocket Streaming ---

    async function startWebSocket() {
        // Show connecting state immediately
        micBtn.classList.add("connecting");
        micBtn.disabled = true;
        micLabel.textContent = "Connecting...";

        try {
            var constraints = { audio: true };
            var deviceId = deviceSelect.value;
            if (deviceId) {
                constraints.audio = { deviceId: { exact: deviceId } };
            }

            localStream = await navigator.mediaDevices.getUserMedia(constraints);

            var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
            var wsUrl = protocol + "//" + window.location.host + "/ws/voice-stream";
            ws = new WebSocket(wsUrl);

            ws.onopen = function () {
                micBtn.classList.remove("connecting");
                micBtn.disabled = false;
                micBtn.classList.add("active");
                setConnectionStatus(true);
                setPipelineStage("listening");
                micLabel.textContent = "Listening... (say something)";
                micLabel.classList.add("streaming");
                toast("Streaming connected", "success");
                startAudioProcessing();
            };

            ws.onclose = function () {
                setConnectionStatus(false);
                clearPipeline();
                if (isStreaming) {
                    disconnectWebSocket();
                }
            };

            ws.onerror = function () {
                micBtn.classList.remove("connecting");
                micBtn.disabled = false;
                toast("WebSocket connection failed", "error");
                disconnectWebSocket();
            };

            ws.onmessage = function (event) {
                var data;
                try {
                    data = JSON.parse(event.data);
                } catch (e) {
                    return;
                }
                handleServerMessage(data);
            };

            isStreaming = true;
            // Don't add .active yet — wait for ws.onopen to transition from connecting to active
        } catch (e) {
            micBtn.classList.remove("connecting");
            micBtn.disabled = false;
            toast("Mic access denied: " + e.message, "error");
            disconnectWebSocket();
        }
    }

    async function startAudioProcessing() {
        audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 48000 });
        await audioContext.audioWorklet.addModule("/static/audio-processor.js?v=5");
        var source = audioContext.createMediaStreamSource(localStream);
        workletNode = new AudioWorkletNode(audioContext, "voice-processor");

        workletNode.port.onmessage = function (e) {
            var b64 = arrayBufferToBase64(e.data);
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: "audio_chunk", audio: b64 }));
            }
        };

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

    function pauseWebSocket() {
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "pause_audio" }));
        }
        // Stop the worklet from sending audio but keep everything alive
        if (workletNode) {
            workletNode.port.postMessage({ type: "pause" });
        }
        if (localStream) {
            localStream.getAudioTracks().forEach(function (t) { t.enabled = false; });
        }
        isPaused = true;
        micBtn.classList.remove("hearing");
        micBtn.classList.add("paused");
        micLabel.textContent = "Paused";
    }

    function resumeWebSocket() {
        if (localStream) {
            localStream.getAudioTracks().forEach(function (t) { t.enabled = true; });
        }
        if (workletNode) {
            workletNode.port.postMessage({ type: "resume" });
        }
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "resume_audio" }));
        }
        isPaused = false;
        micBtn.classList.remove("paused");
        micLabel.textContent = "Listening... (say something)";
    }

    function disconnectWebSocket() {
        if (workletNode) {
            workletNode.disconnect();
            workletNode = null;
        }
        if (audioContext) {
            audioContext.close().catch(function () {});
            audioContext = null;
        }
        if (ws) {
            ws.close();
            ws = null;
        }
        if (localStream) {
            localStream.getTracks().forEach(function (t) { t.stop(); });
            localStream = null;
        }
        isStreaming = false;
        isPaused = false;
        micBtn.classList.remove("active");
        micBtn.classList.remove("hearing");
        micBtn.classList.remove("paused");
        micBtn.classList.remove("connecting");
        micBtn.disabled = false;
        micLabel.textContent = "Click to start";
        micLabel.classList.remove("streaming");
        setConnectionStatus(false);
        clearPipeline();
        hideDisconnectBtn();
    }

    // --- WebRTC Streaming ---

    async function startWebRTC() {
        if (!window.WebRTCClient) {
            toast("WebRTC client not loaded", "error");
            return;
        }

        // Show connecting state immediately
        micBtn.classList.add("connecting");
        micBtn.disabled = true;
        micLabel.textContent = "Establishing WebRTC connection...";

        try {
            var deviceId = deviceSelect.value;

            await window.WebRTCClient.connect(
                deviceId,
                function onMessage(data) {
                    handleServerMessage(data);
                },
                function onDisconnect() {
                    setConnectionStatus(false);
                    clearPipeline();
                    if (isStreaming) {
                        disconnectWebRTC();
                    }
                }
            );

            micBtn.classList.remove("connecting");
            micBtn.disabled = false;
            isStreaming = true;
            micBtn.classList.add("active");
            setConnectionStatus(true);
            setPipelineStage("listening");
            micLabel.textContent = "Listening... (say something)";
            micLabel.classList.add("streaming");
            toast("WebRTC connected", "success");
        } catch (e) {
            micBtn.classList.remove("connecting");
            micBtn.disabled = false;
            toast("WebRTC connection failed: " + e.message, "error");
            disconnectWebRTC();
        }
    }

    function pauseWebRTC() {
        if (window.WebRTCClient) {
            window.WebRTCClient.pause();
        }
        isPaused = true;
        micBtn.classList.remove("hearing");
        micBtn.classList.add("paused");
        micLabel.textContent = "Paused";
    }

    function resumeWebRTC() {
        if (window.WebRTCClient) {
            window.WebRTCClient.resume();
        }
        isPaused = false;
        micBtn.classList.remove("paused");
        micLabel.textContent = "Listening... (say something)";
    }

    function disconnectWebRTC() {
        if (window.WebRTCClient) {
            window.WebRTCClient.disconnect();
        }
        isStreaming = false;
        isPaused = false;
        micBtn.classList.remove("active");
        micBtn.classList.remove("hearing");
        micBtn.classList.remove("paused");
        micBtn.classList.remove("connecting");
        micBtn.disabled = false;
        micLabel.textContent = "Click to start";
        micLabel.classList.remove("streaming");
        setConnectionStatus(false);
        clearPipeline();
        hideDisconnectBtn();
    }

    // --- Transport-aware start/stop/pause/resume ---

    function startStreaming() {
        if (transportMode === "webrtc") {
            startWebRTC();
        } else {
            startWebSocket();
        }
        showDisconnectBtn();
    }

    function pauseStreaming() {
        if (transportMode === "webrtc") {
            pauseWebRTC();
        } else {
            pauseWebSocket();
        }
    }

    function resumeStreaming() {
        if (transportMode === "webrtc") {
            resumeWebRTC();
        } else {
            resumeWebSocket();
        }
    }

    function disconnectStreaming() {
        if (transportMode === "webrtc") {
            disconnectWebRTC();
        } else {
            disconnectWebSocket();
        }
    }

    // Audio queue for sequential playback of chunked TTS (WebSocket only)
    var audioQueue = [];
    var isPlayingAudio = false;

    function stopPlayback() {
        audioQueue = [];
        isPlayingAudio = false;
        audioPlayer.pause();
        audioPlayer.onended = null;
        audioPlayer.onerror = null;
        audioPlayer.src = "";
        // Notify server that playback was stopped
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "playback_done" }));
        }
    }

    function queueAudio(base64) {
        audioQueue.push(base64);
        if (!isPlayingAudio) {
            playNextAudio();
        }
    }

    function playNextAudio() {
        if (audioQueue.length === 0) {
            isPlayingAudio = false;
            // Notify server that all audio finished playing
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: "playback_done" }));
            }
            return;
        }
        isPlayingAudio = true;
        var base64 = audioQueue.shift();
        var binary = atob(base64);
        var bytes = new Uint8Array(binary.length);
        for (var i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i);
        }
        var blob = new Blob([bytes], { type: "audio/wav" });
        var url = URL.createObjectURL(blob);
        audioPlayer.src = url;
        audioPlayer.onended = function () {
            URL.revokeObjectURL(url);
            playNextAudio();
        };
        audioPlayer.onerror = function () {
            URL.revokeObjectURL(url);
            playNextAudio();
        };
        audioPlayer.play().catch(function () { playNextAudio(); });
    }

    // --- Disconnect button ---

    var disconnectBtn = null;

    function showDisconnectBtn() {
        if (disconnectBtn) return;
        disconnectBtn = document.createElement("button");
        disconnectBtn.className = "disconnect-btn";
        disconnectBtn.title = "Disconnect";
        disconnectBtn.innerHTML = "&#x2715;";
        disconnectBtn.addEventListener("click", function (e) {
            e.stopPropagation();
            disconnectStreaming();
        });
        micBtn.parentNode.insertBefore(disconnectBtn, micBtn.nextSibling);
    }

    function hideDisconnectBtn() {
        if (disconnectBtn) {
            disconnectBtn.remove();
            disconnectBtn = null;
        }
    }

    // --- Mic button handler ---

    micBtn.addEventListener("click", function () {
        if (!isStreaming) {
            // Not connected — start
            startStreaming();
        } else if (isPaused) {
            // Paused — resume
            resumeStreaming();
        } else {
            // Active — pause
            pauseStreaming();
        }
    });

    // --- Activation loading overlay ---

    var activationMessages = [
        "Building your agent...",
        "Downloading models...",
        "Warming up the neural networks...",
        "Spicing things up...",
        "Teaching your agent to listen...",
        "Calibrating voice recognition...",
        "Almost there...",
        "Polishing the final bits...",
    ];

    var activationOverlay = null;
    var activationInterval = null;

    function showActivationLoading(agentName) {
        // Create overlay
        activationOverlay = document.createElement("div");
        activationOverlay.className = "activation-overlay";
        activationOverlay.innerHTML =
            '<div class="activation-card">' +
                '<div class="activation-spinner"></div>' +
                '<div class="activation-agent-name">Activating ' + agentName + '</div>' +
                '<div class="activation-message">' + activationMessages[0] + '</div>' +
            '</div>';
        document.body.appendChild(activationOverlay);

        // Cycle through messages
        var msgIdx = 0;
        var msgEl = activationOverlay.querySelector(".activation-message");
        activationInterval = setInterval(function () {
            msgIdx = (msgIdx + 1) % activationMessages.length;
            msgEl.textContent = activationMessages[msgIdx];
        }, 2200);
    }

    function hideActivationLoading() {
        if (activationInterval) {
            clearInterval(activationInterval);
            activationInterval = null;
        }
        if (activationOverlay) {
            activationOverlay.remove();
            activationOverlay = null;
        }
    }

    // --- Agents (sidebar) ---

    async function loadAgents() {
        var listEl = document.getElementById("agent-list");
        if (!listEl) return;
        try {
            var resp = await fetch("/api/agents");
            if (!resp.ok) return;
            var data = await resp.json();
            var agents = data.agents || data;
            if (!agents || agents.length === 0) {
                listEl.innerHTML = '<div class="agents-empty">No agents yet</div>';
                activeAgentBadge.textContent = "No agent";
                return;
            }
            listEl.innerHTML = "";
            var foundActive = false;
            agents.forEach(function (agent) {
                var isActive = agent.status === "active";
                if (isActive) {
                    foundActive = true;
                    activeAgentBadge.textContent = agent.name;
                    // Set transport mode from active agent
                    transportMode = agent.transport || "websocket";
                }
                // Build custom tooltip from pipeline config
                var pipe = agent.pipeline || {};
                var tooltipParts = [];
                if (pipe.stt) tooltipParts.push('<span class="tip-label">STT</span> ' + pipe.stt);
                if (pipe.llm) tooltipParts.push('<span class="tip-label">LLM</span> ' + pipe.llm);
                if (pipe.tts) tooltipParts.push('<span class="tip-label">TTS</span> ' + pipe.tts);
                if (pipe.vad) tooltipParts.push('<span class="tip-label">VAD</span> ' + pipe.vad);
                var transportLabel = agent.transport === "webrtc" ? "WebRTC" : "WebSocket";
                tooltipParts.push('<span class="tip-label">Transport</span> ' + transportLabel);
                var tooltipHTML = tooltipParts.length
                    ? tooltipParts.join('<span class="tip-sep"></span>')
                    : "No models configured";

                var item = document.createElement("div");
                item.className = "agent-item" + (isActive ? " active-agent" : "");
                item.innerHTML =
                    '<span class="agent-name"><span class="agent-active-dot"></span>' + agent.name + "</span>" +
                    '<span class="agent-actions">' +
                        '<button class="agent-delete-btn" title="Delete">&#x2715;</button>' +
                    "</span>" +
                    '<div class="agent-tooltip">' + tooltipHTML + '</div>';
                // Click agent row to activate
                item.addEventListener("click", function (e) {
                    if (e.target.closest(".agent-delete-btn")) return;
                    if (!isActive) activateAgent(agent.name);
                });
                item.querySelector(".agent-delete-btn").addEventListener("click", function (e) {
                    e.stopPropagation();
                    deleteAgent(agent.name);
                });
                listEl.appendChild(item);
            });
            if (!foundActive) {
                activeAgentBadge.textContent = "No agent";
            }
        } catch (e) {
            // Server may not support agents yet
        }
    }

    async function activateAgent(name) {
        // Stop any active streaming before switching agents
        if (isStreaming) {
            disconnectStreaming();
        }

        showActivationLoading(name);
        try {
            var resp = await fetch("/api/agents/" + encodeURIComponent(name) + "/activate", {
                method: "POST"
            });
            if (!resp.ok) throw new Error("Activation failed");
            var data = await resp.json();
            // Update transport mode from activation response
            transportMode = data.transport || "websocket";
            hideActivationLoading();
            toast("Agent '" + name + "' activated (" + (transportMode === "webrtc" ? "WebRTC" : "WebSocket") + ")", "success");
            await loadAgents();
        } catch (e) {
            hideActivationLoading();
            toast("Failed to activate agent: " + e.message, "error");
        }
    }

    async function deleteAgent(name) {
        try {
            var resp = await fetch("/api/agents/" + encodeURIComponent(name), {
                method: "DELETE"
            });
            if (!resp.ok) throw new Error("Delete failed");
            toast("Agent '" + name + "' deleted", "success");
            loadAgents();
        } catch (e) {
            toast("Failed to delete agent: " + e.message, "error");
        }
    }

    // --- Cleanup ---

    window.addEventListener("beforeunload", function () {
        if (isStreaming) disconnectStreaming();
    });

    // Expose for wizard interop
    window.RoohAI = {
        loadAgents: loadAgents,
        toast: toast,
        showActivationLoading: showActivationLoading,
        hideActivationLoading: hideActivationLoading,
        handleServerMessage: handleServerMessage,
        setTransportMode: function (mode) { transportMode = mode; },
        getTransportMode: function () { return transportMode; }
    };

    // --- Theme toggle ---

    var themeToggle = document.getElementById("theme-toggle");

    function applyTheme(theme) {
        document.documentElement.setAttribute("data-theme", theme);
        localStorage.setItem("roohai-theme", theme);
    }

    // Restore saved preference (default: dark)
    var savedTheme = localStorage.getItem("roohai-theme") || "light";
    applyTheme(savedTheme);

    if (themeToggle) {
        themeToggle.addEventListener("click", function () {
            var current = document.documentElement.getAttribute("data-theme");
            applyTheme(current === "dark" ? "light" : "dark");
        });
    }

    // --- Init ---

    enumerateDevices();
    loadAgents();
})();
