// connect.js — Connect browse, wizard, Google OAuth, browse helpers
// Extracted from dashboard.html (lines 5570-6672)

// ── Connect Browse ───────────────────────────────────
async function loadConnectBrowse() {
    const container = document.getElementById('connect-content');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/connect/browse');
        if (!res.ok) throw new Error('HTTP ' + res.status);
        const data = await res.json();
        const services = data.services || {};
        const keys = Object.keys(services);
        if (keys.length === 0) {
            container.innerHTML = '<div class="browse-empty">'
                + '<p>No services connected yet.</p>'
                + '<button class="browse-btn-configure" onclick="loadConnectWizard()">'
                + '<i class="fas fa-plug"></i> Configure Services</button></div>';
            return;
        }
        let html = '<div class="browse-services">';
        keys.filter(k => k !== 'video').forEach(key => {
            const svc = services[key];
            html += '<div class="browse-service-card">';
            html += '<div class="browse-service-header">';
            html += '<div class="icon"><i class="' + escapeHtml(svc.icon || 'fas fa-plug') + '"></i></div>';
            html += '<div class="name">' + escapeHtml(svc.display_name || key) + '</div>';
            if (svc.summary) html += '<div class="summary">' + escapeHtml(svc.summary) + '</div>';
            html += '<button class="browse-item-action" onclick="event.stopPropagation(); loadConnectBrowse()" title="Refresh" style="margin-left:auto"><i class="fas fa-sync-alt"></i></button>';
            html += '</div>';
            if (svc.error) {
                html += '<div class="browse-error">';
                html += '<span>' + escapeHtml(svc.error) + '</span>';
                // Actionable fix buttons per service
                if (key === 'email' || key === 'gmail') {
                    if (svc.error.toLowerCase().includes('imap') || svc.error.toLowerCase().includes('connection') || svc.error.toLowerCase().includes('oauth') || svc.error.toLowerCase().includes('token')) {
                        html += ' <button class="browse-fix-btn" onclick="startGoogleOAuth(\'gmail\')">'
                            + '<i class="fas fa-key"></i> Connect Gmail</button>';
                    } else {
                        html += ' <button class="browse-fix-btn" onclick="loadConnectWizard()">'
                            + '<i class="fas fa-cog"></i> Configure Email</button>';
                    }
                } else if (key === 'github') {
                    if (svc.error.includes('401') || svc.error.toLowerCase().includes('bad credentials') || svc.error.toLowerCase().includes('unauthorized')) {
                        html += ' <button class="browse-fix-btn" onclick="loadConnectWizard()">'
                            + '<i class="fas fa-key"></i> Update Token</button>';
                    } else {
                        html += ' <button class="browse-fix-btn" onclick="loadConnectWizard()">'
                            + '<i class="fas fa-cog"></i> Configure GitHub</button>';
                    }
                } else if (key === 'calendar' || key === 'contacts' || key === 'drive') {
                    html += ' <button class="browse-fix-btn" onclick="startGoogleOAuth(\'' + escapeHtml(key) + '\')">'
                        + '<i class="fas fa-key"></i> Connect ' + escapeHtml(svc.display_name || key) + '</button>';
                } else {
                    html += ' <button class="browse-fix-btn" onclick="loadConnectWizard()">'
                        + '<i class="fas fa-cog"></i> Configure</button>';
                }
                html += '</div>';
            }
            // Service-specific quick actions
            if (key === 'email' && !svc.error) {
                html += '<div style="padding:6px 14px;display:flex;gap:6px;flex-wrap:wrap;border-bottom:1px solid var(--border, rgba(255,255,255,0.05))">';
                html += '<button class="browse-item-action" onclick="quickCommand(\'Triage my inbox — flag anything urgent from donors, board, or grant officers.\')"><i class="fas fa-inbox"></i> Triage</button>';
                html += '<button class="browse-item-action" onclick="switchPanel(\'email\')"><i class="fas fa-envelope-open-text"></i> Full Inbox</button>';
                html += '<button class="browse-item-action" onclick="browseSyncGoogleContacts(this)"><i class="fas fa-address-book"></i> Sync Contacts</button>';
                html += '<button class="browse-item-action" onclick="startGoogleOAuth(\'gmail\')"><i class="fab fa-google"></i> Add Account</button>';
                html += '</div>';
            }
            if (key === 'calendar' && !svc.error) {
                html += '<div style="padding:6px 14px;display:flex;gap:6px;border-bottom:1px solid var(--border, rgba(255,255,255,0.05))">';
                html += '<button class="browse-item-action" onclick="switchPanel(\'calendar\')"><i class="fas fa-calendar-week"></i> Full Calendar</button>';
                html += '<button class="browse-item-action" onclick="showAddCalDavForm()"><i class="fas fa-calendar-plus"></i> Add Calendar</button>';
                html += '</div>';
            }
            if (key === 'drive' && !svc.error) {
                html += '<div style="padding:6px 14px;display:flex;gap:6px;border-bottom:1px solid var(--border, rgba(255,255,255,0.05))">';
                html += '<button class="browse-item-action" onclick="startGoogleOAuth(\'drive\')"><i class="fab fa-google"></i> Add Account</button>';
                html += '</div>';
            }
            if (key === 'github' && !svc.error) {
                html += '<div style="padding:6px 14px;display:flex;gap:6px;border-bottom:1px solid var(--border, rgba(255,255,255,0.05))">';
                html += '<button class="browse-item-action" onclick="window.open(\'https://github.com/notifications\', \'_blank\')"><i class="fas fa-bell"></i> All Notifications</button>';
                html += '</div>';
            }
            (svc.items || []).forEach(item => {
                const clickable = item.id && (key === 'email');
                const clickAttr = clickable
                    ? ' style="cursor:pointer" onclick="openEmailDetail(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item.id_source || 'gmail') + '\')"'
                    : '';
                html += '<div class="browse-item"' + clickAttr + '>';
                html += '<div class="item-content">';
                if (item.url) {
                    html += '<div class="item-name"><a href="' + escapeHtml(item.url) + '" target="_blank" rel="noopener" onclick="event.stopPropagation()">' + escapeHtml(item.name || '') + '</a></div>';
                } else {
                    html += '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                }
                if (item.detail) html += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                html += '</div>';
                // Action buttons
                const actions = item.actions || [];
                if (actions.length || (item.id && key === 'email')) {
                    html += '<div class="browse-item-actions">';
                    actions.forEach(a => {
                        if (a.url) {
                            html += '<a class="browse-item-action" href="' + escapeHtml(a.url) + '" target="_blank" rel="noopener" onclick="event.stopPropagation()">';
                            if (a.icon) html += '<i class="' + escapeHtml(a.icon) + '"></i> ';
                            html += escapeHtml(a.label || '') + '</a>';
                        }
                    });
                    if (item.id && key === 'email') {
                        const _fromEmail = (item.detail || '').replace(/^From:\s*/i, '').replace(/\s*\[.*/, '').trim();
                        html += '<button class="browse-item-action" onclick="event.stopPropagation(); browseEmailFeedback(\'' + escapeHtml(_fromEmail) + '\',\'' + escapeHtml(item.category || '') + '\', this)" title="Correct sorting"><i class="fas fa-sliders-h"></i></button>';
                        html += '<button class="browse-item-action danger" onclick="event.stopPropagation(); browseTrashEmail(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item.id_source || 'gmail') + '\', this)" title="Trash"><i class="fas fa-trash"></i></button>';
                    }
                    html += '</div>';
                }
                html += '</div>';
            });
            // "View All →" navigation links
            const _viewAllMap = {
                email: {label: 'Open Inbox', panel: 'email', icon: 'fa-envelope-open'},
                calendar: {label: 'Full Calendar', panel: 'calendar', icon: 'fa-calendar-week'},
                drive: {label: 'Open Drive', url: 'https://drive.google.com', icon: 'fa-external-link-alt'},
                github: {label: 'Open GitHub', url: 'https://github.com', icon: 'fa-external-link-alt'},
            };
            if (_viewAllMap[key]) {
                const va = _viewAllMap[key];
                if (va.panel) {
                    html += '<div style="padding:8px 14px;text-align:right;border-top:1px solid var(--border, rgba(255,255,255,0.05))">'
                        + '<a href="#" onclick="event.preventDefault();switchPanel(\'' + va.panel + '\')" style="color:var(--accent);font-size:0.82rem;text-decoration:none">'
                        + '<i class="fas ' + va.icon + '"></i> ' + va.label + ' &rarr;</a></div>';
                } else if (va.url) {
                    html += '<div style="padding:8px 14px;text-align:right;border-top:1px solid var(--border, rgba(255,255,255,0.05))">'
                        + '<a href="' + va.url + '" target="_blank" rel="noopener" style="color:var(--accent);font-size:0.82rem;text-decoration:none">'
                        + '<i class="fas ' + va.icon + '"></i> ' + va.label + ' &rarr;</a></div>';
                }
            }
            if (key === 'browser') {
                html += '<div class="browse-url-bar">'
                    + '<input type="text" id="browse-url-input" placeholder="Enter URL..." />'
                    + '<button id="browse-url-go">Go</button>'
                    + '</div>'
                    + '<div id="browse-page-preview"></div>';
            } else {
                html += '<div class="browse-url-bar">'
                    + '<input type="text" class="browse-search-input" data-service="' + escapeHtml(key) + '" placeholder="Search ' + escapeHtml(svc.display_name || key) + '..." />'
                    + '<button class="browse-search-go" data-service="' + escapeHtml(key) + '">Search</button>'
                    + '</div>'
                    + '<div class="browse-search-results" data-service="' + escapeHtml(key) + '"></div>';
                if (key === 'video') {
                    html += '<div class="video-online-search">'
                        + '<div style="font-size:0.78rem;color:var(--text-dim);margin-top:10px"><i class="fas fa-globe"></i> Search online</div>'
                        + '<div class="browse-url-bar">'
                        + '<input type="text" id="video-online-search-input" placeholder="Search videos online..." />'
                        + '<button id="video-online-search-go">Search</button>'
                        + '</div>'
                        + '<div id="video-online-results"></div>'
                        + '</div>';
                }
            }
            html += '</div>';
        });
        html += '</div>';
        html += '<button class="browse-btn-configure" onclick="loadConnectWizard()">'
            + '<i class="fas fa-cog"></i> Configure</button>';
        container.innerHTML = html;
        // Wire up browser URL bar
        const urlInput = document.getElementById('browse-url-input');
        const urlGo = document.getElementById('browse-url-go');
        if (urlInput && urlGo) {
            const doNavigate = async () => {
                const url = urlInput.value.trim();
                if (!url) return;
                const preview = document.getElementById('browse-page-preview');
                preview.innerHTML = '<div class="spinner"></div>';
                urlGo.disabled = true;
                try {
                    const r = await apiFetch(API + '/api/connect/browse/browser/navigate', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({url: url})
                    });
                    const d = await r.json();
                    if (d.success) {
                        if (d.is_video) {
                            preview.innerHTML = '<div class="browse-page-preview">'
                                + '<div class="preview-title">' + escapeHtml(d.title || '') + '</div>'
                                + '<div class="preview-url">' + escapeHtml(d.url || '') + '</div>'
                                + '<div class="video-actions">'
                                + '<button class="video-play-btn" data-url="' + escapeHtml(d.url) + '">'
                                + '<i class="fas fa-play"></i> Play (Ad-Free)</button>'
                                + '<select class="video-quality-select"><option>360p</option>'
                                + '<option selected>720p</option><option>1080p</option></select>'
                                + '<button class="video-download-btn" data-url="' + escapeHtml(d.url) + '">'
                                + '<i class="fas fa-download"></i> Download</button>'
                                + '</div>'
                                + '<div id="video-player-area"></div>'
                                + '<div class="video-download-status" id="video-dl-status"></div>'
                                + '</div>';
                            // Wire play button
                            preview.querySelector('.video-play-btn').addEventListener('click', async function() {
                                const area = document.getElementById('video-player-area');
                                area.innerHTML = '<div class="spinner"></div>';
                                try {
                                    const er = await apiFetch(API + '/api/connect/browse/browser/video/embed', {
                                        method: 'POST',
                                        headers: {'Content-Type': 'application/json'},
                                        body: JSON.stringify({url: this.dataset.url})
                                    });
                                    const ed = await er.json();
                                    if (ed.success) {
                                        area.innerHTML = '<button class="video-close-btn" title="Close player"><i class="fas fa-times"></i></button>' + ed.html;
                                        area.querySelector('.video-close-btn').addEventListener('click', () => { area.innerHTML = ''; });
                                    } else {
                                        area.innerHTML = '<div class="browse-error">' + escapeHtml(ed.error || 'Embed failed') + '</div>';
                                    }
                                } catch (e) {
                                    area.innerHTML = '<div class="browse-error">Failed to load player</div>';
                                }
                            });
                            // Wire download button
                            preview.querySelector('.video-download-btn').addEventListener('click', async function() {
                                const status = document.getElementById('video-dl-status');
                                const quality = preview.querySelector('.video-quality-select').value;
                                this.disabled = true;
                                status.innerHTML = '<div class="spinner" style="display:inline-block;width:14px;height:14px"></div> Downloading at ' + escapeHtml(quality) + '...';
                                try {
                                    const dr = await apiFetch(API + '/api/connect/browse/browser/video/download', {
                                        method: 'POST',
                                        headers: {'Content-Type': 'application/json'},
                                        body: JSON.stringify({url: this.dataset.url, quality: quality})
                                    });
                                    const dd = await dr.json();
                                    if (dd.success) {
                                        status.textContent = 'Downloaded: ' + dd.title + ' (' + dd.size + ', ' + dd.duration + ')';
                                    } else {
                                        status.textContent = 'Download failed: ' + (dd.error || 'Unknown error');
                                    }
                                } catch (e) {
                                    status.textContent = 'Download request failed';
                                }
                                this.disabled = false;
                            });
                        } else {
                            preview.innerHTML = '<div class="browse-page-preview">'
                                + '<div class="preview-title">' + escapeHtml(d.title || '') + '</div>'
                                + '<div class="preview-url">' + escapeHtml(d.url || '') + '</div>'
                                + escapeHtml(d.content || '(empty page)')
                                + '</div>';
                        }
                    } else {
                        preview.innerHTML = '<div class="browse-error">' + escapeHtml(d.error || 'Navigation failed') + '</div>';
                    }
                } catch (err) {
                    preview.innerHTML = '<div class="browse-error">Request failed</div>';
                }
                urlGo.disabled = false;
            };
            urlGo.addEventListener('click', doNavigate);
            urlInput.addEventListener('keydown', e => { if (e.key === 'Enter') doNavigate(); });
        }
        // ── Browse panel helpers ──

        // Email feedback — correct category/priority
        window.browseEmailFeedback = async function(fromEmail, currentCat, btn) {
            // Toggle dropdown
            const existing = btn.parentElement.querySelector('.browse-feedback-dropdown');
            if (existing) { existing.remove(); return; }

            let cats = ['Work', 'Family', 'Personal', 'Financial', 'Dev/CI', 'Events', 'News/Reading', 'Job Search', 'Political', 'Shopping/Deals', 'Services', 'Spam', 'Other'];
            // Add custom categories
            try {
                const ccr = await apiFetch(API + '/api/email/custom-categories');
                const ccd = await ccr.json();
                (ccd.categories || []).forEach(cc => {
                    if (!cats.includes(cc.name)) cats.push(cc.name);
                });
            } catch(e) {}
            const pris = ['urgent', 'action', 'fyi'];
            let dd = document.createElement('div');
            dd.className = 'browse-feedback-dropdown';
            dd.style.cssText = 'position:absolute;right:0;top:100%;background:var(--bg-card,#1e1e2e);border:1px solid var(--border,rgba(255,255,255,0.1));border-radius:6px;padding:8px;z-index:100;min-width:220px;max-height:400px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.3)';
            dd.innerHTML = '<div style="font-size:0.75rem;color:var(--text-dim);margin-bottom:4px">Correct category:</div>'
                + cats.map(c => '<button class="browse-item-action" style="margin:2px" onclick="submitEmailFeedback(\'' + escapeHtml(fromEmail) + '\',\'' + c + '\',\'\',this)">' + c + '</button>').join('')
                + '<div style="font-size:0.75rem;color:var(--text-dim);margin:6px 0 4px">Correct priority:</div>'
                + pris.map(p => {
                    const emoji = p === 'urgent' ? '\ud83d\udd34' : p === 'action' ? '\ud83d\udfe1' : '\u26aa';
                    return '<button class="browse-item-action" style="margin:2px" onclick="submitEmailFeedback(\'' + escapeHtml(fromEmail) + '\',\'\',\'' + p + '\',this)">' + emoji + ' ' + p + '</button>';
                }).join('')
                + '<div style="margin-top:6px;border-top:1px solid var(--border,rgba(255,255,255,0.1));padding-top:6px">'
                + '<button class="browse-item-action" style="margin:2px;color:var(--accent)" onclick="submitEmailFeedback(\'' + escapeHtml(fromEmail) + '\',\'\',\'\',this,true)"><i class="fas fa-star"></i> Make VIP</button>'
                + '</div>';
            btn.parentElement.style.position = 'relative';
            btn.parentElement.appendChild(dd);
            // Close on outside click
            setTimeout(() => {
                document.addEventListener('click', function _close(e) {
                    if (!dd.contains(e.target) && e.target !== btn) { dd.remove(); document.removeEventListener('click', _close); }
                });
            }, 10);
        };

        window.submitEmailFeedback = async function(fromEmail, category, priority, btn, makeVip) {
            try {
                if (makeVip) {
                    await apiFetch(API + '/api/email/vip-senders', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({sender: fromEmail}),
                    });
                    showToast(fromEmail + ' added as VIP', 'success');
                } else {
                    const r = await apiFetch(API + '/api/email/feedback', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({from_email: fromEmail, correct_category: category, correct_priority: priority}),
                    });
                    const d = await r.json();
                    if (d.learned) showToast('Learned: ' + (d.changes || []).join(', '), 'success');
                }
                // Close dropdown
                const dd = btn.closest('.browse-feedback-dropdown');
                if (dd) dd.remove();
            } catch(e) { showToast('Feedback failed: ' + e.message, 'error'); }
        };

        window.browseSyncGoogleContacts = async function(btn) {
            btn.disabled = true;
            const orig = btn.innerHTML;
            btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Syncing...';
            try {
                const r = await apiFetch(API + '/api/contacts/sync-google', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({auto_vip: true}),
                });
                const d = await r.json();
                showToast(d.result || d.error || 'Sync complete', d.error ? 'error' : 'success');
            } catch(e) { showToast('Sync failed: ' + e.message, 'error'); }
            btn.innerHTML = orig;
            btn.disabled = false;
        };

        window.browseTrashEmail = async function(msgId, source, btn) {
            btn.disabled = true;
            btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
            try {
                await apiFetch(API + '/api/email/delete/' + encodeURIComponent(msgId), {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({id_source: source}),
                });
                const row = btn.closest('.browse-item');
                if (row) { row.style.opacity = '0'; setTimeout(() => row.remove(), 300); }
            } catch(e) { showToast('Trash failed: ' + e.message, 'error'); btn.disabled = false; btn.innerHTML = '<i class="fas fa-trash"></i>'; }
        };

        // Wire up service search bars
        document.querySelectorAll('.browse-search-go').forEach(btn => {
            const svcKey = btn.dataset.service;
            const input = document.querySelector('.browse-search-input[data-service="' + svcKey + '"]');
            const resultsDiv = document.querySelector('.browse-search-results[data-service="' + svcKey + '"]');
            if (!input || !resultsDiv) return;
            const doSearch = async () => {
                const q = input.value.trim();
                if (!q) return;
                btn.disabled = true;
                resultsDiv.innerHTML = '<div class="spinner"></div>';
                try {
                    const r = await apiFetch(API + '/api/connect/browse/search', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({service: svcKey, query: q})
                    });
                    const d = await r.json();
                    if (d.error) {
                        resultsDiv.innerHTML = '<div class="browse-error">' + escapeHtml(d.error) + '</div>';
                    } else if ((d.items || []).length === 0) {
                        resultsDiv.innerHTML = '<div class="browse-error">No results found</div>';
                    } else {
                        let rhtml = '';
                        (d.items || []).forEach(item => {
                            rhtml += '<div class="browse-item">';
                            rhtml += '<div class="item-content">';
                            if (item.url) {
                                rhtml += '<div class="item-name"><a href="' + escapeHtml(item.url) + '" target="_blank" rel="noopener">' + escapeHtml(item.name || '') + '</a></div>';
                            } else {
                                rhtml += '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                            }
                            if (item.detail) rhtml += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                            rhtml += '</div>';
                            rhtml += '</div>';
                        });
                        if (d.summary) rhtml += '<div class="item-detail" style="margin-top:4px">' + escapeHtml(d.summary) + '</div>';
                        resultsDiv.innerHTML = rhtml;
                    }
                } catch (err) {
                    resultsDiv.innerHTML = '<div class="browse-error">Search request failed</div>';
                }
                btn.disabled = false;
            };
            btn.addEventListener('click', doSearch);
            input.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); });
        });
        // Wire up online video search (Invidious)
        const vidSearchInput = document.getElementById('video-online-search-input');
        const vidSearchGo = document.getElementById('video-online-search-go');
        if (vidSearchInput && vidSearchGo) {
            const doVidSearch = async () => {
                const q = vidSearchInput.value.trim();
                if (!q) return;
                vidSearchGo.disabled = true;
                const resultsDiv = document.getElementById('video-online-results');
                resultsDiv.innerHTML = '<div class="spinner"></div>';
                try {
                    const r = await apiFetch(API + '/api/connect/browse/video/search', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({query: q})
                    });
                    const d = await r.json();
                    if (d.error) {
                        resultsDiv.innerHTML = '<div class="browse-error">' + escapeHtml(d.error) + '</div>';
                    } else if ((d.items || []).length === 0) {
                        resultsDiv.innerHTML = '<div class="browse-error">No results found</div>';
                    } else {
                        let rhtml = '';
                        (d.items || []).forEach(item => {
                            rhtml += '<div class="video-search-result">';
                            if (item.thumbnail) rhtml += '<img class="video-thumbnail" src="' + escapeHtml(item.thumbnail) + '" alt="" />';
                            rhtml += '<div style="flex:1;min-width:0">'
                                + '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                            if (item.detail) rhtml += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                            rhtml += '<div class="video-actions">'
                                + '<button class="video-play-btn" data-url="' + escapeHtml(item.url || '') + '"><i class="fas fa-play"></i> Play</button>'
                                + '<button class="video-download-btn" data-url="' + escapeHtml(item.url || '') + '"><i class="fas fa-download"></i> Download</button>'
                                + '</div></div></div>';
                        });
                        if (d.summary) rhtml += '<div class="item-detail" style="margin-top:4px">' + escapeHtml(d.summary) + '</div>';
                        resultsDiv.innerHTML = rhtml;
                        // Wire play/download buttons in search results
                        resultsDiv.querySelectorAll('.video-play-btn').forEach(pbtn => {
                            pbtn.addEventListener('click', async function() {
                                const parent = this.closest('.video-search-result');
                                let area = parent.querySelector('.video-result-player');
                                if (!area) { area = document.createElement('div'); area.className = 'video-result-player'; parent.appendChild(area); }
                                area.innerHTML = '<div class="spinner"></div>';
                                try {
                                    const er = await apiFetch(API + '/api/connect/browse/browser/video/embed', {
                                        method: 'POST',
                                        headers: {'Content-Type': 'application/json'},
                                        body: JSON.stringify({url: this.dataset.url})
                                    });
                                    const ed = await er.json();
                                    if (ed.success) {
                                        area.innerHTML = '<button class="video-close-btn" title="Close player"><i class="fas fa-times"></i></button>' + ed.html;
                                        area.querySelector('.video-close-btn').addEventListener('click', () => { area.innerHTML = ''; });
                                    } else {
                                        area.innerHTML = '<div class="browse-error">' + escapeHtml(ed.error || 'Embed failed') + '</div>';
                                    }
                                } catch (e) { area.innerHTML = '<div class="browse-error">Failed to load player</div>'; }
                            });
                        });
                        resultsDiv.querySelectorAll('.video-download-btn').forEach(dbtn => {
                            dbtn.addEventListener('click', async function() {
                                this.disabled = true;
                                this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Downloading...';
                                try {
                                    const dr = await apiFetch(API + '/api/connect/browse/browser/video/download', {
                                        method: 'POST',
                                        headers: {'Content-Type': 'application/json'},
                                        body: JSON.stringify({url: this.dataset.url, quality: '720p'})
                                    });
                                    const dd = await dr.json();
                                    this.innerHTML = dd.success
                                        ? '<i class="fas fa-check"></i> ' + escapeHtml(dd.size)
                                        : '<i class="fas fa-times"></i> Failed';
                                } catch (e) { this.innerHTML = '<i class="fas fa-times"></i> Failed'; }
                                this.disabled = false;
                            });
                        });
                    }
                } catch (err) {
                    resultsDiv.innerHTML = '<div class="browse-error">Search request failed</div>';
                }
                vidSearchGo.disabled = false;
            };
            vidSearchGo.addEventListener('click', doVidSearch);
            vidSearchInput.addEventListener('keydown', e => { if (e.key === 'Enter') doVidSearch(); });
        }
    } catch (e) {
        console.error('loadConnectBrowse error:', e);
        container.innerHTML = '<div class="browse-empty">'
            + '<p>Could not load services. <a href="#" onclick="loadConnectBrowse();return false">Retry</a></p>'
            + '<button class="browse-btn-configure" onclick="loadConnectWizard()">'
            + '<i class="fas fa-plug"></i> Configure Services</button></div>';
    }
}

