// ─── SETTINGS ───
    function switchSettingTab(tabName) {
        // Hide all tabs
        document.querySelectorAll('.setting-tab-content').forEach(el => el.style.display = 'none');
        // Show selected tab
        const tab = document.getElementById('settingTab_' + tabName);
        if (tab) tab.style.display = '';
        // Update sidebar active state
        document.querySelectorAll('#settingsModal .section-menu-item').forEach(el => {
            el.classList.toggle('active', el.dataset.tab === tabName);
        });
    }

    function openSettings() {
        const modal = document.getElementById('settingsModal');
        const saved = JSON.parse(localStorage.getItem('aiSqlSettings') || '{}');
        document.getElementById('settingApiUrl').value = saved.apiUrl || window.location.origin;
        document.getElementById('settingDbHost').value = saved.dbHost || '';
        document.getElementById('settingDefaultModel').value = saved.defaultModel || 'codex/gpt-5.3-codex';
        document.getElementById('settingMaxCycles').value = saved.maxCycles || 3;
        document.getElementById('settingTemperature').value = saved.temperature || 0;
        // Switches
        ['settingAutoChart', 'settingShowSql', 'settingShowLogs', 'settingHitl'].forEach(id => {
            const el = document.getElementById(id);
            if (!el) return;
            const key = id.replace('setting', '').replace(/^./, c => c.toLowerCase());
            if (saved[key] === false) el.classList.remove('on'); else el.classList.add('on');
        });
        if (saved.debug) document.getElementById('settingDebug').classList.add('on');
        else document.getElementById('settingDebug').classList.remove('on');
        // Reset to first tab
        switchSettingTab('connection');
        modal.style.display = 'block';
    }

    function closeSettings() {
        document.getElementById('settingsModal').style.display = 'none';
    }

    function saveSettings() {
        const settings = {
            apiUrl: document.getElementById('settingApiUrl').value,
            dbHost: document.getElementById('settingDbHost').value,
            defaultModel: document.getElementById('settingDefaultModel').value,
            maxCycles: parseInt(document.getElementById('settingMaxCycles').value) || 3,
            temperature: parseFloat(document.getElementById('settingTemperature').value) || 0,
            autoChart: document.getElementById('settingAutoChart').classList.contains('on'),
            showSql: document.getElementById('settingShowSql').classList.contains('on'),
            showLogs: document.getElementById('settingShowLogs').classList.contains('on'),
            hitl: document.getElementById('settingHitl').classList.contains('on'),
            debug: document.getElementById('settingDebug').classList.contains('on'),
        };
        localStorage.setItem('aiSqlSettings', JSON.stringify(settings));
        document.getElementById('modelSelect').value = settings.defaultModel;
        closeSettings();
    }

    function testConnection() {
        const btn = event.target.closest('button');
        const origHTML = btn.innerHTML;
        btn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right:4px;"></i> Testing...';
        btn.disabled = true;
        fetch('/health').then(r => r.json()).then(data => {
            btn.innerHTML = '<i class="fas fa-check" style="margin-right:4px; color:var(--success);"></i> OK';
            setTimeout(() => { btn.innerHTML = origHTML; btn.disabled = false; }, 2000);
        }).catch(err => {
            btn.innerHTML = '<i class="fas fa-times" style="margin-right:4px; color:var(--error);"></i> Fail';
            setTimeout(() => { btn.innerHTML = origHTML; btn.disabled = false; }, 2000);
        });
    }

    function resetSettings() {
        if (confirm('Xóa tất cả cài đặt về mặc định?')) {
            localStorage.removeItem('aiSqlSettings');
            closeSettings();
            openSettings(); // Re-open with defaults
        }
    }

    // Close on backdrop click
    document.getElementById('settingsModal').addEventListener('click', function(e) {
        if (e.target === this) closeSettings();
    });
    // ─── STATE ───
    let currentConversationId = null;
    let currentSession = null;
    let sessions = [];

    function uuidv4() {
        return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        );
    }

    // ─── INIT ───
    document.addEventListener('DOMContentLoaded', () => {
        if (!localStorage.getItem('canifa_token')) {
            localStorage.setItem('canifa_token', 'dev-bypass-token');
            if (!localStorage.getItem('canifa_user')) {
                localStorage.setItem('canifa_user', JSON.stringify({ username: 'dev', role: 'user', id: 'dev-mode' }));
            }
        }

        const urlParams = new URLSearchParams(window.parent.location.search);
        const convId = urlParams.get('conversation_id');
        if (convId) {
            loadConversation(convId);
        } else {
            startNewConversation();
        }
        loadSidebarHistory();
    });

    document.getElementById('promptInput').addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            submitMessage();
        }
    });

    // ─── SIDEBAR & HISTORY ───
    function toggleHistory() {
        document.getElementById('historySidebar').classList.toggle('open');
    }

    async function loadSidebarHistory() {
        if (!localStorage.getItem('canifa_token')) return;
        try {
            const res = await fetch('/api/ai-sql/conversations', {
                headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('canifa_token') || '') }
            });
            const data = await res.json();
            const list = document.getElementById('convList');
            if (data.conversations && data.conversations.length > 0) {
                list.innerHTML = data.conversations.map(c => `
                    <div class="conv-item ${c.conversation_id === currentConversationId ? 'active' : ''}" onclick="loadConversation('${c.conversation_id}')">
                        <div class="conv-title">${escapeHTML(c.title || 'Untitled')}</div>
                        <div class="conv-meta">
                            <span class="pill ${c.status || 'completed'}">${c.status || 'done'}</span>
                            <span>${c.updated_at ? new Date(c.updated_at).toLocaleString('vi-VN') : ''}</span>
                        </div>
                    </div>
                `).join('');
            } else {
                list.innerHTML = '<div style="color: #a78bfa; font-size: 13px; text-align: center; padding: 20px;">Chưa có lịch sử.</div>';
            }
        } catch (e) {
            console.error('Lỗi tải lịch sử:', e);
        }
    }

    // ─── CONVERSATION LOGIC ───
    function startNewConversation() {
        currentConversationId = uuidv4();
        currentSession = null;
        sessions = [];

        document.getElementById('chatMessages').innerHTML = `
            <div class="chat-msg ai-msg">
                🔍 AI SQL Trace sẵn sàng phân tích.<br><br>
                Nhập câu hỏi để AI tự phân tích, tạo SQL, chạy query và trả kết quả dữ liệu sản phẩm.
            </div>
        `;
        document.getElementById('chatTitle').textContent = 'AI SQL Trace';
        updateURL(currentConversationId);
        resetDocumentPane();
        // Close sidebar if it's open
        const sidebar = document.getElementById('historySidebar');
        if (sidebar.classList.contains('open')) sidebar.classList.remove('open');
        loadSidebarHistory();
    }

    async function loadConversation(convId) {
        currentConversationId = convId;
        updateURL(convId);
        const sidebar = document.getElementById('historySidebar');
        if (sidebar.classList.contains('open')) sidebar.classList.remove('open');

        document.getElementById('chatMessages').innerHTML = `<div class="chat-typing"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>`;

        try {
            const res = await fetch(`/api/ai-sql/conversations/${convId}`, {
                headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('canifa_token') || '') }
            });
            const data = await res.json();
            if (data.error) throw new Error(data.error);

            sessions = data.sessions || [];

            // Render chat messages from sessions
            const chatHtml = sessions.map(s => `
                <div class="chat-msg user-msg">${escapeHTML(s.question)}</div>
                <div class="chat-msg ai-msg">
                    <span class="pill ${s.status}">${s.status === 'completed' ? '✅ Done' : s.status === 'error' ? '❌ Error' : '⏳ ' + s.status}</span>
                    ${s.session_data?.summary?.generation_time_ms ? ' • ' + (s.session_data.summary.generation_time_ms / 1000).toFixed(1) + 's' : ''}
                    ${s.session_data?.total_queries ? ' • ' + s.session_data.total_queries + ' queries' : ''}
                </div>
            `).join('');
            document.getElementById('chatMessages').innerHTML = chatHtml;
            document.getElementById('chatTitle').textContent = 'Lịch sử SQL Trace';
            scrollToBottom();

            // Render the latest session's trace in the right pane
            if (sessions.length > 0) {
                const latest = sessions[sessions.length - 1];
                currentSession = latest.session_data || {};
                currentSession.question = latest.question || '';
                currentSession.status = latest.status || 'completed';
                renderDataResults(currentSession);
            } else {
                resetDocumentPane();
            }

            loadSidebarHistory();
        } catch (e) {
            console.error(e);
            document.getElementById('chatMessages').innerHTML = `<div class="chat-msg ai-msg" style="color:#dc2626">Lỗi tải dữ liệu.</div>`;
        }
    }

    // ─── CHAT SUBMISSION (SSE) ───
    async function submitMessage() {
        const input = document.getElementById('promptInput');
        const question = input.value.trim();
        if (!question) return;

        input.value = '';
        input.style.height = '44px';

        const chatBox = document.getElementById('chatMessages');
        chatBox.innerHTML += `<div class="chat-msg user-msg">${escapeHTML(question)}</div>`;

        const agentId = 'agent_' + Date.now();
        chatBox.innerHTML += `
            <div class="chat-msg ai-msg" id="msg_${agentId}">
                <div class="agent-logs" id="log_${agentId}"></div>
                <div class="ai-text" id="text_${agentId}">
                    <div class="chat-typing"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>
                </div>
            </div>`;
        scrollToBottom();

        // Reset document pane for new trace
        document.getElementById('emptyDocState').style.display = 'none';
        const traceOut = document.getElementById('traceOutput');
        traceOut.style.display = 'block';
        traceOut.innerHTML = '<div class="trace-card"><div class="chat-typing"><div class="dot"></div><div class="dot"></div><div class="dot"></div> Đang phân tích...</div></div>';

        // Build session object for live rendering
        currentSession = {
            question: question,
            status: 'running',
            session_id: null,
            cycles: [],
            total_queries: 0,
            successful_queries: 0,
            failed_queries: 0,
            results: [],
            summary: {},
            created_at: new Date().toISOString(),
        };

        try {
            const response = await fetch('/api/ai-sql/trace', {
                method: 'POST',
                headers: { 
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + (localStorage.getItem('canifa_token') || '')
                },
                body: JSON.stringify({
                    question: question,
                    conversation_id: currentConversationId,
                    model: document.getElementById('modelSelect').value
                })
            });

            const reader = response.body.getReader();
            const decoder = new TextDecoder("utf-8");
            let buffer = "";

            while (true) {
                const { value, done } = await reader.read();
                if (done) break;
                buffer += decoder.decode(value, { stream: true });
                const lines = buffer.split('\n');
                buffer = lines.pop();
                for (let line of lines) {
                    if (line.trim() === '') continue;
                    if (line.startsWith('data: ')) {
                        try {
                            const data = JSON.parse(line.substring(6));
                            handleStreamEvent(data, agentId);
                        } catch (e) { console.error("Event parse error", e, line); }
                    }
                }
            }
        } catch (error) {
            console.error("Fetch error:", error);
            document.getElementById(`text_${agentId}`).innerHTML = `<span style="color:#dc2626"><i class="fas fa-exclamation-triangle"></i> Lỗi kết nối server.</span>`;
        }
    }

    function handleStreamEvent(data, agentId) {
        const logBox = document.getElementById(`log_${agentId}`);
        const textBox = document.getElementById(`text_${agentId}`);

        if (data.type === "session_started") {
            logBox.innerHTML += `<div class="agent-log-item think">Session #${data.session_id} started</div>`;
            currentSession.session_id = data.session_id;

        } else if (data.type === "thinking") {
            logBox.innerHTML += `<div class="agent-log-item think">${escapeHTML(data.step)}</div>`;

        } else if (data.type === "pending_approval") {
            // Store tools for later matching with results
            currentSession._pendingTools = data.tools || [];
            currentSession._pendingToolIdx = 0;

            // Show SQL for user to approve/reject
            const tools = data.tools || [];
            const sqlBlocks = tools.map(t => {
                const sql = t.params?.sql || t.params?.expression || '';
                return `<div style="margin-bottom:12px;">
                    <div style="font-size:11px;color:#a78bfa;font-weight:600;margin-bottom:4px;">[${escapeHTML(t.name)}] ${escapeHTML(t.purpose || '')}</div>
                    <pre class="sql-block" style="margin:0;font-size:11px;">${escapeHTML(sql)}</pre>
                </div>`;
            }).join('');

            logBox.innerHTML += `<div class="agent-log-item think">Cycle ${data.cycle}: AI da sinh SQL. Cho duyet...</div>`;

            textBox.innerHTML = `
                <div style="margin-top:8px;">
                    <div style="font-size:12px;color:var(--text-muted);margin-bottom:6px;">${escapeHTML(data.thinking || '')}</div>
                    ${sqlBlocks}
                    <div style="display:flex; gap:8px; margin-top:10px;" id="approvalBtns_${data.cycle}">
                        <button onclick="approveSQL(${currentSession.session_id}, ${data.cycle}, '${agentId}')" style="padding:8px 20px; border-radius:8px; border:none; background:#059669; color:white; font-weight:700; font-size:13px; cursor:pointer; display:flex; align-items:center; gap:6px; transition: all 0.2s;" onmouseenter="this.style.background='#047857'" onmouseleave="this.style.background='#059669'"><i class="fas fa-check"></i> Accept</button>
                        <button onclick="rejectSQL(${currentSession.session_id}, ${data.cycle}, '${agentId}')" style="padding:8px 20px; border-radius:8px; border:1px solid #dc2626; background:white; color:#dc2626; font-weight:700; font-size:13px; cursor:pointer; display:flex; align-items:center; gap:6px; transition: all 0.2s;" onmouseenter="this.style.background='#fef2f2'" onmouseleave="this.style.background='white'"><i class="fas fa-times"></i> Reject</button>
                    </div>
                </div>
            `;

            // Show SQL in right pane too
            renderPendingSQL(data);

        } else if (data.type === "executing") {
            logBox.innerHTML += `<div class="agent-log-item data">Dang chay ${data.tools_count} queries...</div>`;
            textBox.innerHTML = `<div class="chat-typing"><div class="dot"></div><div class="dot"></div><div class="dot"></div> Dang chay SQL...</div>`;

        } else if (data.type === "tool_result") {
            currentSession.total_queries++;
            if (data.success) currentSession.successful_queries++;
            else currentSession.failed_queries++;

            // Match with pending tool to get purpose + SQL
            const pendingTools = currentSession._pendingTools || [];
            const idx = currentSession._pendingToolIdx || 0;
            const matchedTool = pendingTools[idx] || {};
            currentSession._pendingToolIdx = idx + 1;

            currentSession.results.push({
                tool: data.tool,
                success: data.success,
                preview: data.preview,
                purpose: matchedTool.purpose || '',
                sql: matchedTool.params?.sql || matchedTool.params?.expression || '',
            });

            logBox.innerHTML += `<div class="agent-log-item ${data.success ? 'data' : 'think'}">${data.success ? 'OK' : 'ERR'} [${escapeHTML(data.tool)}] ${data.preview?.row_count || 0} rows</div>`;

            // Update right pane with data table
            renderDataResults(currentSession);

        } else if (data.type === "reflect") {
            if (data.summary) currentSession.ai_summary = data.summary;
            if (!data.data_sufficient && data.missing?.length) {
                logBox.innerHTML += `<div class="agent-log-item think">Reflect: Can them du lieu...</div>`;
            } else {
                logBox.innerHTML += `<div class="agent-log-item data">Reflect: Du du lieu</div>`;
            }

        } else if (data.type === "rejected") {
            logBox.innerHTML += `<div class="agent-log-item think">SQL da bi tu choi.</div>`;
            textBox.innerHTML = `<span style="color:var(--text-muted)"><i class="fas fa-ban"></i> SQL da bi tu choi.</span>`;

        } else if (data.type === "complete") {
            currentSession.status = 'completed';
            currentSession.summary = data;
            if (data.ai_summary) currentSession.ai_summary = data.ai_summary;
            if (data.conversation_id) currentConversationId = data.conversation_id;
            textBox.innerHTML = `<span class="pill completed">Done</span> ${data.generation_time_ms ? (data.generation_time_ms / 1000).toFixed(1) + 's' : ''} | ${data.total_queries || 0} queries | ${data.cycles_completed || 0} cycles`;
            renderDataResults(currentSession);
            loadSidebarHistory();

        } else if (data.type === "error") {
            currentSession.status = 'error';
            textBox.innerHTML = `<span style="color:#dc2626"><i class="fas fa-exclamation-triangle"></i> ${escapeHTML(data.message)}</span>`;
            loadSidebarHistory();
        }
        scrollToBottom();
    }

    // ─── APPROVAL ACTIONS ───
    async function approveSQL(sessionId, cycle, agentId) {
        const btns = document.getElementById(`approvalBtns_${cycle}`);
        if (btns) btns.innerHTML = '<span style="color:var(--success);font-size:12px;"><i class="fas fa-spinner fa-spin"></i> Dang chay...</span>';

        try {
            await fetch(`/api/ai-sql/approve/${sessionId}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + (localStorage.getItem('canifa_token') || '')
                },
                body: JSON.stringify({ action: 'accept' })
            });
        } catch (e) {
            console.error('Approve error:', e);
        }
    }

    async function rejectSQL(sessionId, cycle, agentId) {
        const btns = document.getElementById(`approvalBtns_${cycle}`);
        if (btns) btns.innerHTML = '<span style="color:var(--error);font-size:12px;"><i class="fas fa-ban"></i> Da tu choi</span>';

        try {
            await fetch(`/api/ai-sql/approve/${sessionId}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + (localStorage.getItem('canifa_token') || '')
                },
                body: JSON.stringify({ action: 'reject' })
            });
        } catch (e) {
            console.error('Reject error:', e);
        }
    }

    // ─── RIGHT PANE RENDERERS ───
    function renderPendingSQL(data) {
        document.getElementById('emptyDocState').style.display = 'none';
        const traceOut = document.getElementById('traceOutput');
        traceOut.style.display = 'block';
        document.getElementById('docTitle').textContent = currentSession.question || 'SQL Trace';

        const tools = data.tools || [];
        const sqlCards = tools.map(t => {
            const sql = t.params?.sql || t.params?.expression || '';
            return `
                <div class="query-item">
                    <div class="query-item-header">
                        <span class="query-item-name">${escapeHTML(t.name)}</span>
                        <span class="pill running">Cho duyet</span>
                    </div>
                    <div class="query-item-purpose">${escapeHTML(t.purpose || '')}</div>
                    <pre class="sql-block">${escapeHTML(sql)}</pre>
                </div>
            `;
        }).join('');

        traceOut.innerHTML = `
            <div class="trace-card">
                <div class="trace-card-header">
                    <div>
                        <div class="trace-card-label">Cau hoi phan tich</div>
                        <div class="trace-card-title">${escapeHTML(currentSession.question || '')}</div>
                    </div>
                    <span class="pill running">Cho duyet</span>
                </div>
                ${data.thinking ? `<div class="trace-card-text">${escapeHTML(data.thinking)}</div>` : ''}
                ${sqlCards}
            </div>
        `;
    }

    function renderDataResults(session) {
        document.getElementById('emptyDocState').style.display = 'none';
        const traceOut = document.getElementById('traceOutput');
        traceOut.style.display = 'block';
        document.getElementById('docTitle').textContent = session.question || 'SQL Results';

        const s = session.summary || {};
        const headerHtml = `
            <div class="trace-card">
                <div class="trace-card-header">
                    <div>
                        <div class="trace-card-label">Ket qua</div>
                        <div class="trace-card-title">${escapeHTML(session.question || '')}</div>
                    </div>
                    <span class="pill ${session.status === 'completed' ? 'completed' : session.status === 'error' ? 'error' : 'running'}">${session.status || 'running'}</span>
                </div>
                <div class="summary-grid">
                    <div class="summary-block">
                        <div class="label">Trang thai</div>
                        <div class="value">${session.status === 'completed' ? '✅' : session.status === 'error' ? '❌' : '⏳'}</div>
                    </div>
                    <div class="summary-block">
                        <div class="label">Queries</div>
                        <div class="value">${session.total_queries || 0}</div>
                        <div class="meta">${session.successful_queries || 0} OK / ${session.failed_queries || 0} Loi</div>
                    </div>
                    <div class="summary-block">
                        <div class="label">Thoi gian</div>
                        <div class="value">${s.generation_time_ms ? (s.generation_time_ms / 1000).toFixed(1) + 's' : '...'}</div>
                    </div>
                    <div class="summary-block">
                        <div class="label">Cycles</div>
                        <div class="value">${s.cycles_completed || '...'}</div>
                    </div>
                </div>
            </div>
        `;

        // AI Summary card (if available)
        const summaryHtml = session.ai_summary ? `
            <div class="trace-card" style="border-left:4px solid var(--primary);">
                <div style="display:flex; align-items:flex-start; gap:10px;">
                    <span style="font-size:18px;">🤖</span>
                    <div style="font-size:13px; color:#334155; line-height:1.6;">${escapeHTML(session.ai_summary)}</div>
                </div>
            </div>
        ` : '';

        // Collect all rows with data
        const dataResults = (session.results || []).filter(r => r.preview?.row_count > 0);

        if (!dataResults.length) {
            traceOut.innerHTML = headerHtml + summaryHtml + `
                <div class="trace-card">
                    <div style="text-align:center; padding:20px; color:var(--text-muted);">
                        <i class="fas fa-inbox" style="font-size:32px; opacity:0.3; margin-bottom:10px;"></i>
                        <div style="font-size:13px;">Không tìm thấy dữ liệu cho câu hỏi này.</div>
                    </div>
                </div>`;
            return;
        }

        // Build chart + table cards
        let cardsHtml = '';
        const chartsToRender = [];

        dataResults.forEach((r, idx) => {
            const p = r.preview || {};
            const rows = p.preview_rows || [];
            const cols = p.columns || Object.keys(rows[0] || {});

            // Auto-detect chart type from data shape
            const chartData = autoDetectChart(rows, cols, r.purpose || r.tool || 'Query');

            if (chartData) {
                const rendered = renderChartFromData(chartData);
                if (rendered) {
                    cardsHtml += rendered.html;
                    if (rendered.config && rendered.canvasId) {
                        chartsToRender.push({ config: rendered.config, canvasId: rendered.canvasId });
                    }
                }
            }

            // Always show data table below chart
            cardsHtml += renderResultTable(rows, cols, r.purpose || r.tool, p.row_count);
        });

        traceOut.innerHTML = headerHtml + summaryHtml + cardsHtml;

        // Render Chart.js canvases after DOM update
        setTimeout(() => {
            chartsToRender.forEach(ch => {
                const canvas = document.getElementById(ch.canvasId);
                if (canvas) {
                    new Chart(canvas.getContext('2d'), ch.config);
                }
            });
        }, 50);
    }

    function renderPreviewTable(rows, columns) {
        if (!rows || !rows.length) return '<div style="color:var(--text-muted);font-size:12px;padding:10px;">Khong co du lieu</div>';
        const cols = (columns && columns.length) ? columns : Object.keys(rows[0] || {});
        const head = cols.map(c => `<th>${escapeHTML(c)}</th>`).join('');
        const body = rows.map(row =>
            `<tr>${cols.map(c => `<td>${escapeHTML(String(row[c] ?? ''))}</td>`).join('')}</tr>`
        ).join('');
        return `
            <div class="preview-table-wrap">
                <table class="preview-table">
                    <thead><tr>${head}</tr></thead>
                    <tbody>${body}</tbody>
                </table>
            </div>
        `;
    }

    // ─── DOCUMENT PANE ───
    function resetDocumentPane() {
        document.getElementById('emptyDocState').style.display = 'flex';
        document.getElementById('traceOutput').style.display = 'none';
        document.getElementById('traceOutput').innerHTML = '';
        document.getElementById('docTitle').textContent = 'Chua co trace';
    }

    // ─── UTILS ───
    function escapeHTML(str) {
        if(!str) return "";
        return str.toString().replace(/[&<>'"]/g, tag => ({
            '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;'
        }[tag]));
    }

    function scrollToBottom() {
        const chat = document.getElementById('chatMessages');
        chat.scrollTop = chat.scrollHeight;
    }

    function updateURL(convId) {
        try {
            const url = new URL(window.parent.location);
            url.searchParams.set('conversation_id', convId);
            window.parent.history.replaceState({}, '', url);
        } catch(e) {}
    }

    function copyAllResults() {
        if (!currentSession?.results) return;
        let csvParts = [];
        currentSession.results.forEach(r => {
            const p = r.preview || {};
            if (p.preview_rows?.length) {
                const cols = p.columns || Object.keys(p.preview_rows[0] || {});
                csvParts.push(cols.join(','));
                p.preview_rows.forEach(row => {
                    csvParts.push(cols.map(c => {
                        let v = String(row[c] ?? '');
                        if (v.includes(',') || v.includes('"')) v = '"' + v.replace(/"/g, '""') + '"';
                        return v;
                    }).join(','));
                });
                csvParts.push('');
            }
        });
        if (csvParts.length) {
            navigator.clipboard.writeText(csvParts.join('\n'));
            alert('Da copy CSV!');
        }
    }
    // ─── AUTO CHART DETECTION ───
    function autoDetectChart(rows, cols, title) {
        if (!rows || !rows.length || !cols?.length) return null;

        // Find numeric columns
        const numCols = cols.filter(c => rows.some(r => typeof r[c] === 'number' && r[c] !== null));
        // Find string/label columns
        const labCols = cols.filter(c => !numCols.includes(c));

        if (numCols.length === 0) return null;

        // Single row with many numeric columns → number cards
        if (rows.length === 1 && numCols.length >= 3) {
            return {
                chart_type: 'numberCard',
                title: title,
                data: numCols.map(c => ({ label: c, value: rows[0][c] })),
                isNumberCards: true, // custom flag
            };
        }

        // Single row, 1-2 numeric cols → number card
        if (rows.length === 1 && numCols.length <= 2) {
            return {
                chart_type: 'numberCard',
                title: title,
                data: numCols.map(c => ({ label: c, value: rows[0][c] })),
                isNumberCards: true,
            };
        }

        // Time series detection (column named ngay, date, etc.)
        const dateCol = labCols.find(c => /ngay|date|time|thoi_gian|gio/i.test(c));
        if (dateCol && numCols.length >= 1 && rows.length > 1) {
            return {
                chart_type: rows.length > 5 ? 'line' : 'bar',
                title: title,
                data: rows,
                x_key: dateCol,
                y_keys: numCols.slice(0, 4),
            };
        }

        // Category breakdown with multiple rows
        if (labCols.length >= 1 && numCols.length >= 1 && rows.length > 1) {
            // If 2-6 categories, use doughnut/pie. Else bar.
            if (rows.length <= 6 && numCols.length === 1) {
                return {
                    chart_type: 'doughnut',
                    title: title,
                    data: rows,
                    x_key: labCols[0],
                    y_keys: numCols.slice(0, 1),
                    total_label: 'Tổng',
                };
            }
            return {
                chart_type: numCols.length > 2 ? 'multiBar' : 'bar',
                title: title,
                data: rows,
                x_key: labCols[0],
                y_keys: numCols.slice(0, 4),
            };
        }

        return null;
    }

    function renderChartFromData(chartData) {
        if (!chartData) return null;

        // Custom number cards rendering
        if (chartData.isNumberCards) {
            const cardsHtml = chartData.data.map(d => `
                <div style="display:inline-flex; flex-direction:column; align-items:center; padding:16px 20px; background:linear-gradient(135deg, #faf5ff 0%, #f0f2f5 100%); border-radius:12px; min-width:120px; border: 1px solid rgba(124,58,237,0.1);">
                    <div style="font-size:28px; font-weight:800; color:#4f46e5; line-height:1;">${typeof d.value === 'number' ? d.value.toLocaleString('vi-VN') : d.value}</div>
                    <div style="font-size:10px; color:#64748b; margin-top:6px; text-transform:uppercase; font-weight:600; letter-spacing:0.5px;">${escapeHTML(d.label)}</div>
                </div>
            `).join('');
            return {
                html: `<div class="chart-card">
                    <div class="cc-header"><div class="cc-title">${escapeHTML(chartData.title)}</div></div>
                    <div class="cc-body" style="display:flex; flex-wrap:wrap; gap:12px; justify-content:center; padding:16px 0;">${cardsHtml}</div>
                </div>`,
                config: null, canvasId: null,
            };
        }

        // Use chart-templates.js renderers
        const renderers = {
            bar: renderBar,
            horizontalBar: renderHorizontalBar,
            multiBar: renderMultiBar,
            stackedBar: renderStackedBar,
            line: renderLine,
            area: renderArea,
            pie: renderPie,
            doughnut: renderDoughnut,
            scatter: renderScatter,
            combo: renderCombo,
            numberCard: renderNumberCard,
        };

        const renderer = renderers[chartData.chart_type] || renderBar;
        try {
            return renderer(chartData);
        } catch (e) {
            console.warn('Chart render error:', e);
            return null;
        }
    }

    function renderResultTable(rows, cols, title, rowCount) {
        if (!rows?.length) return '';
        const displayCols = cols || Object.keys(rows[0] || {});
        const head = displayCols.map(c => `<th>${escapeHTML(c)}</th>`).join('');
        const body = rows.map(row =>
            `<tr>${displayCols.map(c => `<td>${escapeHTML(String(row[c] ?? ''))}</td>`).join('')}</tr>`
        ).join('');
        return `
            <div class="trace-card" style="margin-top:8px;">
                <div class="trace-card-header">
                    <div><div class="trace-card-label" style="font-size:12px;">${escapeHTML(title || 'Data')}</div></div>
                    <span class="pill completed" style="font-size:10px;">${rowCount || rows.length} rows</span>
                </div>
                <div class="preview-table-wrap">
                    <table class="preview-table">
                        <thead><tr>${head}</tr></thead>
                        <tbody>${body}</tbody>
                    </table>
                </div>
            </div>
        `;
    }