/**
 * LolaCore Chat Interface — 1.0.0 (AJAX Request-Response)
 *
 * Replaces the legacy SSE (Server-Sent Events) architecture with a standard
 * fetch() POST → JSON response flow, using the WordPress 7.0 AI Client backend.
 */
document.addEventListener('DOMContentLoaded', function() {
    if (typeof window.lolacore_widget_state === 'undefined') return;

    const state    = window.lolacore_widget_state;
    const ajaxData = window.lolacore_ajax;

    // --- DOM Elements ---
    const trigger      = document.getElementById('lolacore-widget-trigger');
    const panel        = document.getElementById('lolacore-widget-panel');
    const btnMinimize  = document.getElementById('lolacore-btn-minimize');
    const btnClose     = document.getElementById('lolacore-btn-close');
    const dragHandle   = document.getElementById('lolacore-drag-handle');
    const messagesBox  = document.getElementById('lolacore-messages');
    const input        = document.getElementById('lolacore-input');
    const btnSend      = document.getElementById('lolacore-send');
    const statusBar    = document.getElementById('lolacore-status');
    const onboardingBar = document.getElementById('lolacore-onboarding-status');
    const onboardingText = document.getElementById('lolacore-onboarding-text');

    // --- UUID generator (works without HTTPS) ---
    function generateUUID() {
        if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
            return crypto.randomUUID();
        }
        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        );
    }

    // --- State ---
    let activeSessionId = sessionStorage.getItem('lolacore_active_session_id');
    // Guard: sessionStorage may contain the literal string "null" from an older JS version
    if (!activeSessionId || activeSessionId === 'null' || activeSessionId === 'undefined') {
        activeSessionId = null;
        sessionStorage.removeItem('lolacore_active_session_id');
    }

    let lastActivity    = Date.now();
    let isBusy          = false; // Replaces isStreaming — true while awaiting AJAX response
    let pendingSummary  = '';    // Summary from the last closed session, consumed on next open

    // Tracks tool action confirmations currently in flight. Prevents the same
    // action_id from being POSTed twice (re-click after page reload re-renders
    // the card, accessibility tools firing double events, etc.) which would
    // make the second call hit a consumed pending row and surface a misleading
    // "That confirmation has expired" error after the action already succeeded.
    const inFlightActionIds = new Set();

    // --- Thinking Messages (progressive rotation) ---
    const _thinkingProfessional = [
        'Thinking…',
        'Looking into it…',
        'Let me check a few things…',
        'Going through the details…',
        'Processing your request…',
        'Connecting a few pieces…',
        'Give me a second on this one…',
        'On it…',
    ];

    const _thinkingPersonality = [
        'Hmm, trickier than I thought…',
        'Hold on — I want to get this right.',
        'OK, I see what\'s going on here…',
        'Don\'t rush me.',
        'This deserves a proper answer…',
        'I have opinions about this. One sec.',
        'Let me approach this differently…',
        'OK let me focus a bit harder…',
    ];

    let _thinkingInterval   = null;
    let _thinkingStart      = 0;
    let _thinkingUsed       = [];

    console.log('[Lola] Init 1.0.0 (AJAX):', { activeSessionId });

    // =========================================================================
    // A. Widget Toggle & Drag
    // =========================================================================

    function togglePanel() {
        if (isDragging || dragOccurred) return;

        if (panel.classList.contains('lolacore-hidden')) {
            panel.classList.remove('lolacore-hidden');
            input.focus();
            scrollToBottom();

            if (!activeSessionId) {
                startNewSession(pendingSummary);
                pendingSummary = '';
            }
        } else {
            panel.classList.add('lolacore-hidden');
        }
    }

    trigger.addEventListener('click', togglePanel);
    btnMinimize.addEventListener('click', () => panel.classList.add('lolacore-hidden'));
    btnClose.addEventListener('click', closeAndResetSession);

    // Drag Logic
    let isDragging  = false;
    let dragOccurred = false;
    let currentX, currentY, initialX, initialY;
    let xOffset = 0, yOffset = 0;

    // Restore saved position
    const savedPos = localStorage.getItem('lolacore_widget_position_v2');
    if (savedPos) {
        try {
            const pos = JSON.parse(savedPos);
            xOffset = pos.x || 0;
            yOffset = pos.y || 0;
            // Stop the pulse animation before applying transform — they share
            // the transform property and CSS animations override inline styles.
            trigger.style.animation = 'none';
            setTranslate(xOffset, yOffset, panel);
            setTranslate(xOffset, yOffset, trigger);
        } catch (e) { /* ignore */ }
    }

    trigger.addEventListener('mousedown', dragStart);
    dragHandle.addEventListener('mousedown', dragStart);
    trigger.addEventListener('dragstart', e => e.preventDefault());
    dragHandle.addEventListener('dragstart', e => e.preventDefault());
    trigger.addEventListener('touchstart', dragStart, { passive: false });
    dragHandle.addEventListener('touchstart', dragStart, { passive: false });
    document.addEventListener('mouseup', dragEnd);
    document.addEventListener('touchend', dragEnd);
    document.addEventListener('mousemove', drag, { passive: false });
    document.addEventListener('touchmove', drag, { passive: false });

    function dragStart(e) {
        if (e.type === 'touchstart') {
            initialX = e.touches[0].clientX - xOffset;
            initialY = e.touches[0].clientY - yOffset;
        } else {
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;
        }
        if (e.target === dragHandle || dragHandle.contains(e.target) ||
            e.target === trigger    || trigger.contains(e.target)) {
            isDragging   = true;
            dragOccurred = false;
        }
    }

    function dragEnd() {
        if (!isDragging) return;
        initialX  = currentX;
        initialY  = currentY;
        isDragging = false;
        if (dragOccurred) {
            localStorage.setItem('lolacore_widget_position_v2', JSON.stringify({ x: xOffset, y: yOffset }));
            setTimeout(() => { dragOccurred = false; }, 100);
        }
    }

    function drag(e) {
        if (!isDragging) return;
        if (e.type !== 'touchmove') e.preventDefault();

        const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
        const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;

        if (!dragOccurred &&
            Math.abs(clientX - xOffset - initialX) < 3 &&
            Math.abs(clientY - yOffset - initialY) < 3) {
            return;
        }
        dragOccurred = true;
        currentX     = clientX - initialX;
        currentY     = clientY - initialY;
        xOffset      = currentX;
        yOffset      = currentY;
        setTranslate(currentX, currentY, panel);
        setTranslate(currentX, currentY, trigger);
    }

    function setTranslate(xPos, yPos, el) {
        el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
    }

    // =========================================================================
    // B. UI Helpers
    // =========================================================================

    function scrollToBottom() {
        messagesBox.scrollTop = messagesBox.scrollHeight;
    }

    /**
     * Append a chat message bubble to the messages list.
     * Supports basic Markdown: **bold**, [link](url), newlines.
     *
     * @param {string} role    'user' | 'assistant'
     * @param {string} content Raw text / Markdown content.
     * @returns {HTMLElement}  The created message div.
     */
    function appendMessage(role, content, isError = false) {
        const msgDiv     = document.createElement('div');
        msgDiv.className = `lolacore-message lolacore-msg-${role}`;

        if (isError) {
            msgDiv.classList.add('lola-error-message');
        }

        const avatarUrl = role === 'assistant' ? state.avatar_url : state.user_avatar_url;

        let processContent = content;
        if (role === 'user') {
            // Escape HTML entities for user input so tags like <script> are visible and not parsed as HTML
            processContent = processContent.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
        }

        const htmlContent = renderMarkdown(processContent);

        msgDiv.innerHTML = `
            <img src="${avatarUrl}" class="lolacore-msg-avatar" alt="${role}">
            <div class="lolacore-msg-content">
                <div class="lolacore-msg-text">${htmlContent}</div>
            </div>
        `;
        messagesBox.appendChild(msgDiv);
        scrollToBottom();
        return msgDiv;
    }

    /**
     * Markdown → HTML renderer.
     * Uses marked.js + DOMPurify if available, otherwise fallback.
     *
     * @param {string} text
     * @returns {string}
     */
    function renderMarkdown(text) {
        // Auto-link plain URLs that are NOT already inside a markdown link or HTML tag.
        // Matches http:// and https:// URLs, stops at whitespace or common punctuation.
        // Negative lookbehind: skip if preceded by ]( (markdown link) or " or ' (HTML attr).
        const autolinked = text.replace(
            /(?<!\]\()(?<!")(?<!')(?<!=)(https?:\/\/[^\s<>"'`)\]]+)/gi,
            '[$1]($1)'
        );

        if (typeof marked !== 'undefined') {
            let html = marked.parse(autolinked, { breaks: true });
            if (typeof DOMPurify !== 'undefined') {
                html = DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'download'] });
            }
            // Ensure all links open in new tab and export files have download attribute
            html = html.replace(/<a\s+([^>]*?)href=["']([^"']+)["']([^>]*)>/gi, (match, before, url, after) => {
                let attrs = 'target="_blank" rel="noopener noreferrer"';
                if (url.match(/\.(xml|json|csv|zip|txt)$/i)) {
                    const filename = url.split('/').pop().split('?')[0] || '';
                    attrs += ` download="${filename}"`;
                }
                return `<a ${before}href="${url}"${after} ${attrs}>`;
            });
            return html;
        }

        // Fallback
        return autolinked
            .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
            .replace(/\[(.*?)\]\((.*?)\)/g, (match, linkText, url) => {
                let attrs = 'target="_blank" rel="noopener noreferrer"';
                if (url.match(/\.(xml|json|csv|zip|txt)$/i)) {
                    const filename = url.split('/').pop().split('?')[0] || '';
                    attrs += ` download="${filename}"`;
                }
                return `<a href="${url}" ${attrs}>${linkText}</a>`;
            })
            .replace(/\n/g, '<br>');
    }

    /**
     * Append a small memory-saved indicator below a message bubble.
     *
     * @param {HTMLElement} msgDiv The parent assistant message div.
     * @param {string}      text   The fact text.
     */
    function appendMemoryIndicator(msgDiv, text) {
        const contentDiv  = msgDiv.querySelector('.lolacore-msg-content');
        const indicator   = document.createElement('div');
        indicator.className   = 'lolacore-memory-indicator';
        indicator.textContent = `💾 ${text}`;
        contentDiv.appendChild(indicator);
        scrollToBottom();
    }

    /**
     * Pick a thinking message based on elapsed time.
     * 0-6s: professional only. 6-12s: mix in personality. 12s+: static fallback.
     *
     * @returns {string}
     */
    function pickThinkingMessage() {
        const elapsed = Date.now() - _thinkingStart;

        // Before 6s: professional only.
        // 6-12s: professional + personality.
        // 12s+: everything + long-wait messages (never stops rotating).
        let pool;
        if (elapsed < 6000) {
            pool = _thinkingProfessional;
        } else if (elapsed < 12000) {
            pool = [..._thinkingProfessional, ..._thinkingPersonality];
        } else {
            pool = [
                ..._thinkingProfessional,
                ..._thinkingPersonality,
                'Still working — this one needs extra attention.',
                'Taking a bit longer than usual…',
                'Bear with me, almost cracked it…',
            ];
        }

        // Avoid repeating the last 3 messages shown
        const available = pool.filter(m => !_thinkingUsed.includes(m));
        const source    = available.length > 0 ? available : pool;
        const msg       = source[Math.floor(Math.random() * source.length)];

        _thinkingUsed.push(msg);
        if (_thinkingUsed.length > 3) _thinkingUsed.shift();

        return msg;
    }

    /**
     * Show the thinking status bar with rotating messages.
     */
    function showTypingIndicator() {
        isBusy         = true;
        _thinkingStart = Date.now();
        _thinkingUsed  = [];

        const indicator = statusBar.querySelector('.lolacore-typing-indicator');
        indicator.textContent = pickThinkingMessage();
        statusBar.classList.remove('lolacore-hidden');

        _thinkingInterval = setInterval(() => {
            indicator.textContent = pickThinkingMessage();
        }, 3000);
    }

    /**
     * Hide the status bar, clear rotation, and re-enable input.
     */
    function hideTypingIndicator() {
        isBusy = false;
        statusBar.classList.add('lolacore-hidden');
        if (_thinkingInterval) {
            clearInterval(_thinkingInterval);
            _thinkingInterval = null;
        }
    }

    /**
     * Render a tool-confirmation card as an inline assistant message.
     * Shows what action Lola wants to perform; the user approves or cancels.
     * Only the action_id travels to the backend to prevent parameter injection.
     *
     * @param {Object} data  Backend payload: { action_id, tool_name, params, preview, message }
     */
    function renderToolConfirmation(data) {
        const msgDiv = appendMessage('assistant', data.message || 'I need your approval to proceed:');
        const contentDiv = msgDiv.querySelector('.lolacore-msg-content');

        // Format params as a readable list
        const paramsHtml = data.params
            ? Object.entries(data.params)
                .map(([k, v]) => `<li><strong>${k}:</strong> ${v}</li>`)
                .join('')
            : '';

        const previewHtml = data.preview
            ? `<div class="lolacore-tc-preview">${renderMarkdown(data.preview)}</div>`
            : '';

        const card = document.createElement('div');
        card.className = 'lolacore-tool-confirmation';
        card.innerHTML = `
            <div class="lolacore-tc-header">
                <span class="lolacore-tc-icon">⚠️</span>
                <strong>Approval required</strong>
            </div>
            <div class="lolacore-tc-body">
                <p class="lolacore-tc-action"><strong>${data.tool_name || 'Action'}</strong></p>
                ${paramsHtml ? `<ul class="lolacore-tc-params">${paramsHtml}</ul>` : ''}
                ${previewHtml}
            </div>
            <div class="lolacore-tc-actions">
                <button class="lolacore-tc-btn lolacore-tc-approve"
                        data-action-id="${data.action_id}"
                        data-decision="approve">Approve and execute</button>
                <button class="lolacore-tc-btn lolacore-tc-cancel"
                        data-action-id="${data.action_id}"
                        data-decision="cancel">Cancel</button>
            </div>
        `;
        contentDiv.appendChild(card);
        scrollToBottom();

        // Bind button events — delegates to AJAX handler
        card.querySelector('.lolacore-tc-approve').addEventListener('click', function() {
            handleToolDecision(this.dataset.actionId, 'approve', card);
        });
        card.querySelector('.lolacore-tc-cancel').addEventListener('click', function() {
            handleToolDecision(this.dataset.actionId, 'cancel', card);
        });
    }

    // =========================================================================
    // C. Session Management
    // =========================================================================

    /**
     * Start a fresh chat session: generate UUID, register on backend, show greeting.
     * On first-ever open (no prior sessions + scan data available), runs the
     * onboarding sequence with a visual scan animation and a proactive first message.
     *
     * @param {string} [closeSummary='']  Fresh summary from the just-closed session (if any).
     */
    function startNewSession(closeSummary) {
        console.log('[Lola] startNewSession()');
        activeSessionId = generateUUID();
        sessionStorage.setItem('lolacore_active_session_id', activeSessionId);
        console.log('[Lola] New session UUID:', activeSessionId);

        const registerSession = fetch(ajaxData.ajax_url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action:         'lolacore_start_session',
                session_id:     activeSessionId,
                _lolacore_nonce: ajaxData.chat_nonce
            })
        }).catch(err => console.error('[Lola] start_session error:', err));

        // First-time onboarding: show scan animation + proactive first message
        if (!state.onboarding_complete) {
            runOnboardingSequence(registerSession);
            return;
        }

        // Returning user: contextual greeting using the freshest summary available
        const summary = closeSummary || state.last_session_summary;
        if (summary) {
            appendMessage('assistant',
                `*Last session summary:* ${summary}\n\nShall we continue or start something new?`
            );
        } else {
            appendMessage('assistant', "Hello. I'm ready. What shall we look into today?");
        }
    }

    async function runOnboardingSequence(registerPromise) {
        console.log('[Lola] Running onboarding sequence');

        // Disable input during onboarding
        input.disabled = true;
        btnSend.disabled = true;

        // Show scan animation
        onboardingBar.classList.remove('lolacore-hidden');
        onboardingText.textContent = 'Analyzing your site…';

        // Wait for session to register
        await registerPromise;

        // Simulate scan phases for visual effect
        await delay(800);
        onboardingText.textContent = 'Reviewing plugins and security…';
        await delay(700);
        onboardingText.textContent = 'Building your site profile…';
        await delay(500);

        // Hide scan bar
        onboardingBar.classList.add('lolacore-hidden');

        // Render the scan report card
        renderScanReportCard(state.scan_data);

        await delay(1000);

        if (state.is_configured) {
            input.disabled = false;
            btnSend.disabled = false;
            // Send onboarding init to get Lola's proactive first message
            sendMessage('__LOLA_ONBOARDING_INIT__');
        } else {
            // Missing API: show the CTA
            appendMessage('assistant', "I've scanned your site and have the report ready. To start working together, connect an AI provider in **Settings → Connectors**. You'll need an API key from OpenAI, Anthropic, or any provider supported by WordPress.");
            
            const ctaDiv = document.createElement('div');
            ctaDiv.className = 'lolacore-message lolacore-msg-assistant';
            ctaDiv.innerHTML = `
                <div style="width:28px;height:28px;flex-shrink:0;"></div>
                <div class="lolacore-msg-content">
                    <a href="options-connectors.php" style="display:inline-block; margin-top:5px; padding: 8px 16px; background:#2271b1; color:#fff; text-decoration:none; border-radius:6px; font-weight:600; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
                        Configure AI Connectors
                    </a>
                </div>
            `;
            messagesBox.appendChild(ctaDiv);
            scrollToBottom();
        }

        // Mark onboarding as completed so it never replays
        completeOnboarding();
    }

    /**
     * Fire-and-forget AJAX call to persist the onboarding_complete flag.
     * Also updates the in-memory state so same-page-load reopens
     * never re-trigger the onboarding sequence.
     */
    function completeOnboarding() {
        // Critical: update in-memory flag immediately
        state.onboarding_complete = true;

        fetch(ajaxData.ajax_url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action:          'lolacore_complete_onboarding',
                _lolacore_nonce: ajaxData.chat_nonce
            })
        }).catch(err => console.error('[Lola] completeOnboarding error:', err));
    }

    function renderScanReportCard(scanData) {
        if (!scanData) {
            appendMessage('assistant', "I've completed the initial scan of your site.");
            return;
        }
        
        // Count active plugins from the flat array structure
        let activePlugins = 0;
        let totalPlugins = 0;
        if (Array.isArray(scanData.plugins)) {
            totalPlugins = scanData.plugins.length;
            activePlugins = scanData.plugins.filter(p => p.is_active).length;
        }

        // Security-based health assessment
        let healthStatus = '✅ Healthy';
        if (scanData.security) {
            const hasIssues = scanData.security.debug_mode || !scanData.security.ssl_active;
            if (hasIssues) healthStatus = '⚠️ Needs Attention';
        }
        
        const wpVersion = scanData.wordpress?.wp_version || 'Unknown';
        const phpVersion = scanData.php?.php_version || 'Unknown';
        const dbSize = scanData.database?.total_size_mb || '?';
        const posts = scanData.content?.posts_count ?? 0;
        const pages = scanData.content?.pages_count ?? 0;
        const themeName = scanData.theme?.active_theme_name || 'Unknown';
        
        const cardHtml = `
            <div style="border: 1px solid #dcdcde; border-radius: 8px; overflow: hidden; margin-top: 8px; background: #fff; width: 100%;">
                <div style="background: #f0f6fc; color: #1d2327; padding: 10px 14px; font-weight: 600; border-bottom: 1px solid #dcdcde; display: flex; align-items: center; gap: 8px;">
                    <span>📊</span> <span>Initial System Report</span>
                </div>
                <div style="padding: 14px; font-size: 13px; color: #1d2327;">
                    <ul style="margin: 0; padding-left: 20px; line-height: 1.8;">
                        <li><strong>Status:</strong> ${healthStatus}</li>
                        <li><strong>WordPress:</strong> ${wpVersion}</li>
                        <li><strong>PHP:</strong> ${phpVersion}</li>
                        <li><strong>Active Theme:</strong> ${themeName}</li>
                        <li><strong>Plugins:</strong> ${activePlugins} active / ${totalPlugins} installed</li>
                        <li><strong>Content:</strong> ${posts} posts, ${pages} pages</li>
                        <li><strong>Database:</strong> ${dbSize} MB</li>
                    </ul>
                </div>
            </div>
        `;
        
        const msgDiv = appendMessage('assistant', "I've completed the initial scan of your site. Here's your summary:");
        msgDiv.querySelector('.lolacore-msg-content').insertAdjacentHTML('beforeend', cardHtml);
        scrollToBottom();
    }

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    /**
     * Close the current session and hide the chat panel.
     *
     * The panel closes instantly for responsive UX. The backend close
     * request (which generates a conversation summary via LLM) runs
     * in the background. If the user reopens the widget before the
     * summary arrives, a generic greeting is shown. Once the summary
     * resolves, it's stored in `pendingSummary` for the next open.
     */
    function closeAndResetSession() {
        const closingSessionId = activeSessionId;

        // Reset state and close panel immediately — no waiting
        activeSessionId = null;
        sessionStorage.removeItem('lolacore_active_session_id');
        messagesBox.innerHTML = '';
        panel.classList.add('lolacore-hidden');

        // Fire summary generation in the background
        if (closingSessionId) {
            fetch(ajaxData.ajax_url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action:          'lolacore_close_session',
                    session_id:      closingSessionId,
                    _lolacore_nonce: ajaxData.chat_nonce
                })
            })
            .then(r => r.json())
            .then(result => {
                if (result.success && result.data?.summary) {
                    pendingSummary = result.data.summary;
                }
            })
            .catch(err => console.error('[Lola] close_session error:', err));
        }
    }

    /**
     * Load and render the message history for an existing session (page reload).
     *
     * @param {string} sessionId
     */
    function loadExistingSession(sessionId) {
        fetch(ajaxData.ajax_url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action:          'lolacore_get_history',
                session_id:      sessionId,
                _lolacore_nonce: ajaxData.chat_nonce
            })
        })
        .then(r => r.json())
        .then(result => {
            if (result.success && result.data?.messages) {
                result.data.messages.forEach(msg => {
                    if ((msg.role === 'user' || msg.role === 'assistant') &&
                        msg.content && msg.content !== '__LOLA_ONBOARDING_INIT__') {
                        appendMessage(msg.role, msg.content);
                    }
                });
            }
        })
        .catch(err => console.error('[Lola] get_history error:', err));
    }

    // Inactivity auto-reset (10 min)
    function checkInactivity() {
        const tenMins = 10 * 60 * 1000;
        if (Date.now() - lastActivity > tenMins && activeSessionId) {
            closeAndResetSession();
        }
    }
    setInterval(checkInactivity, 60000);

    // Restore history on page load if a session already exists
    if (activeSessionId) {
        loadExistingSession(activeSessionId);
    }

    // Auto-open logic on load for first-time users.
    // The backend flag is the single source of truth. If onboarding is pending,
    // clear any stale session from a previous activation cycle so that
    // togglePanel → startNewSession → runOnboardingSequence fires cleanly.
    if (!state.onboarding_complete) {
        activeSessionId = null;
        sessionStorage.removeItem('lolacore_active_session_id');
        messagesBox.innerHTML = '';

        setTimeout(() => {
            togglePanel();
        }, 800);
    }

    // =========================================================================
    // D. AJAX Chat (replaces SSE)
    // =========================================================================

    /**
     * Send a user message to the backend via AJAX (POST → JSON).
     * Handles text responses and tool-confirmation requests.
     *
     * @param {string} text The message text.
     */
    async function sendMessage(text) {
        if (!text.trim() || isBusy) return;

        // Show user bubble (unless it's an internal init message)
        if (!text.startsWith('__LOLA_')) {
            appendMessage('user', text);
        }
        input.value       = '';
        input.style.height = 'auto';
        lastActivity      = Date.now();

        showTypingIndicator();

        try {
            const response = await fetch(ajaxData.ajax_url, {
                method:  'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action:          'lolacore_send_message',
                    session_id:      activeSessionId,
                    message:         text,
                    _lolacore_nonce: ajaxData.chat_nonce
                })
            });

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }

            const result = await response.json();
            handleBackendResponse(result);

        } catch (error) {
            console.error('[Lola] sendMessage error:', error);
            appendMessage('assistant', "I couldn't get a response. This is usually a temporary connection issue — try sending your message again. If it keeps happening, check that your AI provider is still connected in Settings → Connectors.", true);
        } finally {
            hideTypingIndicator();
        }
    }

    /**
     * Send the user's decision (approve / cancel) for a pending tool action.
     * Disables the card buttons immediately to prevent double-submission.
     *
     * @param {string}      actionId   The pending action UUID from the backend.
     * @param {string}      decision   'approve' | 'cancel'
     * @param {HTMLElement} cardEl     The confirmation card element (to update its state).
     */
    async function handleToolDecision(actionId, decision, cardEl) {
        // Idempotency guard — see inFlightActionIds declaration for rationale.
        if (inFlightActionIds.has(actionId)) {
            return;
        }
        inFlightActionIds.add(actionId);

        // Disable buttons immediately
        cardEl.querySelectorAll('.lolacore-tc-btn').forEach(b => {
            b.disabled      = true;
            b.style.opacity = '0.5';
        });

        const statusEl       = document.createElement('p');
        statusEl.className   = 'lolacore-tc-status';
        statusEl.textContent = decision === 'approve' ? 'Executing…' : 'Cancelling…';
        cardEl.querySelector('.lolacore-tc-actions').appendChild(statusEl);

        showTypingIndicator();

        try {
            const response = await fetch(ajaxData.ajax_url, {
                method:  'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action:          'lolacore_confirm_tool_action',
                    action_id:       actionId,
                    decision:        decision,
                    session_id:      activeSessionId,
                    _lolacore_nonce: ajaxData.chat_nonce
                })
            });

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }

            const result = await response.json();

            // Update card status badge
            statusEl.textContent = decision === 'approve' ? '✅ Executed' : '❌ Cancelled';
            statusEl.style.color = decision === 'approve' ? '#00a32a' : '#d63638';

            handleBackendResponse(result);

        } catch (err) {
            statusEl.textContent = 'Error';
            statusEl.style.color = '#d63638';
            appendMessage('assistant', "I couldn't get a response. This is usually a temporary connection issue — try sending your message again. If it keeps happening, check that your AI provider is still connected in Settings → Connectors.", true);
            console.error('[Lola] handleToolDecision error:', err);
        } finally {
            inFlightActionIds.delete(actionId);
            hideTypingIndicator();
        }
    }

    /**
     * Centralised handler for all backend JSON responses.
     * Routes to the correct UI update based on `data.type`.
     *
     * Expected response shape (success):
     *   { success: true, data: { type: 'message'|'tool_confirmation'|'error', content, ... } }
     *
     * @param {Object} result  Parsed wp_send_json_success / wp_send_json_error payload.
     */
    function handleBackendResponse(result) {
        if (!result.success) {
            const msg = result.data?.message || "Something went wrong and I couldn't process that. Try refreshing the page.";
            appendMessage('assistant', msg, true);
            return;
        }

        const data = result.data;

        switch (data.type) {
            case 'tool_confirmation':
                // Render a confirmation card — user must approve before action executes
                renderToolConfirmation(data);
                break;

            case 'message':
            default: {
                // Regular assistant text response
                const msgDiv = appendMessage('assistant', data.content || '');

                // Optionally show memory-saved indicator
                if (data.memory && data.memory.length > 0) {
                    data.memory.forEach(fact => appendMemoryIndicator(msgDiv, fact));
                }
                break;
            }
        }
    }

    // =========================================================================
    // E. Input Bindings
    // =========================================================================

    input.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            sendMessage(this.value);
        }
    });

    btnSend.addEventListener('click', function() {
        sendMessage(input.value);
    });

    // Auto-resize textarea up to 120 px
    input.addEventListener('input', function() {
        this.style.height = 'auto';
        this.style.height = (this.scrollHeight < 120 ? this.scrollHeight : 120) + 'px';
    });
});