const WIZARD_LOADING_HTML = '<div class="wizard-loading">'
    + '<div class="wizard-loading-dot"></div>'
    + '<div class="wizard-loading-dot"></div>'
    + '<div class="wizard-loading-dot"></div></div>';

// ── Connect Wizard ───────────────────────────────────
async function loadConnectWizard() {
    const container = document.getElementById('connect-content');
    _wizardContainer = null;  // Reset to connect panel
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/connect/wizard/start', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({args: []})
        });
        const data = await res.json();
        container.innerHTML = '';
        const backBtn = document.createElement('button');
        backBtn.className = 'browse-btn-configure';
        backBtn.innerHTML = '<i class="fas fa-arrow-left"></i> Back to overview';
        backBtn.onclick = () => loadConnectBrowse();
        container.appendChild(backBtn);
        renderWizardMessages(container, data.wizard_messages || []);
        maybeAddWizardInput(container);
    } catch (e) {
        container.innerHTML = '<p>Error loading connect wizard</p>';
    }
}

// ── Google OAuth Panel ────────────────────────────────────────────────────
// Tracks per-service polling timers so we can cancel if user navigates away
async function startGoogleOAuth(service) {
    // Convenience wrapper — can be called from anywhere (email panel, briefing, etc.)
    // Switches to the Google panel and starts the OAuth flow for the given service.
    switchPanel('google-oauth');
    // Wait for panel to render, then trigger the auth flow
    setTimeout(async () => {
        try {
            const res = await apiFetch(API + '/api/connect/google/start/' + service, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
            });
            const data = await res.json();
            if (data.auth_url) {
                window.open(data.auth_url, '_blank');
                loadGoogleOAuth();
            } else if (data.error) {
                alert(data.error);
            }
        } catch (e) {
            alert('Failed to start Gmail authorization: ' + e.message);
        }
    }, 300);
}

