// ─── STATE ───
    let currentConversationId = null;
    let currentReportsChain = []; // Array of reports in the current conversation
    let currentReportIndex = -1; // Index in currentReportsChain
    let activeAgentBubble = null; // DOM element for streaming AI log
    
    // UUID Generator for new 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)
        );
    }

    // ─── AUTH FETCH WITH AUTO-REFRESH ───
    function getToken() { return localStorage.getItem('canifa_token') || ''; }

    async function authFetch(url, options = {}) {
        options.headers = { ...options.headers, 'Authorization': 'Bearer ' + getToken() };
        let res = await fetch(url, options);
        if (res.status === 401) {
            // Try to refresh the token
            const refreshRes = await fetch('/api/auth/refresh', {
                method: 'POST',
                headers: { 'Authorization': 'Bearer ' + getToken() }
            });
            if (refreshRes.ok) {
                const data = await refreshRes.json();
                localStorage.setItem('canifa_token', data.access_token);
                options.headers['Authorization'] = 'Bearer ' + data.access_token;
                res = await fetch(url, options);
            } else {
                // Token fully expired — keep running in dev mode
                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' }));
                }
                return res;
            }
        }
        return res;
    }

    // Proactive refresh every 20 hours to prevent mid-session expiry
    setInterval(async () => {
        if (!getToken()) return;
        try {
            const res = await fetch('/api/auth/refresh', {
                method: 'POST',
                headers: { 'Authorization': 'Bearer ' + getToken() }
            });
            if (res.ok) {
                const data = await res.json();
                localStorage.setItem('canifa_token', data.access_token);
            }
        } catch (e) { console.warn('Token refresh failed:', e); }
    }, 20 * 60 * 60 * 1000); // 20 hours

    // ─── INITIALIZATION ───
    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();
        resumePendingTask();
        setupInlineEdit();
    });

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

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

    async function loadSidebarHistory() {
        if (!getToken()) return;
        try {
            const res = await authFetch('/api/reports/conversations');
            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 || 'Hội thoại mới')}</div>
                        <div class="conv-meta">${new Date(c.updated_at).toLocaleString('vi-VN')}</div>
                    </div>
                `).join('');
            } else {
                list.innerHTML = '<div style="color: #94a3b8; 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();
        currentReportsChain = [];
        currentReportIndex = -1;
        
        document.getElementById('chatMessages').innerHTML = `
            <div class="chat-msg ai-msg" id="welcomeMsg">
                👋 Chào sếp, tính năng Báo cáo & Phân tích Dữ liệu Hệ thống sẵn sàng.<br><br>
                Sếp muốn tạo báo cáo doanh thu, tồn kho hay tra cứu thông tin gì?
            </div>
        `;
        document.getElementById('chatTitle').textContent = 'Hội thoại mới';
        
        updateURL(currentConversationId);
        resetDocumentPane();
        toggleHistory(); // Close sidebar if open
        loadSidebarHistory(); // Remove active state
    }

    async function loadConversation(convId) {
        currentConversationId = convId;
        updateURL(convId);
        toggleHistory(); // Hide sidebar
        
        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 authFetch(`/api/reports/conversations/${convId}`);
            const data = await res.json();
            
            if (data.error) throw new Error(data.error);
            
            // 1. Render Chat Messages
            const chatHtml = data.messages.map(m => {
                if (m.is_human) {
                    return `<div class="chat-msg user-msg">${escapeHTML(m.text)}</div>`;
                } else {
                    // Try to parse if it's a report link
                    let text = escapeHTML(m.text);
                    const match = text.match(/\|REPORT:(\d+)/);
                    if (match) {
                        const rId = match[1];
                        text = text.replace(/\|REPORT:\d+/, '') + `<br><br><button class="btn-action" style="font-size:11px; padding: 4px 8px; display:inline-flex;" onclick="viewReportId(${rId})"><i class="fas fa-eye"></i> Xem báo cáo này</button>`;
                    }
                    return `<div class="chat-msg ai-msg">${text}</div>`;
                }
            }).join('');
            
            document.getElementById('chatMessages').innerHTML = chatHtml;
            document.getElementById('chatTitle').textContent = 'Lịch sử cuộc trò chuyện';
            scrollToBottom();
            
            // 2. Setup Reports Chain
            currentReportsChain = data.reports || [];
            if (currentReportsChain.length > 0) {
                // View the latest version automatically
                currentReportIndex = currentReportsChain.length - 1;
                viewReportId(currentReportsChain[currentReportIndex].id);
            } else {
                resetDocumentPane();
            }
            
            loadSidebarHistory(); // Update active state
            
        } catch (e) {
            console.error(e);
            document.getElementById('chatMessages').innerHTML = `<div class="chat-msg ai-msg" style="color:#ef4444">Lỗi tải dữ liệu.</div>`;
        }
    }

    // ─── CHAT SUBMISSION ───
    async function submitMessage() {
        const input = document.getElementById('promptInput');
        const question = input.value.trim();
        if (!question) return;
        
        // 1. Optimistic UI Update
        input.value = '';
        input.style.height = '44px';
        
        const chatBox = document.getElementById('chatMessages');
        chatBox.innerHTML += `<div class="chat-msg user-msg">${escapeHTML(question)}</div>`;
        
        // Prepare AI bubble
        const agentId = 'agent_' + Date.now();
        chatBox.innerHTML += `
            <div class="chat-msg ai-msg" id="msg_${agentId}">
                <div class="agent-process" id="process_${agentId}">
                    <div class="agent-process-header" onclick="this.parentElement.classList.toggle('collapsed')">
                        <div class="process-icon" id="processIcon_${agentId}"><i class="fas fa-cog fa-spin"></i></div>
                        <span class="process-label" id="processLabel_${agentId}">Đang phân tích...</span>
                        <i class="fas fa-chevron-down process-toggle"></i>
                    </div>
                    <div class="agent-process-body" id="log_${agentId}"></div>
                </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();
        
        // Determine parent_report_id if this is a follow-up
        let parentId = null;
        let currentHtml = null;
        if (currentReportsChain.length > 0 && currentReportIndex >= 0) {
            parentId = currentReportsChain[currentReportIndex].id;
            const reportOut = document.getElementById('report-output');
            currentHtml = reportOut.innerHTML;
        }

        try {
            const response = await authFetch('/api/report-html', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    question: question,
                    conversation_id: currentConversationId,
                    parent_report_id: parentId,
                    current_html: currentHtml,
                    model: document.getElementById('modelSelect').value
                })
            });

            // Check if response is JSON (queued mode) or SSE stream (direct mode)
            const contentType = response.headers.get('content-type') || '';
            
            if (contentType.includes('application/json')) {
                // ── QUEUED MODE: Redis-backed ──
                const result = await response.json();
                if (result.mode === 'queued' && result.task_id) {
                    // Save task for reload recovery
                    sessionStorage.setItem('report_task_id', result.task_id);
                    sessionStorage.setItem('report_agent_id', agentId);
                    
                    // Connect to SSE stream for this task
                    connectTaskStream(result.task_id, agentId);
                }
            } else {
                // ── DIRECT MODE: old SSE fallback ──
                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:#ef4444">Lỗi kết nối server.</span>`;
        }
    }

    // ── Redis Queue SSE Stream Connector ──
    function connectTaskStream(taskId, agentId, lastId = '0-0') {
        const url = `/api/report-html/stream/${taskId}?last_id=${encodeURIComponent(lastId)}`;
        const source = new EventSource(url);
        let latestMsgId = lastId;

        source.onmessage = (e) => {
            try {
                const data = JSON.parse(e.data);
                if (data._msg_id) latestMsgId = data._msg_id;
                handleStreamEvent(data, agentId);
                
                if (data.type === 'done' || data.type === 'error') {
                    source.close();
                    sessionStorage.removeItem('report_task_id');
                    sessionStorage.removeItem('report_agent_id');
                    sessionStorage.removeItem('report_last_msg_id');
                }
            } catch (err) { console.error("SSE parse error", err); }
        };

        source.onerror = () => {
            source.close();
            // Auto-reconnect after 2s with last known position
            sessionStorage.setItem('report_last_msg_id', latestMsgId);
            console.log('SSE disconnected, reconnecting in 2s from', latestMsgId);
            setTimeout(() => connectTaskStream(taskId, agentId, latestMsgId), 2000);
        };
    }

    // ── Resume pending task on page reload ──
    function resumePendingTask() {
        const taskId = sessionStorage.getItem('report_task_id');
        const agentId = sessionStorage.getItem('report_agent_id');
        if (taskId && agentId) {
            const lastId = sessionStorage.getItem('report_last_msg_id') || '0-0';
            console.log('📡 Resuming task:', taskId, 'from', lastId);
            connectTaskStream(taskId, agentId, lastId);
        }
    }

    function handleStreamEvent(data, agentId) {
        const logBox = document.getElementById(`log_${agentId}`);
        const textBox = document.getElementById(`text_${agentId}`);
        
        if (data.type === "thinking") {
            const label = document.getElementById(`processLabel_${agentId}`);
            if (label) label.textContent = data.step;
            scrollToBottom();

        } else if (data.type === "thinking_token") {
            let thinkEl = document.getElementById(`think_stream_${agentId}`);
            if (!thinkEl) {
                logBox.innerHTML += `<div class="thinking-stream streaming" id="think_stream_${agentId}"></div>`;
                thinkEl = document.getElementById(`think_stream_${agentId}`);
            }
            thinkEl.textContent += data.token;
            logBox.scrollTop = logBox.scrollHeight;

        } else if (data.type === "plan_ready") {
            const thinkEl = document.getElementById(`think_stream_${agentId}`);
            if (thinkEl) thinkEl.classList.remove('streaming');
            
            const label = document.getElementById(`processLabel_${agentId}`);
            if (label) label.textContent = `${data.current_task || 'Kế hoạch'} — ${data.tools_count} truy vấn (vòng ${data.cycle})`;
            
            const tasks = (data.sub_tasks || []);
            if (tasks.length > 0) {
                let chipsHtml = '<div class="tool-chips">';
                tasks.forEach(t => {
                    chipsHtml += `<span class="tool-chip pending">${escapeHTML(t)}</span>`;
                });
                chipsHtml += '</div>';
                logBox.innerHTML += chipsHtml;
            }
            scrollToBottom();

        } else if (data.type === "tool_call") {
             const toolName = data.tool || data.tool_name || 'unknown';
             const sql = data.params?.sql || '';
             logBox.innerHTML += `
                <div class="process-step">
                    <div class="step-icon query"><i class="fas fa-database"></i></div>
                    <div class="step-text">
                        <span class="step-label">${escapeHTML(toolName)}</span> ${escapeHTML(data.purpose || '')}
                        ${sql ? `<span class="step-detail">${escapeHTML(sql.substring(0, 120))}${sql.length > 120 ? '...' : ''}</span>` : ''}
                    </div>
                </div>`;
             scrollToBottom();

        } else if (data.type === "tool_result") {
             const isOK = data.success;
             const iconClass = isOK ? 'success' : 'error';
             const icon = isOK ? 'fa-check' : 'fa-times';
             const preview = data.preview || {};
             const detail = isOK
                 ? `${preview.row_count || 0} dòng dữ liệu`
                 : (preview.error || 'Lỗi truy vấn');
             logBox.innerHTML += `
                <div class="process-step">
                    <div class="step-icon ${iconClass}"><i class="fas ${icon}"></i></div>
                    <div class="step-text">
                        <span class="step-label">${escapeHTML(data.tool || '')}</span> — ${escapeHTML(String(detail).substring(0, 100))}
                    </div>
                </div>`;
             scrollToBottom();

        } else if (data.type === "reflect") {
            const sufficient = data.data_sufficient;
            const iconClass = sufficient ? 'success' : 'reflect';
            const icon = sufficient ? 'fa-check-double' : 'fa-sync-alt';
            const msg = sufficient ? 'Đã đủ dữ liệu → đang viết báo cáo...' : 'Cần thêm dữ liệu...';
            
            logBox.innerHTML += `
                <div class="process-step">
                    <div class="step-icon ${iconClass}"><i class="fas ${icon}"></i></div>
                    <div class="step-text"><span class="step-label">${escapeHTML(msg)}</span></div>
                </div>`;
            
            if (data.missing && data.missing.length > 0) {
                logBox.innerHTML += `<div class="tool-chips">${data.missing.map(m => `<span class="tool-chip error">⚠ ${escapeHTML(m)}</span>`).join('')}</div>`;
            }
            
            const label = document.getElementById(`processLabel_${agentId}`);
            if (label) label.textContent = sufficient ? '✅ Phân tích hoàn tất — đang viết...' : `🔄 Vòng ${data.cycle}: thu thập thêm...`;
            scrollToBottom();

        } else if (data.type === "html_token") {
            // ── Throttled progressive rendering ──
            // Buffer tokens, render to DOM every 300ms for smooth section-by-section display
            if (!window._htmlStream) window._htmlStream = {};
            if (!window._htmlStream[agentId]) {
                window._htmlStream[agentId] = { buffer: '', rendered: '', timer: null };
            }
            const stream = window._htmlStream[agentId];
            stream.buffer += data.token;
            
            const reportOut = document.getElementById('report-output');
            const emptyState = document.getElementById('emptyDocState');
            if (emptyState) emptyState.style.display = 'none';
            reportOut.style.display = 'block';
            
            // Throttle: render at most once every 300ms
            if (!stream.timer) {
                stream.timer = setTimeout(() => {
                    stream.timer = null;
                    if (stream.buffer !== stream.rendered) {
                        reportOut.innerHTML = stream.buffer;
                        stream.rendered = stream.buffer;
                        // Auto-scroll report panel to bottom
                        const paper = document.getElementById('paperContainer');
                        if (paper) paper.scrollTop = paper.scrollHeight;
                    }
                }, 300);
            }
            
            // Collapse the process panel when writing starts
            const process = document.getElementById(`process_${agentId}`);
            if (process && !process.classList.contains('collapsed')) {
                process.classList.add('collapsed');
                const procIcon = document.getElementById(`processIcon_${agentId}`);
                if (procIcon) { procIcon.classList.add('done'); procIcon.innerHTML = '<i class="fas fa-check"></i>'; }
            }

        } else if (data.type === "direct_response") {
            const process = document.getElementById(`process_${agentId}`);
            if (process) process.style.display = 'none';
            textBox.innerHTML = escapeHTML(data.message);
            scrollToBottom();

        } else if (data.type === "html_complete") {
             const rId = data.report_id;
             
             // Final render — use the clean complete HTML from backend
             const reportOut = document.getElementById('report-output');
             const finalHtml = data.html || (window._htmlStream && window._htmlStream[agentId] ? window._htmlStream[agentId].buffer : '');
             
             // Clear throttle timer
             if (window._htmlStream && window._htmlStream[agentId]) {
                 clearTimeout(window._htmlStream[agentId].timer);
                 delete window._htmlStream[agentId];
             }
             
             reportOut.innerHTML = finalHtml;
             
             // Execute any <script> tags (e.g. Chart.js)
             const scripts = reportOut.getElementsByTagName('script');
             for (let i = 0; i < scripts.length; i++) {
                 const newScript = document.createElement('script');
                 if (scripts[i].src) {
                     newScript.src = scripts[i].src;
                 } else {
                     newScript.textContent = scripts[i].textContent;
                 }
                 document.body.appendChild(newScript);
             }
             
             const procIcon = document.getElementById(`processIcon_${agentId}`);
             if (procIcon) { procIcon.classList.add('done'); procIcon.innerHTML = '<i class="fas fa-check"></i>'; }
             const label = document.getElementById(`processLabel_${agentId}`);
             if (label) label.textContent = '✅ Báo cáo đã hoàn thành';
             const process = document.getElementById(`process_${agentId}`);
             if (process) process.classList.add('collapsed');
             
             textBox.innerHTML = `Đã tạo xong Báo cáo.<br><br><button class="btn-action" style="font-size:11px; padding: 4px 8px; display:inline-flex;" onclick="viewReportId(${rId})"><i class="fas fa-eye"></i> Xem báo cáo</button>`;
             loadConversation(currentConversationId);
             
        } else if (data.type === "done") {
            const typing = textBox.querySelector('.chat-typing');
            if (typing) typing.remove();
            
            const procIcon = document.getElementById(`processIcon_${agentId}`);
            if (procIcon && !procIcon.classList.contains('done')) {
                procIcon.classList.add('done');
                procIcon.innerHTML = '<i class="fas fa-check"></i>';
            }

        } else if (data.type === "error") {
            textBox.innerHTML = `<span style="color:#ef4444"><i class="fas fa-exclamation-triangle"></i> ${escapeHTML(data.message)}</span>`;
            const procIcon = document.getElementById(`processIcon_${agentId}`);
            if (procIcon) { procIcon.style.background = '#ef4444'; procIcon.innerHTML = '<i class="fas fa-times"></i>'; }
        }
        
        scrollToBottom();
    }

    // ─── DOCUMENT RENDERER ───
    async function viewReportId(reportId) {
        try {
            document.getElementById('emptyDocState').style.display = 'none';
            document.getElementById('report-output').style.display = 'block';
            document.getElementById('followup-container').style.display = 'block';
            document.getElementById('report-output').innerHTML = '<div class="chat-typing"><div class="dot"></div><div class="dot"></div><div class="dot"></div> Đang tải báo cáo...</div>';
            
            const res = await authFetch(`/api/reports/${reportId}`);
            const data = await res.json();
            if (data.error || !data.report) throw new Error(data.error || "Không tìm thấy report");
            
            const r = data.report;
            
            // Render HTML
            const reportOut = document.getElementById('report-output');
            reportOut.innerHTML = r.html_content;
            
            // Extract and execute scripts so Chart.js can render
            const scripts = reportOut.getElementsByTagName('script');
            for (let i = 0; i < scripts.length; i++) {
                const newScript = document.createElement('script');
                if (scripts[i].src) {
                    newScript.src = scripts[i].src;
                } else {
                    newScript.textContent = scripts[i].innerHTML;
                }
                document.body.appendChild(newScript);
                // We keep the script in the document so global configurations persist if needed
            }
            
            // Update Title
            document.getElementById('docTitle').textContent = r.title || 'Báo cáo chưa có tên';
            
            // Re-run prism if any code blocks
            if (window.Prism) Prism.highlightAllUnder(reportOut);
            
            // Update version Nav
            updateVersionNav(r.version);
            
            // Update active index in chain array (if it exists)
            const index = currentReportsChain.findIndex(x => x.id === parseInt(reportId));
            if (index !== -1) currentReportIndex = index;

        } catch (e) {
            console.error(e);
            document.getElementById('report-output').innerHTML = `<div style="color:#ef4444; padding: 20px;">Lỗi tải báo cáo: ${e.message}</div>`;
        }
    } // <-- MISSING BRACE FIXED

    function resetDocumentPane() {
        document.getElementById('emptyDocState').style.display = 'flex';
        document.getElementById('report-output').style.display = 'none';
        document.getElementById('followup-container').style.display = 'none';
        document.getElementById('docTitle').textContent = 'Chưa có báo cáo';
        document.getElementById('versionNav').style.display = 'none';
        document.getElementById('report-output').innerHTML = '';
        currentReportsChain = [];
        currentReportIndex = -1;
    }

    // ─── FOLLOW-UP SSE HANDLER ───
    let followUpAppendixIndex = 0;

    async function submitFollowup() {
        const inputRaw = document.getElementById('followup-input');
        const btnSubmit = document.getElementById('btn-submit-followup');
        const question = inputRaw.value.trim();
        if(!question) return;

        // Ensure we are viewing a report
        if (!currentReportsChain || currentReportsChain.length === 0 || currentReportIndex < 0) {
            alert('Bạn cần mở một báo cáo để có thể viết thêm phụ lục.');
            return;
        }

        const parentReportId = currentReportsChain[currentReportIndex].id;

        // UI Feedback
        inputRaw.value = '';
        inputRaw.disabled = true;
        btnSubmit.disabled = true;
        btnSubmit.innerHTML = '<i class="fas fa-spinner fa-spin" style="font-size: 12px;"></i>';

        // Add follow-up section stub
        const reportOut = document.getElementById('report-output');
        const stubId = `followup-${Date.now()}`;
        
        // Count existing followups to determine index if we just loaded this
        const existingFollowups = reportOut.querySelectorAll('.followup-section').length;
        if(existingFollowups > followUpAppendixIndex) followUpAppendixIndex = existingFollowups;

        const stubDiv = document.createElement('div');
        stubDiv.className = 'followup-section';
        stubDiv.id = stubId;
        stubDiv.innerHTML = `
            <div class="section" style="border: 2px dashed #cbd5e1; background: #f8fafc;">
                <h4 style="color: #64748b; margin-top: 0;"><i class="fas fa-sync fa-spin"></i> Đang phân tích: "${question}"...</h4>
                <div id="status-${stubId}" style="font-size: 13px; color: #3b82f6; font-family: monospace;">Khởi tạo lưới dữ liệu...</div>
                <div id="content-${stubId}"></div>
            </div>
        `;
        reportOut.appendChild(stubDiv);
        
        // Scroll to the bottom of the document
        document.getElementById('docScroll').scrollTop = document.getElementById('docScroll').scrollHeight;

        try {
            const reqData = {
                question: question,
                parent_report_id: parentReportId,
                appendix_index: followUpAppendixIndex,
                model: document.getElementById('modelSelect').value
            };

            const response = await authFetch('/api/report-followup', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(reqData)
            });

            if(!response.ok) throw new Error("HTTP " + response.status);

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

            let htmlContent = "";
            let htmlTarget = document.getElementById(`content-${stubId}`);
            let statusTarget = document.getElementById(`status-${stubId}`);

            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(); // keep last incomplete line

                for (let line of lines) {
                    if (line.trim() === '') continue;
                    if (line.startsWith('data: ')) {
                        try {
                            const data = JSON.parse(line.substring(6));
                            
                            if(data.type === "thinking") {
                                statusTarget.textContent = "> " + data.step;
                            } else if(data.type === "tool_call") {
                                statusTarget.textContent = `> Đang chạy tool: ${data.tool}...`;
                            } else if(data.type === "html_token") {
                                htmlContent += data.token;
                                htmlTarget.innerHTML = htmlContent;
                            } else if(data.type === "followup_complete") {
                                // Replace full HTML and render scripts/charts
                                stubDiv.innerHTML = data.html;
                                
                                // Execute scripts
                                const scripts = stubDiv.getElementsByTagName('script');
                                for (let i = 0; i < scripts.length; i++) {
                                    const newScript = document.createElement('script');
                                    if (scripts[i].src) newScript.src = scripts[i].src;
                                    else newScript.textContent = scripts[i].innerHTML;
                                    document.body.appendChild(newScript);
                                }
                                
                                if (window.Prism) Prism.highlightAllUnder(stubDiv);
                                
                                followUpAppendixIndex++;
                            }

                        } catch (e) { console.error("Event parse error", e, line); }
                    }
                }
            }

        } catch(e) {
            console.error(e);
            document.getElementById(`status-${stubId}`).innerHTML = `<span style="color:red">Lỗi: ${e.message}</span>`;
        } finally {
            inputRaw.disabled = false;
            btnSubmit.disabled = false;
            btnSubmit.innerHTML = '<i class="fas fa-paper-plane" style="font-size: 12px; margin-right: 2px; margin-top: 2px;"></i>';
            inputRaw.focus();
        }
    }

    // ─── VERSION NAVIGATION ───
    function updateVersionNav(currentVersion) {
        document.getElementById('versionNav').style.display = 'flex';
        document.getElementById('lblVersion').textContent = `v${currentVersion}`;
        
        // Filter current chain to find min and max versions. Wait, reports list might NOT be perfectly chronological if generated weirdly, but usually parent_id maps it.
        // Actually, currentReportsChain has EVERYTHING in the conversation. Some might be parallel branches!
        // To keep it simple, we just navigate linear index in currentReportsChain.
        
        if (currentReportIndex <= 0) {
            document.getElementById('btnPrevVersion').disabled = true;
        } else {
            document.getElementById('btnPrevVersion').disabled = false;
        }
        
        if (currentReportIndex >= currentReportsChain.length - 1) {
            document.getElementById('btnNextVersion').disabled = true;
        } else {
            document.getElementById('btnNextVersion').disabled = false;
        }
    }

    function navigateVersion(direction) {
        const targetIndex = currentReportIndex + direction;
        if (targetIndex >= 0 && targetIndex < currentReportsChain.length) {
            currentReportIndex = targetIndex;
            viewReportId(currentReportsChain[targetIndex].id);
        }
    }

    // ─── 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 exportToPDF() {
        window.print(); // Easy printing. The CSS should hide left-pane on print.
    }

    // ─── INLINE EDIT (Simplified) ───
    let selectedTextInfo = { text: '', context: '', range: null };
    
    function setupInlineEdit() {
        const reportOut = document.getElementById('report-output');
        const popup = document.getElementById('inlineEditPopup');

        reportOut.addEventListener('mouseup', function(e) {
            const selection = window.getSelection();
            const text = selection.toString().trim();
            
            if (text.length > 5) {
                // Approximate context
                const block = selection.anchorNode.parentNode;
                const context = block.innerText || block.textContent || "";
                
                selectedTextInfo = {
                    text: text,
                    context: context,
                    range: selection.getRangeAt(0).cloneRange()
                };
                
                popup.style.display = 'block';
                popup.style.top = (e.pageY + 10) + 'px';
                popup.style.left = e.pageX + 'px';
            } else {
                popup.style.display = 'none';
            }
        });

        document.addEventListener('mousedown', function(e) {
            if (!popup.contains(e.target) && !reportOut.contains(e.target)) {
                popup.style.display = 'none';
            }
        });
    }

    async function triggerInlineEdit(action) {
        const popup = document.getElementById('inlineEditPopup');
        popup.style.display = 'none';
        if (!selectedTextInfo.text || !selectedTextInfo.range) return;

        // Visual feedback
        const span = document.createElement('span');
        span.className = 'agent-editing';
        span.style.backgroundColor = '#fbbf24';
        span.style.color = 'black';
        span.textContent = `[⏳ AI đang sửa: ${action}...]`;
        selectedTextInfo.range.deleteContents();
        selectedTextInfo.range.insertNode(span);

        try {
            const res = await authFetch('/api/report-inline-edit', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    selected_text: selectedTextInfo.text,
                    action: action,
                    context: selectedTextInfo.context
                })
            });
            const data = await res.json();
            
            span.style.backgroundColor = '#d1fae5'; // success green
            span.innerHTML = `${data.new_text} <em style="font-size:10px;color:#059669;cursor:pointer;padding-left:4px" title="${escapeHTML(data.explanation || '')}">[Chấp nhận]</em>`;
            
            const acceptBtn = span.querySelector('em');
            acceptBtn.onclick = async function() {
                // Replace span with the plain HTML string to remove the green background
                span.outerHTML = data.new_text;
                
                // Now get the NEW HTML tree and save it as a new version
                const updatedHtml = document.getElementById('report-output').innerHTML;
                document.getElementById('docTitle').textContent = 'Đang lưu phiên bản mới...';

                try {
                    const saveRes = await authFetch('/api/report-save-edit', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            conversation_id: currentConversationId,
                            parent_report_id: currentReportsChain[currentReportIndex].id,
                            new_html_content: updatedHtml
                        })
                    });
                    const saveData = await saveRes.json();
                    if (saveData.success) {
                        // Refresh to fetch the new chain and switch to the new version display
                        loadConversation(currentConversationId);
                    } else {
                        alert("Lỗi lưu vào DB: " + (saveData.detail || saveData.error));
                    }
                } catch(err) {
                    alert("Lỗi kết nối khi lưu: " + err.message);
                }
            };
        } catch (e) {
            console.error(e);
            span.style.backgroundColor = '#fecaca';
            span.textContent = `[Lỗi: ${selectedTextInfo.text}]`;
        }
    }