/* App state management, initialization, theme system */

const App = (() => {
    const state = {
        currentConversationId: null,
        currentConversationType: 'chat',
        currentSpaceId: sessionStorage.getItem('anteroom_space_id') || null,
        currentDatabase: null,
        isStreaming: false,
        isPlanMode: false,
        isReadOnly: false,
        availableModels: [],
        databases: [],
        clientId: crypto.randomUUID(),
    };

    let _eventSource = null;
    let _esConnectedAt = 0;
    let _esFailCount = 0;
    let _recovering = false;
    const _shownApprovalIds = new Set();
    const _debugMode = new URLSearchParams(window.location.search).has('debug');

    function _debugLog(component, message, data) {
        if (!_debugMode) return;
        const ts = new Date().toISOString().slice(11, 23);
        if (data !== undefined) {
            console.debug(`[anteroom ${ts}] ${component}: ${message}`, data);
        } else {
            console.debug(`[anteroom ${ts}] ${component}: ${message}`);
        }
    }

    // --- Theme System ---

    const THEMES = {
        midnight: { label: 'Midnight', colors: ['#0f1117', '#1a1d27', '#3b82f6'] },
        dawn:     { label: 'Dawn',     colors: ['#faf8f5', '#f2efe9', '#7c5cbf'] },
        aurora:   { label: 'Aurora',    colors: ['#090b10', '#12151e', '#7c3aed'] },
        ember:    { label: 'Ember',     colors: ['#171210', '#211a16', '#e8913a'] },
    };

    function _migrateLocalStorage() {
        const migrations = [
            ['parlor_theme', 'anteroom_theme'],
            ['parlor_stream_raw_mode', 'anteroom_stream_raw_mode'],
        ];
        migrations.forEach(([oldKey, newKey]) => {
            if (localStorage.getItem(oldKey) !== null && localStorage.getItem(newKey) === null) {
                localStorage.setItem(newKey, localStorage.getItem(oldKey));
                localStorage.removeItem(oldKey);
            }
        });
    }

    function getTheme() {
        return localStorage.getItem('anteroom_theme') || 'midnight';
    }

    function setTheme(name) {
        if (!THEMES[name]) return;
        document.documentElement.setAttribute('data-theme', name);
        localStorage.setItem('anteroom_theme', name);
        _updateThemePicker();
    }

    function _updateThemePicker() {
        const picker = document.getElementById('theme-picker');
        if (!picker) return;
        const current = getTheme();
        picker.querySelectorAll('.theme-card').forEach(card => {
            card.classList.toggle('active', card.dataset.theme === current);
        });
    }

    function _renderThemePicker() {
        const picker = document.getElementById('theme-picker');
        if (!picker) return;
        picker.innerHTML = '';
        const current = getTheme();

        Object.entries(THEMES).forEach(([key, theme]) => {
            const card = document.createElement('div');
            card.className = 'theme-card' + (key === current ? ' active' : '');
            card.dataset.theme = key;

            const preview = document.createElement('div');
            preview.className = 'theme-preview';
            theme.colors.forEach(color => {
                const swatch = document.createElement('span');
                swatch.style.background = color;
                preview.appendChild(swatch);
            });

            const label = document.createElement('span');
            label.className = 'theme-card-label';
            label.textContent = theme.label;

            card.appendChild(preview);
            card.appendChild(label);
            card.addEventListener('click', () => setTheme(key));
            picker.appendChild(card);
        });
    }

    // --- Mobile Sidebar ---

    function _initMobileSidebar() {
        const menuBtn = document.getElementById('mobile-menu-btn');
        const sidebar = document.getElementById('sidebar');
        const overlay = document.getElementById('sidebar-overlay');

        if (menuBtn) {
            menuBtn.addEventListener('click', () => {
                sidebar.classList.toggle('open');
                overlay.classList.toggle('active', sidebar.classList.contains('open'));
            });
        }

        if (overlay) {
            overlay.addEventListener('click', () => {
                sidebar.classList.remove('open');
                overlay.classList.remove('active');
            });
        }
    }

    // --- CSRF & API ---

    function _getCsrfToken() {
        const match = document.cookie.split('; ').find(c => c.startsWith('anteroom_csrf='));
        return match ? match.split('=')[1] : '';
    }

    function _handle401() {
        if (_recovering) return;

        const now = Date.now();
        const key = '_anteroom_401_ts';
        const retryKey = '_anteroom_401_retries';
        const prev = parseInt(sessionStorage.getItem(key) || '0', 10);
        const retries = parseInt(sessionStorage.getItem(retryKey) || '0', 10);
        sessionStorage.setItem(key, String(now));

        if (now - prev < 5000) {
            // Rapid 401s detected — check if we should retry or give up
            if (retries >= 3) {
                // Too many retries — show banner with manual retry button
                if (!document.getElementById('auth-error-banner')) {
                    const banner = document.createElement('div');
                    banner.id = 'auth-error-banner';
                    banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:10000;background:var(--error,#dc2626);color:#fff;text-align:center;padding:12px 16px;font-size:14px;display:flex;justify-content:center;align-items:center;gap:12px;';
                    const msg = document.createElement('span');
                    msg.textContent = 'Session expired. Could not recover automatically.';
                    const btn = document.createElement('button');
                    btn.textContent = 'Retry';
                    btn.style.cssText = 'background:#fff;color:#dc2626;border:none;border-radius:4px;padding:4px 12px;cursor:pointer;font-weight:600;';
                    btn.onclick = () => {
                        _recovering = true;
                        sessionStorage.removeItem(key);
                        sessionStorage.removeItem(retryKey);
                        banner.remove();
                        window.location.href = '/';
                    };
                    banner.appendChild(msg);
                    banner.appendChild(btn);
                    document.body.appendChild(banner);
                }
                return;
            }
            // Auto-retry after a delay
            _recovering = true;
            sessionStorage.setItem(retryKey, String(retries + 1));
            setTimeout(() => { window.location.href = '/'; }, 2000);
            return;
        }
        // First 401 — reset retry counter and redirect immediately
        _recovering = true;
        sessionStorage.setItem(retryKey, '0');
        window.location.href = '/';
    }

    async function api(url, options = {}) {
        if (state.currentDatabase) {
            const sep = url.includes('?') ? '&' : '?';
            url += `${sep}db=${encodeURIComponent(state.currentDatabase)}`;
        }
        options.credentials = 'same-origin';
        if (!options.headers) options.headers = {};
        options.headers['X-Client-Id'] = state.clientId;
        if (['POST', 'PATCH', 'PUT', 'DELETE'].includes((options.method || '').toUpperCase())) {
            options.headers['X-CSRF-Token'] = _getCsrfToken();
        }
        const response = await fetch(url, options);
        if (response.status === 401) {
            _handle401();
            throw new Error('Session expired');
        }
        if (response.status === 429) {
            Chat.showToast('Rate limited — please wait a moment before retrying.');
            throw new Error('Rate limited');
        }
        if (!response.ok) {
            const err = await response.json().catch(() => ({ detail: response.statusText }));
            throw new Error(err.detail || `HTTP ${response.status}`);
        }
        if (response.status === 204) return null;
        const ct = response.headers.get('content-type') || '';
        if (ct.includes('application/json')) {
            return response.json();
        }
        return response;
    }

    // --- Init ---

    async function init() {
        _migrateLocalStorage();
        Chat.init();
        Canvas.init();
        Sources.init();
        if (typeof Workflows !== 'undefined') Workflows.init();
        Sidebar.init();
        Palette.init();
        Attachments.init();
        initRawToggle();
        _initPlanToggle();
        _initMobileSidebar();
        _renderThemePicker();

        // Offline detection
        const offlineBanner = document.getElementById('offline-banner');
        window.addEventListener('offline', () => {
            if (offlineBanner) offlineBanner.style.display = '';
        });
        window.addEventListener('online', () => {
            if (offlineBanner) offlineBanner.style.display = 'none';
        });
        if (!navigator.onLine && offlineBanner) {
            offlineBanner.style.display = '';
        }

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.shiftKey && e.key === 'N') {
                e.preventDefault();
                newConversation();
            }
            if (e.key === 'Escape' && state.isStreaming) {
                Chat.stopGeneration();
            }
        });

        // Settings modal
        initSettings();

        // MCP modal
        _initMcpModal();

        // Check config status and cache available models
        try {
            const config = await api('/api/config');
            if (!config.ai || !config.ai.base_url) {
                document.getElementById('setup-banner').style.display = '';
            }
            if (config.mcp_servers && config.mcp_servers.length > 0) {
                const connected = config.mcp_servers.filter(s => s.status === 'connected');
                const totalTools = connected.reduce((sum, s) => sum + s.tool_count, 0);
                document.getElementById('mcp-status').textContent =
                    `${totalTools} tools / ${connected.length} servers`;
            }
            if (config.identity && config.identity.user_id) {
                App.state.localUserId = config.identity.user_id;
            }
            if (config.read_only) {
                App.state.isReadOnly = true;
                const banner = document.getElementById('read-only-banner');
                if (banner) banner.style.display = '';
            }
        } catch {
            // Config endpoint may not exist yet
        }

        // Fetch available models for all model selectors
        await refreshModels();

        initModelSelector();

        // Load databases
        _initDbModal();
        await loadDatabases();
        document.getElementById('btn-db-add').addEventListener('click', addDatabase);

        // Load spaces
        _initSpaceModal();
        await loadSpaces();
        document.getElementById('btn-space-add').addEventListener('click', () => _openSpaceModal(null));

        // Read URL params for shared DB links
        const urlParams = _readUrlParams();

        // Load conversations
        await Sidebar.refresh();

        // Load conversation from URL param, or most recent.
        // Sidebar.refresh() above already fetched conversations — reuse via getConversations()
        // to avoid a redundant /api/conversations request.
        if (urlParams.conversationId) {
            try {
                await loadConversation(urlParams.conversationId);
            } catch {
                const cached = Sidebar.getConversations();
                if (cached && cached.length > 0) {
                    await loadConversation(cached[0].id);
                }
            }
        } else {
            const cached = Sidebar.getConversations();
            if (cached && cached.length > 0) {
                await loadConversation(cached[0].id);
            } else {
                _connectEventSource();
            }
        }
    }

    function initRawToggle() {
        const topbar = document.getElementById('chat-topbar');
        const btn = document.createElement('button');
        btn.className = 'btn-raw-toggle' + (Chat.isRawMode() ? ' active' : '');
        btn.title = 'Toggle raw text during streaming';
        btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span>Raw</span>';
        btn.addEventListener('click', () => {
            const newVal = !Chat.isRawMode();
            Chat.setRawMode(newVal);
            btn.classList.toggle('active', newVal);
        });
        topbar.appendChild(btn);
    }

    function _initPlanToggle() {
        const btn = document.getElementById('btn-plan-toggle');
        if (!btn) return;
        btn.addEventListener('click', () => {
            const newVal = !state.isPlanMode;
            setPlanMode(newVal);
        });

        // Plan panel close button
        const closeBtn = document.getElementById('plan-close');
        if (closeBtn) closeBtn.addEventListener('click', () => setPlanMode(false));

        // Plan approve/reject buttons
        const approveBtn = document.getElementById('plan-approve');
        const rejectBtn = document.getElementById('plan-reject');
        if (approveBtn) approveBtn.addEventListener('click', _approvePlan);
        if (rejectBtn) rejectBtn.addEventListener('click', _rejectPlan);
    }

    function setPlanMode(active) {
        state.isPlanMode = active;
        const btn = document.getElementById('btn-plan-toggle');
        const panel = document.getElementById('plan-panel');
        const chatMain = document.querySelector('.chat-main');
        if (btn) btn.classList.toggle('active', active);
        if (panel) panel.style.display = active ? '' : 'none';
        if (chatMain) chatMain.classList.toggle('with-plan', active);

        // Reset plan panel content when entering plan mode
        if (active) {
            const emptyEl = document.getElementById('plan-empty');
            const contentEl = document.getElementById('plan-content');
            const footerEl = document.getElementById('plan-footer');
            if (emptyEl) emptyEl.style.display = '';
            if (contentEl) { contentEl.style.display = 'none'; contentEl.innerHTML = ''; }
            if (footerEl) footerEl.style.display = 'none';

            // Check if a plan already exists for current conversation
            if (state.currentConversationId) {
                _loadExistingPlan(state.currentConversationId);
            }
        }
    }

    async function _loadExistingPlan(conversationId) {
        try {
            const resp = await api(`/api/conversations/${encodeURIComponent(conversationId)}/plan`);
            if (resp && resp.exists && resp.content) {
                _showPlanContent(resp.content);
            }
        } catch { /* no plan yet */ }
    }

    function _showPlanContent(content) {
        const emptyEl = document.getElementById('plan-empty');
        const contentEl = document.getElementById('plan-content');
        const footerEl = document.getElementById('plan-footer');
        const badge = document.getElementById('plan-status-badge');
        if (emptyEl) emptyEl.style.display = 'none';
        if (contentEl) {
            contentEl.style.display = '';
            contentEl.innerHTML = Chat.renderMarkdown(content);
            Chat.highlightCode(contentEl);
        }
        if (footerEl) footerEl.style.display = '';
        if (badge) { badge.textContent = 'Ready'; badge.className = 'plan-status-badge plan-status-ready'; }
    }

    async function _approvePlan() {
        const convId = state.currentConversationId;
        if (!convId) return;
        const approveBtn = document.getElementById('plan-approve');
        const rejectBtn = document.getElementById('plan-reject');
        if (approveBtn) approveBtn.disabled = true;
        if (rejectBtn) rejectBtn.disabled = true;

        try {
            const resp = await api(`/api/conversations/${encodeURIComponent(convId)}/plan/approve`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({}),
            });
            if (resp && resp.content) {
                // Exit plan mode and send the approved plan as a follow-up message
                setPlanMode(false);
                const approvedPrompt = `The following plan has been approved. Execute it step by step:\n\n${resp.content}`;
                Chat.sendPlanExecution(approvedPrompt);
            }
        } catch (err) {
            if (approveBtn) approveBtn.disabled = false;
            if (rejectBtn) rejectBtn.disabled = false;
            Chat.showToast('Failed to approve plan: ' + err.message);
        }
    }

    async function _rejectPlan() {
        const convId = state.currentConversationId;
        if (!convId) return;
        const approveBtn = document.getElementById('plan-approve');
        const rejectBtn = document.getElementById('plan-reject');
        if (approveBtn) approveBtn.disabled = true;
        if (rejectBtn) rejectBtn.disabled = true;

        try {
            await api(`/api/conversations/${encodeURIComponent(convId)}/plan/reject`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({}),
            });
            // Reset plan panel to empty state
            const emptyEl = document.getElementById('plan-empty');
            const contentEl = document.getElementById('plan-content');
            const footerEl = document.getElementById('plan-footer');
            const badge = document.getElementById('plan-status-badge');
            if (emptyEl) { emptyEl.style.display = ''; emptyEl.querySelector('p').textContent = 'Plan rejected. Send a new message to create another plan.'; }
            if (contentEl) { contentEl.style.display = 'none'; contentEl.innerHTML = ''; }
            if (footerEl) footerEl.style.display = 'none';
            if (badge) { badge.textContent = ''; badge.className = 'plan-status-badge'; }
        } catch (err) {
            if (approveBtn) approveBtn.disabled = false;
            if (rejectBtn) rejectBtn.disabled = false;
            Chat.showToast('Failed to reject plan: ' + err.message);
        }
    }

    let _currentModel = '';

    function initModelSelector() {
        const btn = document.getElementById('model-selector-btn');
        const dropdown = document.getElementById('model-selector-dropdown');

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            const isOpen = dropdown.classList.contains('open');
            dropdown.classList.toggle('open');
            btn.classList.toggle('active');
            if (!isOpen) _renderModelDropdown();
        });

        document.addEventListener('click', () => {
            dropdown.classList.remove('open');
            btn.classList.remove('active');
        });

        dropdown.addEventListener('click', (e) => e.stopPropagation());
    }

    function _renderModelDropdown() {
        const dropdown = document.getElementById('model-selector-dropdown');
        dropdown.innerHTML = '';

        const defaultItem = document.createElement('div');
        defaultItem.className = 'model-selector-item' + (!_currentModel ? ' selected' : '');
        defaultItem.textContent = 'Default model';
        defaultItem.addEventListener('click', () => _selectModel(''));
        dropdown.appendChild(defaultItem);

        state.availableModels.forEach(m => {
            const item = document.createElement('div');
            item.className = 'model-selector-item' + (m === _currentModel ? ' selected' : '');
            item.textContent = m;
            item.addEventListener('click', () => _selectModel(m));
            dropdown.appendChild(item);
        });
    }

    async function _selectModel(model) {
        _currentModel = model;
        document.getElementById('model-selector-label').textContent = model || 'Default model';
        document.getElementById('model-selector-dropdown').classList.remove('open');
        document.getElementById('model-selector-btn').classList.remove('active');

        if (!state.currentConversationId) return;
        try {
            await api(`/api/conversations/${state.currentConversationId}`, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ model }),
            });
        } catch {
            // ignore
        }
    }

    async function refreshModels() {
        try {
            const models = await api('/api/models');
            if (Array.isArray(models) && models.length > 0) {
                state.availableModels = models;
                return;
            }
        } catch { /* fall through */ }
        // Fallback: try the validate endpoint
        try {
            const validation = await api('/api/config/validate', { method: 'POST' });
            if (validation.models && validation.models.length > 0) {
                state.availableModels = validation.models.sort();
            }
        } catch { /* keep existing list */ }
    }

    function _populateModelSelect(selectEl, selectedValue, includeDefault) {
        selectEl.innerHTML = '';
        if (includeDefault) {
            const defaultOpt = document.createElement('option');
            defaultOpt.value = '';
            defaultOpt.textContent = 'Use global default';
            selectEl.appendChild(defaultOpt);
        }
        if (state.availableModels.length === 0) {
            if (!includeDefault) {
                const opt = document.createElement('option');
                opt.value = selectedValue || '';
                opt.textContent = selectedValue || 'No models available';
                selectEl.appendChild(opt);
            }
        } else {
            state.availableModels.forEach(m => {
                const opt = document.createElement('option');
                opt.value = m;
                opt.textContent = m;
                selectEl.appendChild(opt);
            });
        }
        selectEl.value = selectedValue || '';
    }

    async function newConversation(type) {
        const btn = document.getElementById('btn-new-chat');
        if (btn) btn.disabled = true;
        try {
            const convType = type || document.getElementById('new-conv-type').value || 'chat';
            const payload = { type: convType };
            if (state.currentSpaceId) payload.space_id = state.currentSpaceId;
            const opts = {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload),
            };
            const conv = await api('/api/conversations', opts);
            state.currentConversationId = conv.id;
            state.currentConversationType = convType;
            Chat.setConversationType(convType);
            Chat.abortStream();
            Chat.setStreaming(false);
            Chat.loadMessages([]);
            Canvas.resetCanvas();
            _currentModel = '';
            document.getElementById('model-selector-label').textContent = 'Default model';
            await Sidebar.refresh();
            Sidebar.setActive(conv.id);
            _updateUrl();
            _connectEventSource();
            document.getElementById('message-input').focus();
        } catch (err) {
            _debugLog('newConversation', 'Failed: ' + err.message);
            Chat.showToast('Failed to create conversation: ' + (err.message || 'check connection'));
        } finally {
            if (btn) btn.disabled = false;
        }
    }

    async function loadConversation(id) {
        try {
            Chat.abortStream();
            Chat.setStreaming(false);
            state.currentConversationId = id;
            const detail = await api(`/api/conversations/${id}`);
            state.currentConversationType = detail.type || 'chat';
            Chat.setConversationType(state.currentConversationType);
            Chat.loadMessages(detail.messages || []);
            Sidebar.setActive(id);
            _currentModel = detail.model || '';
            document.getElementById('model-selector-label').textContent = _currentModel || 'Default model';
            _updateUrl();
            _connectEventSource();
            Canvas.loadForConversation(id);
            if (state.isPlanMode) {
                _loadExistingPlan(id);
            }
            _checkStreamStatus(id);
        } catch (err) {
            _debugLog('loadConversation', 'Failed: ' + err.message);
            Chat.showToast('Failed to load conversation: ' + (err.message || 'check connection'));
        }
    }

    async function _checkStreamStatus(conversationId) {
        try {
            const status = await api(`/api/conversations/${conversationId}/stream-status`);
            if (status.active) {
                _debugLog('stream_status', 'Active stream detected, age=' + (status.age_seconds || 0) + 's');
                Chat.setStreaming(true);
                Chat.showThinkingFromEvent();
            }
        } catch {
            // ignore — endpoint may not exist on older server versions
        }
    }

    // --- URL Params ---

    function _readUrlParams() {
        const params = new URLSearchParams(window.location.search);
        const db = params.get('db');
        const c = params.get('c');
        if (db) state.currentDatabase = db;
        return { db, conversationId: c };
    }

    function _updateUrl() {
        const params = new URLSearchParams();
        if (state.currentDatabase) params.set('db', state.currentDatabase);
        if (state.currentConversationId) params.set('c', state.currentConversationId);
        const qs = params.toString();
        const newUrl = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
        if (newUrl !== window.location.pathname + window.location.search) {
            window.history.replaceState(null, '', newUrl);
        }
    }

    // --- Real-time Event Source ---

    function _connectEventSource() {
        if (_eventSource) {
            _eventSource.close();
            _eventSource = null;
        }

        // Clear only pending (unresolved) approval/ask_user prompts on reconnect;
        // preserve resolved cards so the user retains approval history (#864)
        Chat.cleanupPendingPrompts(_shownApprovalIds);

        const db = state.currentDatabase || 'personal';
        const _safeParam = (v) => /^[a-zA-Z0-9_-]+$/.test(v);
        if (!_safeParam(db)) return;
        let url = `/api/events?db=${encodeURIComponent(db)}&client_id=${encodeURIComponent(state.clientId)}`;
        if (state.currentConversationId) {
            url += `&conversation_id=${encodeURIComponent(state.currentConversationId)}`;
        }

        _eventSource = new EventSource(url);

        _eventSource.addEventListener('new_message', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            if (data.conversation_id === state.currentConversationId) {
                Chat.appendRemoteMessage(data.role, data.content);
            }
        });

        _eventSource.addEventListener('stream_start', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            if (data.conversation_id === state.currentConversationId) {
                Chat.startRemoteStream();
            }
        });

        _eventSource.addEventListener('stream_token', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            if (data.conversation_id === state.currentConversationId) {
                Chat.handleRemoteToken(data.content);
            }
        });

        _eventSource.addEventListener('stream_done', (e) => {
            const data = JSON.parse(e.data);
            if (data.conversation_id === state.currentConversationId) {
                // Always clear streaming state for the current conversation,
                // regardless of client_id. This recovers from scenarios where
                // _checkStreamStatus set isStreaming=true but no SSE reader
                // is running (e.g., page reload during active stream).
                Chat.setStreaming(false);
                if (data.client_id === state.clientId) return;
                Chat.finalizeRemoteStream();
            }
        });

        _eventSource.addEventListener('title_changed', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            Sidebar.updateTitle(data.conversation_id, data.title);
        });

        _eventSource.addEventListener('conversation_created', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            Sidebar.refresh();
        });

        _eventSource.addEventListener('conversation_deleted', (e) => {
            const data = JSON.parse(e.data);
            if (data.client_id === state.clientId) return;
            if (data.conversation_id === state.currentConversationId) {
                state.currentConversationId = null;
                Chat.loadMessages([]);
            }
            Sidebar.refresh();
        });

        _eventSource.addEventListener('approval_required', (e) => {
            const data = JSON.parse(e.data);
            if (data.conversation_id === state.currentConversationId) {
                if (_shownApprovalIds.has(data.approval_id)) return;
                _shownApprovalIds.add(data.approval_id);
                Chat.showApprovalPrompt(data);
            }
        });

        _eventSource.addEventListener('ask_user_required', (e) => {
            const data = JSON.parse(e.data);
            if (data.conversation_id === state.currentConversationId) {
                if (_shownApprovalIds.has(data.ask_id)) return;
                _shownApprovalIds.add(data.ask_id);
                Chat.showAskUserPrompt(data);
            }
        });

        _eventSource.addEventListener('approval_resolved', (e) => {
            const data = JSON.parse(e.data);
            _shownApprovalIds.delete(data.approval_id);
            Chat.resolveApprovalCard(data.approval_id, data.approved, data.reason);
        });

        _eventSource.addEventListener('ask_user_resolved', (e) => {
            const data = JSON.parse(e.data);
            _shownApprovalIds.delete(data.ask_id);
            Chat.resolveAskUserCard(data.ask_id, data.reason);
        });

        _eventSource.addEventListener('approval_executing', (e) => {
            const data = JSON.parse(e.data);
            if (data.conversation_id === state.currentConversationId) {
                Chat.showThinkingFromEvent();
            }
        });

        _eventSource.onopen = () => {
            _esConnectedAt = Date.now();
            _esFailCount = 0;
            _debugLog('sse', 'EventSource connected');
        };

        _eventSource.onerror = () => {
            _debugLog('sse', 'EventSource error, failCount=' + (_esFailCount + 1));
            if (_recovering) {
                if (_eventSource) { _eventSource.close(); _eventSource = null; }
                return;
            }

            const elapsed = Date.now() - _esConnectedAt;
            _esFailCount++;

            // Rapid failure (< 3s after connect) likely means 401.
            // After 5 consecutive rapid failures, trigger auth recovery.
            // The server sends retry: to control reconnect interval for
            // rate-limit scenarios; this guard is strictly for broken auth.
            if (elapsed < 3000 && _esFailCount >= 5) {
                if (_eventSource) {
                    _eventSource.close();
                    _eventSource = null;
                }
                _handle401();
                return;
            }

            // Normal reconnect with backoff (5s base, max 30s)
            const delay = Math.min(5000 * _esFailCount, 30000);
            setTimeout(() => {
                if (_eventSource && _eventSource.readyState === EventSource.CLOSED) {
                    _connectEventSource();
                }
            }, delay);
        };
    }

    // Close SSE connection and abort in-flight streams on page unload to free
    // HTTP connection slots.  Without this, Windows browsers (strict 6 connections
    // per origin on HTTP/1.1) can hang on page refresh because the old SSE
    // connection lingers.  Aborting the chat stream also lets the server-side
    // _poll_disconnect detect the closure quickly instead of waiting for a timeout.
    window.addEventListener('beforeunload', () => {
        Chat.abortStream();
        if (_eventSource) {
            _eventSource.close();
            _eventSource = null;
        }
    });

    // pagehide fires more reliably than beforeunload on mobile and some desktop
    // browsers (e.g. bfcache-based navigation).  Belt-and-suspenders with above.
    window.addEventListener('pagehide', () => {
        Chat.abortStream();
        if (_eventSource) {
            _eventSource.close();
            _eventSource = null;
        }
    });

    function formatTimestamp(iso) {
        const d = new Date(iso);
        const now = new Date();
        const diff = now - d;
        if (diff < 60000) return 'Just now';
        if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
        if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
        if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`;
        return d.toLocaleDateString();
    }

    function initSettings() {
        const modal = document.getElementById('settings-modal');
        const openBtn = document.getElementById('btn-settings');
        const closeBtn = document.getElementById('settings-close');
        const cancelBtn = document.getElementById('settings-cancel');
        const saveBtn = document.getElementById('settings-save');

        openBtn.addEventListener('click', openSettings);
        closeBtn.addEventListener('click', () => modal.style.display = 'none');
        cancelBtn.addEventListener('click', () => modal.style.display = 'none');
        saveBtn.addEventListener('click', saveSettings);
        modal.addEventListener('click', (e) => {
            if (e.target === modal) modal.style.display = 'none';
        });
    }

    async function openSettings() {
        const modal = document.getElementById('settings-modal');
        const modelSelect = document.getElementById('setting-model');
        const promptTextarea = document.getElementById('setting-system-prompt');

        _renderThemePicker();

        try {
            const config = await api('/api/config');
            promptTextarea.value = config.ai.system_prompt || '';

            modal.style.display = 'flex';
            await refreshModels();
            _populateModelSelect(modelSelect, config.ai.model || '', false);
        } catch (e) {
            modal.style.display = 'flex';
        }
    }

    async function saveSettings() {
        const model = document.getElementById('setting-model').value;
        const systemPrompt = document.getElementById('setting-system-prompt').value;

        try {
            await api('/api/config', {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ model, system_prompt: systemPrompt }),
            });
            document.getElementById('settings-modal').style.display = 'none';
        } catch (e) {
            alert('Failed to save settings: ' + e.message);
        }
    }

    // --- Spaces ---
    let _spacesLoaded = false;
    let _loadedSpaces = [];

    async function loadSpaces() {
        const list = document.getElementById('space-list');
        const activeBar = document.getElementById('space-active-bar');
        const activeName = document.getElementById('space-active-name');
        const select = document.getElementById('space-select');
        if (!list || !select) return;

        let spaces = [];
        try {
            spaces = await api('/api/spaces');
            while (select.options.length > 1) select.remove(1);
            spaces.forEach(s => {
                const opt = document.createElement('option');
                opt.value = s.id;
                opt.textContent = s.name;
                select.appendChild(opt);
            });
            if (state.currentSpaceId) {
                if (spaces.some(s => s.id === state.currentSpaceId)) {
                    select.value = state.currentSpaceId;
                } else {
                    state.currentSpaceId = null;
                    sessionStorage.removeItem('anteroom_space_id');
                }
            }
        } catch {
            // ignore — spaces feature may not be available
        }

        _loadedSpaces = spaces;
        _renderSpaceList(spaces);

        if (!_spacesLoaded) {
            _spacesLoaded = true;
            const clearBtn = document.getElementById('btn-space-clear');
            if (clearBtn) {
                clearBtn.addEventListener('click', async () => {
                    state.currentSpaceId = null;
                    sessionStorage.removeItem('anteroom_space_id');
                    select.value = '';
                    state.currentConversationId = null;
                    Chat.loadMessages([]);
                    await loadSpaces();
                    await Sidebar.refresh();
                });
            }
            const editActiveBtn = document.getElementById('btn-space-edit-active');
            if (editActiveBtn) {
                editActiveBtn.addEventListener('click', () => {
                    const active = _loadedSpaces.find(s => s.id === state.currentSpaceId);
                    if (active && active.origin !== 'local') {
                        _openSpaceModal(active);
                    }
                });
            }
        }
    }

    function _renderSpaceList(spaces) {
        const list = document.getElementById('space-list');
        const activeBar = document.getElementById('space-active-bar');
        const activeName = document.getElementById('space-active-name');
        if (!list) return;
        list.innerHTML = '';

        // "All" item
        const allItem = document.createElement('div');
        allItem.className = 'space-item space-all' + (!state.currentSpaceId ? ' active' : '');
        allItem.innerHTML = '<span>All Spaces</span>';
        allItem.addEventListener('click', async () => {
            state.currentSpaceId = null;
            sessionStorage.removeItem('anteroom_space_id');
            document.getElementById('space-select').value = '';
            state.currentConversationId = null;
            Chat.loadMessages([]);
            await loadSpaces();
            await Sidebar.refresh();
        });
        list.appendChild(allItem);

        if (spaces.length === 0) {
            const hint = document.createElement('div');
            hint.className = 'space-empty-hint';
            hint.textContent = 'No spaces yet. Create one to organize your conversations.';
            list.appendChild(hint);
        }

        spaces.forEach(s => {
            const item = document.createElement('div');
            item.className = 'space-item' + (state.currentSpaceId === s.id ? ' active' : '');
            const nameSpan = document.createElement('span');
            nameSpan.className = 'space-item-name';
            nameSpan.textContent = s.name;
            if (s.origin) {
                const badge = document.createElement('span');
                badge.className = 'space-origin';
                badge.title = s.origin === 'local' ? 'Synced from a YAML file' : 'User-wide space';
                badge.textContent = s.origin === 'local' ? 'synced' : 'global';
                nameSpan.appendChild(badge);
            }
            item.appendChild(nameSpan);

            // Action buttons
            const actions = document.createElement('span');
            actions.className = 'space-item-actions';

            // Edit button (only for DB-created spaces without a source file)
            if (s.origin !== 'local') {
                const editBtn = document.createElement('button');
                editBtn.className = 'space-item-action';
                editBtn.title = 'Edit space';
                editBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
                editBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    _openSpaceModal(s);
                });
                actions.appendChild(editBtn);
            }

            // Delete button
            const deleteBtn = document.createElement('button');
            deleteBtn.className = 'space-item-action space-item-delete';
            deleteBtn.title = 'Delete space';
            deleteBtn.innerHTML = '&times;';
            deleteBtn.addEventListener('click', async (e) => {
                e.stopPropagation();
                if (!confirm(`Delete space "${s.name}"?`)) return;
                try {
                    const resp = await fetch(`/api/spaces/${encodeURIComponent(s.id)}`, {
                        method: 'DELETE',
                        credentials: 'same-origin',
                        headers: { 'X-CSRF-Token': _getCsrfToken() },
                    });
                    if (!resp.ok) {
                        const err = await resp.json().catch(() => ({}));
                        alert(err.detail || `Failed to delete space (${resp.status})`);
                        return;
                    }
                    if (state.currentSpaceId === s.id) {
                        state.currentSpaceId = null;
                        sessionStorage.removeItem('anteroom_space_id');
                    }
                    await loadSpaces();
                    await Sidebar.refresh();
                } catch (e) {
                    alert('Failed to delete space: ' + (e.message || 'network error'));
                }
            });
            actions.appendChild(deleteBtn);

            item.appendChild(actions);
            item.addEventListener('click', async () => {
                state.currentSpaceId = s.id;
                sessionStorage.setItem('anteroom_space_id', s.id);
                document.getElementById('space-select').value = s.id;
                state.currentConversationId = null;
                Chat.loadMessages([]);
                await loadSpaces();
                await Sidebar.refresh();
            });
            list.appendChild(item);
        });

        const chatIndicator = document.getElementById('chat-space-indicator');
        const chatIndicatorName = document.getElementById('chat-space-indicator-name');
        const chatIndicatorOrigin = document.getElementById('chat-space-indicator-origin');

        if (state.currentSpaceId) {
            const active = spaces.find(s => s.id === state.currentSpaceId);
            if (active && activeBar && activeName) {
                activeName.textContent = active.name;
                activeBar.style.display = 'flex';
                const editActiveBtn = document.getElementById('btn-space-edit-active');
                if (editActiveBtn) {
                    editActiveBtn.style.display = active.origin === 'local' ? 'none' : '';
                }
            }
            if (active && chatIndicator && chatIndicatorName) {
                chatIndicatorName.textContent = active.name;
                if (chatIndicatorOrigin) {
                    chatIndicatorOrigin.textContent = active.origin === 'local' ? 'synced' : (active.origin === 'global' ? 'global' : '');
                }
                chatIndicator.style.display = 'flex';
            }
        } else {
            if (activeBar) activeBar.style.display = 'none';
            if (chatIndicator) chatIndicator.style.display = 'none';
        }
    }

    function _openSpaceModal(space) {
        const modal = document.getElementById('space-modal');
        const title = document.getElementById('space-modal-title');
        const idInput = document.getElementById('space-modal-id');
        const nameInput = document.getElementById('space-name-input');
        const instructionsInput = document.getElementById('space-instructions-input');
        const modelInput = document.getElementById('space-model-input');
        const saveBtn = document.getElementById('space-modal-save');
        const sourcesSection = document.getElementById('space-sources-section');

        if (space) {
            title.textContent = 'Edit Space';
            saveBtn.textContent = 'Save';
            idInput.value = space.id;
            nameInput.value = space.name || '';
            instructionsInput.value = space.instructions || '';
            modelInput.value = space.model || '';
            if (sourcesSection) {
                sourcesSection.style.display = '';
                _loadSpaceSources(space.id);
            }
        } else {
            title.textContent = 'New Space';
            saveBtn.textContent = 'Create';
            idInput.value = '';
            nameInput.value = '';
            instructionsInput.value = '';
            modelInput.value = '';
            if (sourcesSection) sourcesSection.style.display = 'none';
        }
        modal.style.display = 'flex';
        nameInput.focus();
    }

    async function _loadSpaceSources(spaceId) {
        const listEl = document.getElementById('space-sources-list');
        const picker = document.getElementById('space-sources-picker');
        if (!listEl) return;
        listEl.innerHTML = '<div style="padding:8px;color:var(--text-muted);font-size:12px">Loading...</div>';

        try {
            const linked = await api(`/api/spaces/${encodeURIComponent(spaceId)}/sources?link_type=direct`);
            listEl.innerHTML = '';
            const linkedIds = new Set(linked.map(s => s.id));

            linked.forEach(src => {
                const row = document.createElement('div');
                row.className = 'space-source-row';
                const titleSpan = document.createElement('span');
                titleSpan.className = 'source-title';
                titleSpan.textContent = src.title || 'Untitled';
                const typeSpan = document.createElement('span');
                typeSpan.className = 'source-type';
                typeSpan.textContent = src.type || '';
                const unlinkBtn = document.createElement('button');
                unlinkBtn.className = 'btn-unlink';
                unlinkBtn.title = 'Unlink from space';
                unlinkBtn.textContent = '\u00d7';
                unlinkBtn.addEventListener('click', async () => {
                    try {
                        await api(`/api/spaces/${encodeURIComponent(spaceId)}/sources/${encodeURIComponent(src.id)}`, {
                            method: 'DELETE',
                        });
                        await _loadSpaceSources(spaceId);
                        Sources.refreshList();
                    } catch (err) {
                        unlinkBtn.textContent = '!';
                        unlinkBtn.title = 'Unlink failed';
                    }
                });
                row.appendChild(titleSpan);
                row.appendChild(typeSpan);
                row.appendChild(unlinkBtn);
                listEl.appendChild(row);
            });

            // Populate the picker with all sources not already linked
            if (picker) {
                const allSources = await api('/api/sources?limit=0');
                const sourceList = allSources.sources || [];
                picker.innerHTML = '<option value="">Link a source...</option>';
                sourceList.forEach(src => {
                    if (!linkedIds.has(src.id)) {
                        const opt = document.createElement('option');
                        opt.value = src.id;
                        opt.textContent = `${src.title || 'Untitled'} (${src.type || 'unknown'})`;
                        picker.appendChild(opt);
                    }
                });
                // Remove old listener by replacing node
                const newPicker = picker.cloneNode(true);
                picker.parentNode.replaceChild(newPicker, picker);
                newPicker.addEventListener('change', async () => {
                    const sourceId = newPicker.value;
                    if (!sourceId) return;
                    try {
                        await api(`/api/spaces/${encodeURIComponent(spaceId)}/sources`, {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ source_id: sourceId }),
                        });
                        await _loadSpaceSources(spaceId);
                        Sources.refreshList();
                    } catch (err) {
                        newPicker.value = '';
                    }
                });
            }
        } catch (err) {
            listEl.innerHTML = `<div style="padding:8px;color:var(--text-muted);font-size:12px">Failed to load sources.</div>`;
        }
    }

    function _initSpaceModal() {
        const modal = document.getElementById('space-modal');
        if (!modal) return;
        const closeBtn = document.getElementById('space-modal-close');
        const cancelBtn = document.getElementById('space-modal-cancel');
        const saveBtn = document.getElementById('space-modal-save');

        const closeModal = () => { modal.style.display = 'none'; };
        closeBtn.addEventListener('click', closeModal);
        cancelBtn.addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });

        saveBtn.addEventListener('click', async () => {
            const id = document.getElementById('space-modal-id').value;
            const name = document.getElementById('space-name-input').value.trim();
            const instructions = document.getElementById('space-instructions-input').value;
            const model = document.getElementById('space-model-input').value.trim();

            if (!name) { alert('Space name is required.'); return; }
            if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(name)) {
                alert('Invalid name. Must start with a letter or number, and contain only letters, numbers, hyphens, and underscores (max 64 chars).');
                return;
            }

            try {
                let resp;
                if (id) {
                    resp = await fetch(`/api/spaces/${encodeURIComponent(id)}`, {
                        method: 'PATCH',
                        credentials: 'same-origin',
                        headers: {
                            'Content-Type': 'application/json',
                            'X-CSRF-Token': _getCsrfToken(),
                        },
                        body: JSON.stringify({ name, instructions, model: model || null }),
                    });
                } else {
                    resp = await fetch('/api/spaces', {
                        method: 'POST',
                        credentials: 'same-origin',
                        headers: {
                            'Content-Type': 'application/json',
                            'X-CSRF-Token': _getCsrfToken(),
                        },
                        body: JSON.stringify({ name, instructions, model: model || null }),
                    });
                }
                if (!resp.ok) {
                    const err = await resp.json().catch(() => ({}));
                    alert(err.detail || `Failed to save space (${resp.status})`);
                    return;
                }
                modal.style.display = 'none';
                await loadSpaces();
            } catch (e) {
                alert('Failed to save space: ' + e.message);
            }
        });
    }

    // --- Databases ---

    async function loadDatabases() {
        try {
            state.databases = await api('/api/databases');
        } catch { /* ignore */ }
        _renderDatabaseList();
    }

    function _renderDatabaseList() {
        const list = document.getElementById('db-list');
        if (!list) return;
        list.innerHTML = '';

        const dbSvg = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>';

        state.databases.forEach(db => {
            const item = document.createElement('div');
            const isActive = (db.name === 'personal' && !state.currentDatabase) ||
                             db.name === state.currentDatabase;
            item.className = 'db-item' + (isActive ? ' active' : '');
            item.innerHTML = `<span class="db-item-icon">${dbSvg}</span><span class="db-item-name">${DOMPurify.sanitize(db.name)}</span>`;
            if (db.name !== 'personal') {
                const removeBtn = document.createElement('button');
                removeBtn.className = 'db-item-remove';
                removeBtn.title = 'Remove database';
                removeBtn.innerHTML = '&times;';
                removeBtn.addEventListener('click', async (e) => {
                    e.stopPropagation();
                    if (!confirm(`Remove database "${db.name}"? The database file will not be deleted.`)) return;
                    try {
                        await fetch(`/api/databases/${encodeURIComponent(db.name)}`, {
                            method: 'DELETE',
                            credentials: 'same-origin',
                            headers: { 'X-CSRF-Token': _getCsrfToken() },
                        });
                        if (state.currentDatabase === db.name) {
                            state.currentDatabase = null;
                        }
                        await loadDatabases();
                        await Sidebar.refresh();
                    } catch { /* ignore */ }
                });
                item.appendChild(removeBtn);
            }
            item.addEventListener('click', async () => {
                state.currentDatabase = db.name === 'personal' ? null : db.name;
                state.currentConversationId = null;
                Chat.loadMessages([]);
                _renderDatabaseList();
                _updateUrl();
                _connectEventSource();
                await Sidebar.refresh();
            });
            list.appendChild(item);
        });
    }

    function addDatabase() {
        const modal = document.getElementById('db-modal');
        const nameInput = document.getElementById('db-name-input');
        const pathInput = document.getElementById('db-path-input');
        const panel = document.getElementById('browse-panel');

        nameInput.value = '';
        pathInput.value = '';
        panel.style.display = 'none';
        modal.style.display = 'flex';
        nameInput.focus();
    }

    function _initDbModal() {
        const modal = document.getElementById('db-modal');
        const closeBtn = document.getElementById('db-modal-close');
        const cancelBtn = document.getElementById('db-modal-cancel');
        const saveBtn = document.getElementById('db-modal-save');
        const browseBtn = document.getElementById('btn-browse');

        const closeModal = () => { modal.style.display = 'none'; };
        closeBtn.addEventListener('click', closeModal);
        cancelBtn.addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });

        browseBtn.addEventListener('click', () => {
            const panel = document.getElementById('browse-panel');
            if (panel.style.display === 'none') {
                panel.style.display = 'flex';
                const current = document.getElementById('db-path-input').value.trim();
                _browseTo(current ? _parentDir(current) : '~');
            } else {
                panel.style.display = 'none';
            }
        });

        saveBtn.addEventListener('click', async () => {
            const name = document.getElementById('db-name-input').value.trim();
            const path = document.getElementById('db-path-input').value.trim();
            if (!name) { alert('Database name is required.'); return; }
            if (!path) { alert('Database path is required.'); return; }
            try {
                await fetch('/api/databases', {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': _getCsrfToken(),
                    },
                    body: JSON.stringify({ name, path }),
                });
                modal.style.display = 'none';
                await loadDatabases();
            } catch (e) {
                alert('Failed to add database: ' + e.message);
            }
        });
    }

    function _parentDir(path) {
        const parts = path.replace(/\\/g, '/').split('/');
        parts.pop();
        return parts.join('/') || '/';
    }

    async function _browseTo(dir) {
        const currentEl = document.getElementById('browse-current');
        const entriesEl = document.getElementById('browse-entries');

        currentEl.textContent = 'Loading...';
        entriesEl.innerHTML = '';

        try {
            const data = await api(`/api/browse?path=${encodeURIComponent(dir)}`);
            currentEl.textContent = data.current;

            if (data.parent) {
                const upEl = document.createElement('div');
                upEl.className = 'browse-entry browse-up';
                upEl.innerHTML = '<span class="browse-entry-icon">\u2190</span><span class="browse-entry-name">..</span>';
                upEl.addEventListener('click', () => _browseTo(data.parent));
                entriesEl.appendChild(upEl);
            }

            data.entries.forEach(entry => {
                const el = document.createElement('div');
                el.className = 'browse-entry ' + (entry.type === 'dir' ? 'is-dir' : 'is-file');

                const icon = entry.type === 'dir'
                    ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>'
                    : '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>';

                el.innerHTML = `<span class="browse-entry-icon">${icon}</span><span class="browse-entry-name">${DOMPurify.sanitize(entry.name)}</span>`;

                el.addEventListener('click', () => {
                    const fullPath = data.current + (data.current.endsWith('/') ? '' : '/') + entry.name;
                    if (entry.type === 'dir') {
                        _browseTo(fullPath);
                    } else {
                        document.getElementById('db-path-input').value = fullPath;
                        document.getElementById('browse-panel').style.display = 'none';
                    }
                });
                entriesEl.appendChild(el);
            });

            if (data.entries.length === 0 && !data.parent) {
                const empty = document.createElement('div');
                empty.className = 'browse-entry';
                empty.innerHTML = '<span class="browse-entry-name" style="color:var(--text-muted)">Empty directory</span>';
                entriesEl.appendChild(empty);
            }
        } catch (e) {
            currentEl.textContent = 'Error: ' + e.message;
        }
    }

    // --- MCP Modal ---

    function _initMcpModal() {
        const modal = document.getElementById('mcp-modal');
        const closeBtn = document.getElementById('mcp-modal-close');
        const statusEl = document.getElementById('mcp-status');

        if (closeBtn) {
            closeBtn.addEventListener('click', () => { modal.style.display = 'none'; });
        }
        if (modal) {
            modal.addEventListener('click', (e) => {
                if (e.target === modal) modal.style.display = 'none';
            });
        }
        if (statusEl) {
            statusEl.style.cursor = 'pointer';
            statusEl.addEventListener('click', openMcpModal);
        }
    }

    async function openMcpModal() {
        const modal = document.getElementById('mcp-modal');
        const listEl = document.getElementById('mcp-server-list');
        listEl.innerHTML = '<div style="color:var(--text-muted);text-align:center;padding:20px">Loading...</div>';
        modal.style.display = 'flex';

        try {
            const config = await api('/api/config');
            const servers = config.mcp_servers || [];
            if (servers.length === 0) {
                listEl.innerHTML = '<div style="color:var(--text-muted);text-align:center;padding:20px">No MCP servers configured.</div>';
                return;
            }
            _renderMcpServers(servers);
        } catch (e) {
            listEl.innerHTML = `<div style="color:var(--error);text-align:center;padding:20px">Failed to load: ${DOMPurify.sanitize(e.message)}</div>`;
        }
    }

    function _renderMcpServers(servers) {
        const listEl = document.getElementById('mcp-server-list');
        listEl.innerHTML = '';

        servers.forEach(srv => {
            const row = document.createElement('div');
            row.className = 'mcp-server-row';

            const info = document.createElement('div');
            info.className = 'mcp-server-info';

            const name = document.createElement('span');
            name.className = 'mcp-server-name';
            name.textContent = srv.name;

            const badge = document.createElement('span');
            badge.className = 'mcp-status-badge mcp-status-' + srv.status;
            badge.textContent = srv.status;

            const detail = document.createElement('span');
            detail.className = 'mcp-server-detail';
            let detailText = srv.transport;
            if (srv.status === 'connected') {
                detailText += ` \u2022 ${srv.tool_count} tool${srv.tool_count !== 1 ? 's' : ''}`;
            }
            if (srv.error_message) {
                detailText += ` \u2022 ${srv.error_message}`;
            }
            detail.textContent = detailText;

            info.appendChild(name);
            info.appendChild(badge);
            info.appendChild(detail);

            const actions = document.createElement('div');
            actions.className = 'mcp-server-actions';

            if (srv.status === 'connected') {
                const reconnectBtn = document.createElement('button');
                reconnectBtn.className = 'btn-mcp-action';
                reconnectBtn.textContent = 'Reconnect';
                reconnectBtn.addEventListener('click', () => _mcpAction(srv.name, 'reconnect'));
                actions.appendChild(reconnectBtn);

                const disconnectBtn = document.createElement('button');
                disconnectBtn.className = 'btn-mcp-action btn-mcp-danger';
                disconnectBtn.textContent = 'Disconnect';
                disconnectBtn.addEventListener('click', () => _mcpAction(srv.name, 'disconnect'));
                actions.appendChild(disconnectBtn);
            } else {
                const connectBtn = document.createElement('button');
                connectBtn.className = 'btn-mcp-action btn-mcp-connect';
                connectBtn.textContent = 'Connect';
                connectBtn.addEventListener('click', () => _mcpAction(srv.name, 'connect'));
                actions.appendChild(connectBtn);
            }

            row.appendChild(info);
            row.appendChild(actions);
            listEl.appendChild(row);
        });
    }

    async function _mcpAction(name, action) {
        try {
            await api(`/api/mcp/servers/${encodeURIComponent(name)}/${action}`, { method: 'POST' });
            await openMcpModal();
            await _refreshMcpStatus();
        } catch (e) {
            alert(`MCP ${action} failed: ${e.message}`);
        }
    }

    async function _refreshMcpStatus() {
        try {
            const config = await api('/api/config');
            const statusEl = document.getElementById('mcp-status');
            if (config.mcp_servers && config.mcp_servers.length > 0) {
                const connected = config.mcp_servers.filter(s => s.status === 'connected');
                const totalTools = connected.reduce((sum, s) => sum + s.tool_count, 0);
                statusEl.textContent = `${totalTools} tools / ${connected.length} servers`;
            } else {
                statusEl.textContent = '';
            }
        } catch { /* ignore */ }
    }

    document.addEventListener('DOMContentLoaded', init);

    return {
        state, api, _handle401, _getCsrfToken, _selectModel, newConversation, loadConversation,
        loadDatabases, addDatabase, loadSpaces, refreshModels, formatTimestamp,
        getTheme, setTheme, THEMES, openMcpModal, openSettings,
        setPlanMode, _showPlanContent, _debugLog,
    };
})();