// _googleOAuthPollers — managed by PollManager('google-oauth-*')

const _GOOGLE_SERVICES = ['gmail', 'calendar', 'drive', 'contacts'];
const _GOOGLE_SERVICE_LABELS = {
    gmail: { label: 'Gmail', icon: 'fas fa-envelope', desc: 'Read & send email' },
    calendar: { label: 'Calendar', icon: 'fas fa-calendar-alt', desc: 'Schedule & events' },
    drive: { label: 'Drive', icon: 'fas fa-hdd', desc: 'Files & documents' },
    contacts: { label: 'Contacts', icon: 'fas fa-address-book', desc: 'People & addresses' },
};

function _googleMakeAuthBtn(label, cls, svc, statusEl, actionEl) {
    const btn = document.createElement('button');
    btn.className = cls;
    btn.textContent = label;
    btn.addEventListener('click', () => _googleStartFlow(svc, statusEl, actionEl));
    return btn;
}

async function _googleStartFlow(service, statusEl, actionEl) {
    actionEl.innerHTML = '';
    const spinner = document.createElement('span');
    spinner.textContent = '⏳ Starting…';
    spinner.style.fontSize = '0.85rem';
    actionEl.appendChild(spinner);

    let authUrl = null;
    try {
        const res = await apiFetch(API + '/api/connect/google/start/' + service, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
        });
        const data = await res.json();
        if (data.error) {
            actionEl.innerHTML = '<span style="color:var(--error);font-size:0.82rem">❌ ' + escapeHtml(data.error) + '</span>';
            return;
        }
        authUrl = data.auth_url;
    } catch (e) {
        actionEl.innerHTML = '<span style="color:var(--error);font-size:0.82rem">❌ ' + escapeHtml(e.message) + '</span>';
        return;
    }

    // Show link + open automatically
    statusEl.innerHTML = '<span class="google-badge waiting">⏳ Waiting…</span>';
    actionEl.innerHTML = '';
    const link = document.createElement('a');
    link.href = authUrl;
    link.target = '_blank';
    link.rel = 'noopener noreferrer';
    link.className = 'google-auth-btn google-open-btn';
    link.textContent = '🔐 Open Auth Page';
    actionEl.appendChild(link);
    const hint = document.createElement('div');
    hint.className = 'google-oauth-hint';
    hint.textContent = 'Sign in, approve, then return here.';
    actionEl.appendChild(hint);
    try { window.open(authUrl, '_blank'); } catch (e2) {}

    // Poll for completion
    PollManager.start('google-oauth-' + service, async () => {
        try {
            const r = await apiFetch(API + '/api/connect/google/status/' + service);
            const st = await r.json();
            if (!st.done) return;
            PollManager.stop('google-oauth-' + service);
            if (st.success) {
                statusEl.innerHTML = '<span class="google-badge connected">✅ Connected</span>';
                actionEl.innerHTML = '';
                actionEl.appendChild(_googleMakeAuthBtn('Re-authorize', 'google-reauth-btn', service, statusEl, actionEl));
            } else {
                statusEl.innerHTML = '<span class="google-badge error">❌ Failed</span>';
                actionEl.innerHTML = '<span style="color:var(--error);font-size:0.82rem">' + escapeHtml(st.error || 'Unknown error') + '</span> ';
                actionEl.appendChild(_googleMakeAuthBtn('Retry', 'google-auth-btn', service, statusEl, actionEl));
            }
        } catch (e3) {}
    }, APP_CONFIG.poll.googleOAuth);
}

async function loadGoogleOAuth() {
    // Ensure the Connect panel is active and visible
    document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
    document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
    const connectNav = document.querySelector('.nav-item[data-panel="connect"]');
    const connectPanel = document.getElementById('connect-panel');
    if (connectNav) connectNav.classList.add('active');
    if (connectPanel) connectPanel.classList.add('active');
    _wizardContainer = null;

    const container = document.getElementById('connect-content');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    // Cancel any existing Google OAuth pollers
    Object.keys(PollManager._intervals).filter(k => k.startsWith('google-oauth-')).forEach(k => PollManager.stop(k));

    // Fetch current token status
    let tokenStatus = {};
    try {
        const res = await apiFetch(API + '/api/connect/google/token-status');
        if (res.ok) tokenStatus = await res.json();
    } catch (e) {}

    container.innerHTML = '';

    // Back button
    const backBtn = document.createElement('button');
    backBtn.className = 'browse-btn-configure';
    backBtn.innerHTML = '<i class="fas fa-arrow-left"></i> Back';
    backBtn.addEventListener('click', () => loadConnectBrowse());
    container.appendChild(backBtn);

    // Reset tokens button
    const resetBtn = document.createElement('button');
    resetBtn.className = 'browse-btn-configure';
    resetBtn.style.cssText = 'margin-left:8px;color:var(--error);border-color:var(--error)';
    resetBtn.innerHTML = '<i class="fas fa-trash"></i> Reset tokens';
    resetBtn.addEventListener('click', async () => {
        if (!confirm('Delete all Google token files?')) return;
        await apiFetch(API + '/api/connect/google/reset', {
            method: 'POST', headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({target: 'tokens'})
        });
        loadGoogleOAuth();
    });
    container.appendChild(resetBtn);

    // Reset all button
    const resetAllBtn = document.createElement('button');
    resetAllBtn.className = 'browse-btn-configure';
    resetAllBtn.style.cssText = 'margin-left:8px;color:var(--error);border-color:var(--error)';
    resetAllBtn.innerHTML = '<i class="fas fa-trash-alt"></i> Reset all';
    resetAllBtn.addEventListener('click', async () => {
        if (!confirm('Delete credentials.json AND all token files?')) return;
        await apiFetch(API + '/api/connect/google/reset', {
            method: 'POST', headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({target: 'all'})
        });
        loadGoogleOAuth();
    });
    container.appendChild(resetAllBtn);

    const title = document.createElement('div');
    title.className = 'wizard-text';
    title.innerHTML = '<b>Google Services Authorization</b>';
    container.appendChild(title);

    if (!tokenStatus.credentials) {
        const warn = document.createElement('div');
        warn.className = 'wizard-text';
        warn.innerHTML = '<span style="color:var(--warning)">⚠️ No credentials.json found.</span> '
            + 'Upload your Google OAuth credentials file first.';
        container.appendChild(warn);
        return;
    }

    // One card per service
    _GOOGLE_SERVICES.forEach(svc => {
        const meta = _GOOGLE_SERVICE_LABELS[svc];
        const connected = !!tokenStatus[svc];

        const card = document.createElement('div');
        card.className = 'google-oauth-card';

        const header = document.createElement('div');
        header.className = 'google-oauth-card-header';
        header.innerHTML = '<i class="' + meta.icon + '"></i> <b>' + meta.label + '</b>'
            + ' <span class="google-oauth-desc">' + meta.desc + '</span>';

        const statusEl = document.createElement('div');
        statusEl.className = 'google-oauth-status';
        statusEl.innerHTML = connected
            ? '<span class="google-badge connected">✅ Connected</span>'
            : '<span class="google-badge">Not connected</span>';

        const actionEl = document.createElement('div');
        actionEl.className = 'google-oauth-action';
        actionEl.appendChild(_googleMakeAuthBtn(
            connected ? 'Re-authorize' : 'Authorize',
            connected ? 'google-reauth-btn' : 'google-auth-btn',
            svc, statusEl, actionEl
        ));

        card.appendChild(header);
        card.appendChild(statusEl);
        card.appendChild(actionEl);
        container.appendChild(card);
    });
}

// Track active wizard container (chat vs connect panel)
let _wizardContainer = null;
function getWizardContainer() {
    return _wizardContainer || document.getElementById('connect-content');
}

function renderWizardMessages(container, messages) {
    // If any message looks like the old Google OAuth wizard flow, redirect to panel
    const hasGoogleAuthText = messages.some(msg =>
        msg.type === 'text' && msg.text &&
        (msg.text.includes('accounts.google.com/o/oauth2') ||
         msg.text.includes('/connect google code'))
    );
    if (hasGoogleAuthText) {
        loadGoogleOAuth();
        return;
    }

    let isSetupStep = false;
    let hasCardMenu = false;
    let hasSuccessMsg = false;

    messages.forEach(msg => {
        if (msg.type === 'card_menu') {
            container.appendChild(renderCardMenu(msg));
            hasCardMenu = true;
        } else if (msg.type === 'menu') {
            container.appendChild(renderMenu(msg));
            isSetupStep = true;
        } else if (msg.type === 'text') {
            const div = document.createElement('div');
            div.className = 'wizard-text';
            div.innerHTML = linkifyUrls(msg.text);
            container.appendChild(div);
            if (msg.text.length > 80) isSetupStep = true;
            // Detect completion messages
            const lower = (msg.text || '').toLowerCase();
            if (lower.includes('saved') || lower.includes('connected')
                || lower.includes('success') || lower.includes('configured')
                || lower.includes('all set') || lower.includes('enabled')) {
                hasSuccessMsg = true;
            }
        }
    });

    // Back button on every non-root wizard step
    if (!hasCardMenu) {
        const nav = document.createElement('div');
        nav.style.cssText = 'display:flex;gap:8px;margin-top:14px;';
        const backBtn = document.createElement('button');
        backBtn.className = 'btn';
        backBtn.style.cssText = 'padding:8px 16px;font-size:0.85rem;';
        backBtn.innerHTML = '<i class="fas fa-arrow-left" style="margin-right:4px;"></i> Back to cards';
        backBtn.onclick = () => loadConnectWizard();
        nav.appendChild(backBtn);
        container.appendChild(nav);
    }

    // Setup step: add input field + Helper Hat button
    if (isSetupStep && !hasSuccessMsg) {
        const wrap = document.createElement('div');
        wrap.className = 'wizard-input-wrap';
        wrap.style.marginBottom = '12px';
        wrap.innerHTML = '<input type="text" class="wizard-text-input" '
            + 'placeholder="Paste your key or answer here..." '
            + 'style="flex:1;padding:10px 14px;border-radius:8px;border:1px solid var(--border);'
            + 'background:var(--bg-input);color:var(--text-primary);font-size:0.95rem;">'
            + '<button class="btn btn-primary" onclick="wizardSendInput()" '
            + 'style="padding:10px 18px;">Send</button>';
        container.appendChild(wrap);
        const inp = wrap.querySelector('.wizard-text-input');
        if (inp) {
            inp.focus();
            inp.addEventListener('keypress', e => {
                if (e.key === 'Enter') wizardSendInput();
            });
        }

        const hatBtn = document.createElement('button');
        hatBtn.className = 'wizard-hat-btn';
        hatBtn.innerHTML = '<i class="fa-solid fa-hat-wizard"></i> Too complicated? Let Helper set it up';
        hatBtn.onclick = () => wizardHandoff();
        container.appendChild(hatBtn);
    }

    // Auto-return to cards after success
    if (hasSuccessMsg && !hasCardMenu) {
        setTimeout(() => loadConnectWizard(), 2500);
    }
}

function renderCardMenu(msg) {
    const card = document.createElement('div');
    card.className = 'wizard-card';

    // Tabs
    const tabBar = document.createElement('div');
    tabBar.className = 'wizard-tabs';
    (msg.tabs || []).forEach(tab => {
        const btn = document.createElement('button');
        btn.className = 'wizard-tab' + (tab.active ? ' active' : '');
        btn.textContent = tab.icon + ' ' + tab.label;
        btn.onclick = () => wizardSwitchTab(tab.id);
        tabBar.appendChild(btn);
    });
    card.appendChild(tabBar);

    // Tab content text
    if (msg.tab_content) {
        const content = document.createElement('div');
        content.className = 'wizard-tab-content';
        content.innerHTML = linkifyUrls(msg.tab_content);
        card.appendChild(content);
    }

    // Service buttons
    if (msg.tab_options && msg.tab_options.length > 0) {
        const btns = document.createElement('div');
        btns.className = 'wizard-buttons';
        msg.tab_options.forEach(opt => {
            const btn = document.createElement('button');
            btn.className = 'wizard-btn';
            btn.textContent = opt.label;
            btn.onclick = function() { wizardSelect(opt.key, this); };
            btns.appendChild(btn);
        });
        card.appendChild(btns);
    }

    return card;
}

function renderMenu(msg) {
    const card = document.createElement('div');
    card.className = 'wizard-card';

    if (msg.text) {
        const text = document.createElement('div');
        text.className = 'wizard-tab-content';
        text.innerHTML = linkifyUrls(msg.text);
        card.appendChild(text);
    }

    if (msg.options) {
        const btns = document.createElement('div');
        btns.className = 'wizard-buttons';
        msg.options.forEach(opt => {
            const btn = document.createElement('button');
            btn.className = 'wizard-btn';
            btn.textContent = opt.label;
            btn.onclick = function() { wizardSelect(opt.key, this); };
            btns.appendChild(btn);
        });
        card.appendChild(btns);
    }

    return card;
}

async function wizardHandoff() {
    // Fetch current wizard context, then hand off to the Helper agent
    try {
        const ctxResp = await apiFetch('/api/connect/wizard/context');
        const ctx = await ctxResp.json();

        const serviceName = ctx.service_name || 'this service';
        const step = ctx.step || '';

        // Get any visible wizard text as extra context
        const wizardPanel = document.getElementById('connect-panel');
        const visibleText = wizardPanel
            ? Array.from(wizardPanel.querySelectorAll('.wizard-tab-content, .wizard-text'))
                .map(el => el.textContent).join(' ').substring(0, 500)
            : '';

        const resp = await apiFetch('/api/wizard/handoff', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                service_name: serviceName,
                step: step,
                context: visibleText,
                source: 'connect',
            }),
        });
        const data = await resp.json();

        // Switch to chat panel and show the agent's response
        switchPanel('chat');
        if (data.response) {
            addMessage(data.response, 'assistant');
        }
    } catch (err) {
        console.error('Wizard handoff failed:', err);
        addMessage('Helper handoff failed — please try again or continue the wizard.', 'assistant');
    }
}

async function wizardSelect(key, clickedBtn) {
    // Google OAuth — intercept and route to dedicated panel
    if (key.startsWith('google_auth_')) {
        loadGoogleOAuth();
        return;
    }

    // Google credentials upload — open file picker instead of API call
    if (key === 'google_upload_creds') {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json,application/json';
        input.onchange = async () => {
            if (!input.files || !input.files[0]) return;
            const container = getWizardContainer();
            container.innerHTML = WIZARD_LOADING_HTML;
            const form = new FormData();
            form.append('file', input.files[0]);
            try {
                const resp = await apiFetch(API + '/api/connect/upload/google-credentials', {
                    method: 'POST', body: form
                });
                const data = await resp.json();
                container.innerHTML = '';
                if (data.success) {
                    // Go straight to the OAuth panel — no wizard needed
                    loadGoogleOAuth();
                } else {
                    const div = document.createElement('div');
                    div.className = 'wizard-text';
                    div.innerHTML = '\u274c ' + (data.error || 'Upload failed');
                    container.appendChild(div);
                }
            } catch (e) {
                container.innerHTML = '<p>Upload failed: ' + e.message + '</p>';
            }
        };
        input.click();
        return;
    }

    // Disable all wizard buttons and show loading on clicked one
    if (clickedBtn) {
        const allBtns = clickedBtn.closest('.wizard-buttons')?.querySelectorAll('.wizard-btn') || [];
        allBtns.forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
        clickedBtn.style.opacity = '1';
        clickedBtn.innerHTML = clickedBtn.textContent + ' <span class="wizard-loading-dot" style="display:inline-block;width:6px;height:6px;margin-left:4px"></span>';
    }
    const container = getWizardContainer();
    if (!clickedBtn) container.innerHTML = WIZARD_LOADING_HTML;
    try {
        const res = await apiFetch(API + '/api/connect/wizard/select', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({key: key})
        });
        const data = await res.json();
        // Google OAuth redirect — show dedicated panel
        if (data.redirect === 'google_oauth') {
            loadGoogleOAuth();
            return;
        }
        container.innerHTML = '';
        renderWizardMessages(container, data.wizard_messages || []);
        maybeAddWizardInput(container);
        // Tutorial auto-advance: Mind card wizard done → handoff
        if (_tutorialStep === 2
            && container === document.getElementById('tutorial-wizard-target')) {
            const hasSpread = data.wizard_messages && data.wizard_messages.some(
                m => m.options && m.options.some(
                    o => o.key && o.key.startsWith('tarot_card_')
                )
            );
            if (hasSpread) {
                _tutorialStep = 3;
                renderTutorialStep(
                    _tutorialOwner, _tutorialName, _creatureSpeciesKey
                );
                return;
            }
        }
        if (container.closest('#messages')) messagesDiv.scrollTop = messagesDiv.scrollHeight;
    } catch (e) {
        container.innerHTML = '<p>Error communicating with wizard</p>';
    }
}

async function wizardSwitchTab(tabId) {
    const container = getWizardContainer();
    container.innerHTML = WIZARD_LOADING_HTML;
    try {
        const res = await apiFetch(API + '/api/connect/wizard/tab', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({tab: tabId})
        });
        const data = await res.json();
        container.innerHTML = '';
        renderWizardMessages(container, data.wizard_messages || []);
        maybeAddWizardInput(container);
        if (container.closest('#messages')) messagesDiv.scrollTop = messagesDiv.scrollHeight;
    } catch (e) {
        container.innerHTML = '<p>Error switching tab</p>';
    }
}

async function wizardSendInput() {
    const inp = document.querySelector('.wizard-text-input');
    if (!inp) return;
    const text = inp.value.trim();
    if (!text) return;
    inp.value = '';

    const container = getWizardContainer();
    container.innerHTML = WIZARD_LOADING_HTML;
    try {
        const res = await apiFetch(API + '/api/connect/wizard/input', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({text: text})
        });
        const data = await res.json();
        container.innerHTML = '';
        renderWizardMessages(container, data.wizard_messages || []);
        maybeAddWizardInput(container);
        // Tutorial auto-advance: Mind card completed via text input
        if (_tutorialStep === 2
            && container === document.getElementById('tutorial-wizard-target')) {
            const hasSpread = data.wizard_messages && data.wizard_messages.some(
                m => m.options && m.options.some(
                    o => o.key && o.key.startsWith('tarot_card_')
                )
            );
            if (hasSpread) {
                _tutorialStep = 3;
                renderTutorialStep(
                    _tutorialOwner, _tutorialName, _creatureSpeciesKey
                );
                return;
            }
        }
        if (container.closest('#messages')) messagesDiv.scrollTop = messagesDiv.scrollHeight;
    } catch (e) {
        container.innerHTML = '<p>Error sending input</p>';
    }
}

function maybeAddWizardInput(container) {
    // Always add input when wizard is active — user may need to type
    // a token, password, or other value even when buttons are present
    const wrap = document.createElement('div');
    wrap.className = 'wizard-input-wrap';
    wrap.innerHTML = '<input type="text" class="wizard-text-input" placeholder="Type your answer...">'
        + '<button class="btn btn-primary" onclick="wizardSendInput()">Send</button>';
    container.appendChild(wrap);
    const inp = wrap.querySelector('.wizard-text-input');
    if (inp) {
        inp.focus();
        inp.addEventListener('keypress', e => { if (e.key === 'Enter') wizardSendInput(); });
    }
}

function addWizardMessage(msg) {
    const wrap = document.createElement('div');
    wrap.className = 'message assistant';
    wrap.id = 'chat-wizard-container';
    _wizardContainer = wrap;
    if (msg.type === 'card_menu') {
        wrap.appendChild(renderCardMenu(msg));
    } else if (msg.type === 'menu') {
        wrap.appendChild(renderMenu(msg));
    } else if (msg.type === 'text') {
        const p = document.createElement('p');
        p.innerHTML = linkifyUrls(msg.text);
        wrap.appendChild(p);
    }
    messagesDiv.appendChild(wrap);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
}

