/**
 * AUDIOBoxx AI assistant — correct message order, typing, streamed answers.
 */
(function () {
    'use strict';

    const STREAM_CHARS = 4;
    const STREAM_MS = 12;
    const THINK_MS_MIN = 500;
    const THINK_MS_MAX = 1400;

    let busy = false;
    let greetToken = 0;
    let queue = Promise.resolve();

    function el(id) { return document.getElementById(id); }

    function escapeHtml(s) {
        return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    /** Simple markdown bold **text** → <strong> */
    function formatAiText(s) {
        const safe = escapeHtml(s || '');
        return safe.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
    }

    function tabLabel(tab) {
        const map = {
            overview: 'Overview',
            fingerprint: 'Fingerprint',
            identify: 'Identify',
            library: 'Library',
            streams: 'Streams',
            monitor: 'Monitor',
            recording: 'Recording',
            results: 'View Results',
            report: 'Report',
            about: 'About',
            settings: 'Settings'
        };
        return map[tab] || tab || '';
    }

    function setThinking(on) {
        const root = el('help-bot-root');
        const fab = el('help-bot-toggle');
        const status = el('help-bot-status');
        if (root) root.classList.toggle('is-thinking', on);
        if (fab) fab.classList.toggle('is-thinking', on);
        if (on) stopIdleMoodCycle();
        syncRobotMood();
        if (status) {
            status.classList.toggle('is-busy', on);
            if (!on && statusOnline) status.textContent = 'AI online · ready to help';
        }
        busy = on;
        const input = el('help-bot-input');
        const send = el('help-bot-form')?.querySelector('button[type="submit"]');
        if (input) input.disabled = on;
        if (send) send.disabled = on;
    }

    let statusOnline = true;

    function scrollLog() {
        const log = el('help-bot-messages');
        if (log) log.scrollTop = log.scrollHeight;
    }

    function appendUserMsg(text) {
        const log = el('help-bot-messages');
        if (!log) return;
        const row = document.createElement('div');
        row.className = 'help-bot-msg help-bot-msg--user';
        row.innerHTML =
            '<div class="help-bot-msg-label">You</div>'
            + '<div class="help-bot-bubble"><p>' + escapeHtml(text) + '</p></div>';
        log.appendChild(row);
        scrollLog();
        return row;
    }

    const POS_STORAGE_KEY = 'abox-help-bot-position';
    const ENABLED_STORAGE_KEY = 'abox-help-bot-enabled';
    const DRAG_THRESHOLD_PX = 8;
    const IDLE_MOOD_POOL = [
        { mood: 'idle', weight: 3 },
        { mood: 'relaxed', weight: 2, stunt: 'nod' },
        { mood: 'think', weight: 2, stunt: 'think' },
        { mood: 'curious', weight: 2, stunt: 'look-around' },
        { mood: 'cheerful', weight: 2, stunt: 'bounce' },
        { mood: 'sleepy', weight: 1, stunt: 'shiver' },
        { mood: 'idle', weight: 2, stunt: 'wave' },
        { mood: 'idle', weight: 2, stunt: 'spin-360' },
        { mood: 'curious', weight: 1, stunt: 'peek' },
        { mood: 'cheerful', weight: 1, stunt: 'wiggle' },
        { mood: 'relaxed', weight: 1, stunt: 'tablet-tap' },
        { mood: 'think', weight: 1, stunt: 'alert-scan' }
    ];
    const STUNT_DURATIONS = {
        'spin-360': 5200,
        'look-around': 4000,
        'wave': 3600,
        'alert-scan': 3500,
        'bounce': 3200,
        'wiggle': 3000,
        'peek': 2800,
        'nod': 2600,
        'shiver': 2600,
        'tablet-tap': 2400,
        'think': 2400
    };
    let botEnabled = true;
    let idleMoodTimer = null;
    let lastIdleMood = 'idle';
    let activeTask = null;
    let lastTaskProgressAt = 0;
    let lastTaskPhase = null;
    const STRESS_PCT = 95;
    let systemStressed = false;
    let lastMatchReactAt = 0;
    let lastNomatchReactAt = 0;
    let celebrateTimer = null;

    const TASK_MOODS = {
        fingerprint: 'fingerprint',
        identify: 'identify'
    };

    /** 3D host — floating robot canvas only (bubble lives in separate slot above) */
    function botHostHtml(sizeClass, mood) {
        const m = mood || 'idle';
        const cw = sizeClass.indexOf('sm') >= 0 ? 52 : (sizeClass.indexOf('head') >= 0 ? 72 : 108);
        const ch = sizeClass.indexOf('sm') >= 0 ? 58 : (sizeClass.indexOf('head') >= 0 ? 80 : 116);
        return '<div class="abox-3d-host ' + sizeClass + '" data-mood="' + escapeHtml(m) + '" aria-hidden="true">'
            + '<canvas class="abox-3d-canvas" width="' + cw + '" height="' + ch + '"></canvas>'
            + '</div>';
    }

    function fabShellHtml(sizeClass, mood) {
        if (sizeClass.indexOf('fab') < 0) return botHostHtml(sizeClass, mood);
        return '<span class="help-bot-bubble-slot" aria-live="polite"></span>' + botHostHtml(sizeClass, mood);
    }

    /** Flat fallback if WebGL / Three.js unavailable */
    function botFallbackHtml(sizeClass) {
        return '<div class="abox-3d-host abox-3d-fallback ' + sizeClass + '" data-mood="idle" aria-hidden="true">'
            + '<div class="abox-3d-fallback__bot" role="img" aria-label="AUDIOBoxx assistant">'
            + '<span class="abox-3d-fallback__head"></span>'
            + '<span class="abox-3d-fallback__face">'
            + '<span class="abox-3d-fallback__eye abox-3d-fallback__eye--l"></span>'
            + '<span class="abox-3d-fallback__eye abox-3d-fallback__eye--r"></span>'
            + '<span class="abox-3d-fallback__mouth"></span>'
            + '</span>'
            + '<span class="abox-3d-fallback__tablet" aria-hidden="true"></span>'
            + '</div></div>';
    }

    async function mountAvatarWithFallback(wrapper) {
        if (!wrapper) return false;
        const host = findHost(wrapper);
        if (!host) return false;
        if (window.AboxBuddy3d?.dispose) window.AboxBuddy3d.dispose(host);
        const b3 = window.AboxBuddy3d;
        if (!b3) {
            if (!host.classList.contains('abox-3d-fallback')) {
                const size = host.className.match(/abox-3d-host--\w+/)?.[0] || 'abox-3d-host--fab';
                host.outerHTML = botFallbackHtml(size);
            }
            return false;
        }
        const inst = await b3.mountWhenReady(wrapper);
        if (!inst && !host.classList.contains('abox-3d-fallback')) {
            const size = host.classList.contains('abox-3d-host--sm') ? 'abox-3d-host--sm'
                : (host.classList.contains('abox-3d-host--head') ? 'abox-3d-host--head' : 'abox-3d-host--fab');
            host.outerHTML = botFallbackHtml(size);
            return false;
        }
        return !!inst;
    }

    function findHost(container) {
        if (!container) return null;
        return container.querySelector?.('.abox-3d-host')
            || (container.classList?.contains('abox-3d-host') ? container : null);
    }

    function mountAvatar(wrapper) {
        mountAvatarWithFallback(wrapper);
    }

    function setUnitMood(container, mood) {
        const m = mood || 'idle';
        const host = findHost(container);
        if (host) host.dataset.mood = m;
        if (window.AboxBuddy3d) window.AboxBuddy3d.setMood(host || container, m);
    }

    function playBuddyStunt(container, kind) {
        if (window.AboxBuddy3d) window.AboxBuddy3d.playStunt(container, kind);
    }

    function clamp(n, min, max) {
        return Math.max(min, Math.min(max, n));
    }

    function syncPanelPlacement(root) {
        if (!root) return;
        const r = root.getBoundingClientRect();
        const openAbove = r.top < window.innerHeight * 0.42;
        root.classList.toggle('help-bot-panel-above', openAbove);
    }

    function applyBuddyPosition(root, x, y, persist) {
        if (!root) return;
        const w = root.offsetWidth || 96;
        const h = root.offsetHeight || 100;
        const nx = clamp(x, 4, window.innerWidth - w - 4);
        const ny = clamp(y, 4, window.innerHeight - h - 4);
        root.style.left = nx + 'px';
        root.style.top = ny + 'px';
        root.style.right = 'auto';
        root.style.bottom = 'auto';
        root.dataset.positioned = '1';
        syncPanelPlacement(root);
        if (persist) {
            try {
                localStorage.setItem(POS_STORAGE_KEY, JSON.stringify({ x: nx, y: ny }));
            } catch { /* ignore */ }
        }
    }

    function restoreBuddyPosition(root) {
        if (!root) return;
        try {
            const raw = localStorage.getItem(POS_STORAGE_KEY);
            if (!raw) return;
            const p = JSON.parse(raw);
            if (typeof p.x === 'number' && typeof p.y === 'number') {
                applyBuddyPosition(root, p.x, p.y, false);
            }
        } catch { /* ignore */ }
    }

    function initBuddyDrag(root, fab) {
        if (!root || !fab) return;
        let dragging = false;
        let moved = false;
        let startX = 0;
        let startY = 0;
        let originX = 0;
        let originY = 0;

        fab.addEventListener('pointerdown', (e) => {
            if (e.button !== 0) return;
            dragging = true;
            moved = false;
            startX = e.clientX;
            startY = e.clientY;
            const r = root.getBoundingClientRect();
            originX = r.left;
            originY = r.top;
            fab.setPointerCapture(e.pointerId);
            root.classList.add('is-dragging');
        });

        fab.addEventListener('pointermove', (e) => {
            if (!dragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            if (!moved && Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
            moved = true;
            applyBuddyPosition(root, originX + dx, originY + dy, false);
        });

        fab.addEventListener('pointerup', (e) => {
            if (!dragging) return;
            dragging = false;
            root.classList.remove('is-dragging');
            try { fab.releasePointerCapture(e.pointerId); } catch { /* ignore */ }
            if (moved) {
                const r = root.getBoundingClientRect();
                applyBuddyPosition(root, r.left, r.top, true);
                fab.dataset.dragSuppress = '1';
                window.setTimeout(() => { delete fab.dataset.dragSuppress; }, 320);
            }
        });

        fab.addEventListener('pointercancel', () => {
            dragging = false;
            root.classList.remove('is-dragging');
        });

        window.addEventListener('resize', () => {
            if (root.dataset.positioned !== '1') return;
            const r = root.getBoundingClientRect();
            applyBuddyPosition(root, r.left, r.top, true);
        });
    }

    function markOnTask() {
        const root = el('help-bot-root');
        if (root) root.classList.add('is-on-task');
    }

    function stuntForTaskStart(kind) {
        return kind === 'fingerprint' ? 'fingerprint-start' : 'identify-start';
    }

    function stuntForPhase(kind, phase) {
        if (kind === 'fingerprint') {
            if (phase === 'decoding') return 'fingerprint-decode';
            if (phase === 'fingerprinting') return 'fingerprint-hash';
        }
        if (kind === 'identify' && phase) return 'identify-search';
        return null;
    }

    function stuntForTaskEnd(outcome) {
        if (outcome === 'done') {
            const r = Math.random();
            if (r < 0.25) return 'bounce';
            if (r < 0.4) return 'wave';
            return 'task-done';
        }
        if (outcome === 'cancel') return 'task-cancel';
        if (Math.random() < 0.3) return 'shrug';
        return 'task-error';
    }

    function stuntIdleDuration(stunt) {
        return STUNT_DURATIONS[stunt] || 2800;
    }

    function bubbleMountEl() {
        const icon = el('help-bot-fab-icon');
        return icon?.querySelector('.help-bot-bubble-slot') || icon;
    }

    function ensureFabBubble() {
        const mount = bubbleMountEl();
        if (!mount) return null;
        let bubble = mount.querySelector('.abox-3d-bubble');
        if (!bubble) {
            bubble = document.createElement('span');
            bubble.className = 'abox-3d-bubble';
            mount.appendChild(bubble);
        }
        return bubble;
    }

    function setTaskBubble(text, cssClass) {
        const root = el('help-bot-root');
        if (!text) return;
        const bubble = ensureFabBubble();
        if (!bubble) return;
        bubble.className = 'abox-3d-bubble' + (cssClass ? ' ' + cssClass : '');
        bubble.textContent = text.length > 18 ? text.slice(0, 17) + '…' : text;
        root?.classList.add('show-tip');
    }

    function clearTaskBubbleSoon(ms) {
        window.setTimeout(() => el('help-bot-root')?.classList.remove('show-tip'), ms || 2800);
    }

    function fabIconEl() {
        return el('help-bot-fab-icon');
    }

    function readBotEnabled() {
        try {
            const v = localStorage.getItem(ENABLED_STORAGE_KEY);
            if (v === '0' || v === 'false') return false;
        } catch { /* ignore */ }
        return true;
    }

    function isBotEnabled() {
        return botEnabled;
    }

    function stopIdleMoodCycle() {
        if (idleMoodTimer) {
            clearTimeout(idleMoodTimer);
            idleMoodTimer = null;
        }
    }

    function canCycleIdleMood() {
        return botEnabled && !activeTask && !busy && !systemStressed && !celebrateTimer;
    }

    function randomIdleDelayMs() {
        return 4200 + Math.floor(Math.random() * 3800);
    }

    function pickIdleMood() {
        let total = 0;
        const pool = IDLE_MOOD_POOL.filter((e) => {
            if (e.mood === lastIdleMood && IDLE_MOOD_POOL.length > 1) return false;
            return true;
        });
        const list = pool.length ? pool : IDLE_MOOD_POOL;
        list.forEach((e) => { total += e.weight; });
        let r = Math.random() * total;
        for (let i = 0; i < list.length; i++) {
            r -= list[i].weight;
            if (r <= 0) return list[i];
        }
        return list[0];
    }

    function scheduleIdleMoodCycle() {
        stopIdleMoodCycle();
        if (!canCycleIdleMood()) return;
        idleMoodTimer = window.setTimeout(() => {
            idleMoodTimer = null;
            if (!canCycleIdleMood()) {
                scheduleIdleMoodCycle();
                return;
            }
            const pick = pickIdleMood();
            lastIdleMood = pick.mood;
            const fab = fabIconEl();
            const headAv = el('help-bot-head-avatar');
            setUnitMood(fab, pick.mood);
            if (headAv && !busy) {
                setUnitMood(headAv, pick.mood === 'sleepy' ? 'relaxed' : pick.mood);
            }
            if (pick.stunt && fab) {
                playBuddyStunt(fab, pick.stunt);
                const spinMs = stuntIdleDuration(pick.stunt);
                idleMoodTimer = window.setTimeout(() => {
                    idleMoodTimer = null;
                    scheduleIdleMoodCycle();
                }, spinMs);
                return;
            }
            scheduleIdleMoodCycle();
        }, randomIdleDelayMs());
    }

    function onIdleMoodContextChange() {
        if (canCycleIdleMood()) scheduleIdleMoodCycle();
        else stopIdleMoodCycle();
    }

    function setBotEnabled(on, persist) {
        botEnabled = !!on;
        if (persist !== false) {
            try {
                localStorage.setItem(ENABLED_STORAGE_KEY, botEnabled ? '1' : '0');
            } catch { /* ignore */ }
        }
        applyBotEnabledUI();
    }

    function applyBotEnabledUI() {
        const root = el('help-bot-root');
        const restore = el('help-bot-restore');
        const cb = el('help-bot-enabled');
        if (!root) return;
        root.classList.toggle('is-bot-disabled', !botEnabled);
        if (cb) cb.checked = botEnabled;
        if (restore) restore.hidden = botEnabled;
        if (!botEnabled) {
            setOpen(false);
            stopIdleMoodCycle();
            root.setAttribute('aria-hidden', 'true');
        } else {
            root.setAttribute('aria-hidden', root.classList.contains('is-open') ? 'false' : 'true');
            syncRobotMood();
            onIdleMoodContextChange();
        }
    }

    function restoreFabMoodAfterReaction() {
        if (celebrateTimer) clearTimeout(celebrateTimer);
        celebrateTimer = window.setTimeout(() => {
            celebrateTimer = null;
            const root = el('help-bot-root');
            if (!root || !botEnabled || activeTask || busy) return;
            if (systemStressed) {
                setUnitMood(fabIconEl(), 'panic');
            } else {
                onIdleMoodContextChange();
            }
        }, 2600);
    }

    function reactSystemMetrics(cpuPct, ramPct) {
        if (!botEnabled) return;
        const cpu = Number(cpuPct) || 0;
        const ram = Number(ramPct) || 0;
        const peak = Math.max(cpu, ram);
        const root = el('help-bot-root');
        const fab = fabIconEl();
        const was = systemStressed;
        systemStressed = peak >= STRESS_PCT;

        if (root) root.classList.toggle('is-system-stress', systemStressed);

        if (systemStressed && !was) {
            playBuddyStunt(fab, 'system-panic');
            setTaskBubble('😱 CPU/RAM ' + Math.round(peak) + '%!', 'abox-3d-bubble--panic');
            clearTaskBubbleSoon(3200);
            if (!activeTask && !busy) setUnitMood(fab, 'panic');
        } else if (!systemStressed && was) {
            if (!activeTask && !busy) onIdleMoodContextChange();
        } else if (systemStressed && !activeTask && !busy) {
            setUnitMood(fab, 'panic');
        }
    }

    function reactContentMatch(opts) {
        if (!botEnabled) return;
        const now = Date.now();
        if (now - lastMatchReactAt < 3500) return;
        lastMatchReactAt = now;

        const fab = fabIconEl();
        const title = (opts && (opts.title || opts.trackId || opts.file)) || '';
        const conf = opts && opts.confidence != null
            ? ' ' + Math.round(Number(opts.confidence) * 100) + '%'
            : '';
        const src = opts && opts.source === 'monitor' ? 'Live' : (opts && opts.source === 'identify' ? 'ID' : '');

        playBuddyStunt(fab, 'match-money');
        setUnitMood(fab, 'celebrate');
        const label = '💰 $$$' + (src ? ' ' + src : '') + conf;
        setTaskBubble(title ? '💰 ' + title : label, 'abox-3d-bubble--money');
        clearTaskBubbleSoon(3400);
        restoreFabMoodAfterReaction();
    }

    function reactContentNomatch(opts) {
        if (!botEnabled) return;
        const now = Date.now();
        if (now - lastNomatchReactAt < 8000) return;
        lastNomatchReactAt = now;
        if (activeTask) return;

        const fab = fabIconEl();
        playBuddyStunt(fab, Math.random() < 0.5 ? 'shrug' : 'match-nomatch');
        setTaskBubble('🤷 No match', 'abox-3d-bubble--muted');
        clearTaskBubbleSoon(2200);
        if (!systemStressed && !busy) {
            onIdleMoodContextChange();
        }
    }

    function applyTaskVisual() {
        const root = el('help-bot-root');
        const fabIcon = el('help-bot-fab-icon');
        if (!root || !activeTask) return;
        root.classList.remove('is-task-fingerprint', 'is-task-identify');
        root.classList.add('is-task-' + activeTask.kind);
        const mood = activeTask.phase === 'decoding'
            ? 'fingerprint-decode'
            : (TASK_MOODS[activeTask.kind] || 'idle');
        setUnitMood(fabIcon, mood);
    }

    function startBuddyTask(opts) {
        if (!botEnabled) return;
        const kind = opts && opts.kind;
        if (kind !== 'fingerprint' && kind !== 'identify') return;
        activeTask = {
            kind,
            label: opts.label || (kind === 'fingerprint' ? 'Fingerprinting…' : 'Identifying…'),
            pct: 0,
            phase: opts.phase || null
        };
        lastTaskPhase = opts.phase || null;
        stopIdleMoodCycle();
        markOnTask();
        applyTaskVisual();
        setTaskBubble(activeTask.label);
        playBuddyStunt(el('help-bot-fab-icon'), stuntForTaskStart(kind));
    }

    function updateBuddyTask(opts) {
        if (!activeTask || !opts || opts.kind !== activeTask.kind) return;
        const now = Date.now();
        if (now - lastTaskProgressAt < 350 && opts.pct != null && opts.pct < 100) return;
        lastTaskProgressAt = now;

        if (typeof opts.pct === 'number') activeTask.pct = opts.pct;
        if (opts.phase) activeTask.phase = opts.phase;

        const fabIcon = el('help-bot-fab-icon');
        if (opts.phase && opts.phase !== lastTaskPhase) {
            lastTaskPhase = opts.phase;
            let stunt = stuntForPhase(activeTask.kind, opts.phase);
            if (!stunt && activeTask.kind === 'identify' && Math.random() < 0.2) stunt = 'peek';
            if (stunt) playBuddyStunt(fabIcon, stunt);
        } else if (activeTask.kind === 'identify' && opts.pct != null && opts.pct > 0 && opts.pct < 100) {
            if (!lastTaskPhase || lastTaskPhase === 'listen') {
                lastTaskPhase = 'search';
                playBuddyStunt(fabIcon, 'identify-search');
            }
        }

        let label = opts.label;
        if (!label) {
            if (activeTask.kind === 'fingerprint') {
                if (opts.phase === 'decoding') label = 'Converting…';
                else if (opts.phase === 'fingerprinting') label = 'Hashing…';
                else if (opts.pct != null && opts.total) label = opts.pct + '% · ' + opts.done + '/' + opts.total;
                else label = 'Fingerprinting…';
            } else {
                if (opts.pct != null && opts.total) label = opts.pct + '% · ' + opts.done + '/' + opts.total;
                else label = 'Searching…';
            }
        }
        activeTask.label = label;
        applyTaskVisual();
        setTaskBubble(label);
    }

    function endBuddyTask(opts) {
        if (!activeTask || (opts && opts.kind && opts.kind !== activeTask.kind)) return;
        const root = el('help-bot-root');
        const fabIcon = el('help-bot-fab-icon');
        const outcome = (opts && opts.outcome) || 'done';
        activeTask = null;
        lastTaskProgressAt = 0;
        lastTaskPhase = null;

        if (root) {
            root.classList.remove('is-on-task', 'is-task-fingerprint', 'is-task-identify');
        }
        playBuddyStunt(fabIcon, stuntForTaskEnd(outcome));
        if (outcome === 'done' && Math.random() < 0.45) {
            const extras = ['spin-360', 'wave', 'bounce', 'wiggle'];
            const extra = extras[Math.floor(Math.random() * extras.length)];
            window.setTimeout(() => {
                if (!activeTask && !busy && botEnabled) playBuddyStunt(fabIcon, extra);
            }, 750);
        }
        if (outcome === 'done') {
            setTaskBubble('Done');
            clearTaskBubbleSoon();
        } else if (outcome === 'cancel') {
            setTaskBubble('Paused');
            clearTaskBubbleSoon();
        } else {
            setUnitMood(fabIcon, 'alert');
            setTaskBubble('Failed');
            clearTaskBubbleSoon();
            window.setTimeout(() => {
                if (!busy && botEnabled) onIdleMoodContextChange();
            }, 2000);
        }
        syncRobotMood();
        onIdleMoodContextChange();
    }

    window.AboxBuddy = {
        enabled: isBotEnabled,
        setEnabled: setBotEnabled,
        startTask: startBuddyTask,
        updateTask: updateBuddyTask,
        endTask: endBuddyTask,
        reactSystem: reactSystemMetrics,
        reactMatch: reactContentMatch,
        reactNomatch: reactContentNomatch
    };

    function syncRobotMood() {
        const root = el('help-bot-root');
        const fabIcon = el('help-bot-fab-icon');
        const headAv = el('help-bot-head-avatar');
        if (!root) return;
        if (activeTask && !busy) {
            applyTaskVisual();
            return;
        }
        if (root.classList.contains('is-open')) {
            if (busy) {
                setUnitMood(fabIcon, 'think');
                setUnitMood(headAv, 'think');
                stopIdleMoodCycle();
                return;
            }
            setUnitMood(headAv, systemStressed ? 'panic' : (lastIdleMood || 'idle'));
            if (systemStressed) setUnitMood(fabIcon, 'panic');
            else onIdleMoodContextChange();
            return;
        }
        if (busy) {
            setUnitMood(fabIcon, 'think');
            setUnitMood(headAv, 'think');
            playBuddyStunt(fabIcon, 'think');
            return;
        }
        setUnitMood(headAv, busy ? 'think' : 'idle');
        if (!activeTask) {
            setUnitMood(fabIcon, systemStressed ? 'panic' : lastIdleMood || 'idle');
        }
        onIdleMoodContextChange();
    }

    function appendTyping() {
        const log = el('help-bot-messages');
        if (!log) return;
        removeTyping();
        const row = document.createElement('div');
        row.className = 'help-bot-msg help-bot-msg--bot help-bot-msg--typing';
        row.dataset.typing = '1';
        row.innerHTML =
            '<div class="help-bot-msg-inner">'
            + '<span class="help-bot-avatar help-bot-avatar--sm" aria-hidden="true">' + botHostHtml('abox-3d-host--sm', 'think') + '</span>'
            + '<div class="help-bot-bubble">'
            + '<div class="help-bot-msg-label"><span class="help-bot-ai-badge">AI</span> Assistant is typing…</div>'
            + '<div class="help-bot-typing-dots" aria-hidden="true"><span></span><span></span><span></span></div>'
            + '</div></div>';
        log.appendChild(row);
        mountAvatar(row);
        scrollLog();
    }

    function removeTyping() {
        el('help-bot-messages')?.querySelectorAll('[data-typing="1"]').forEach(n => n.remove());
    }

    function formatAnswerBody(data) {
        const parts = [];
        if (data.pageTab) {
            parts.push('<p class="help-bot-page"><span class="help-bot-page-tag">'
                + escapeHtml(tabLabel(data.pageTab)) + ' tab</span></p>');
        }
        if (data.summary) {
            parts.push('<p class="help-bot-lead">' + escapeHtml(data.summary) + '</p>');
        }
        if (data.steps && data.steps.length) {
            parts.push('<p class="help-bot-label">Detailed steps</p><ol class="help-bot-steps">');
            for (const s of data.steps) parts.push('<li>' + escapeHtml(s) + '</li>');
            parts.push('</ol>');
        }
        if (data.tips && data.tips.length) {
            parts.push('<p class="help-bot-label">Tips</p><ul class="help-bot-tips">');
            for (const t of data.tips) parts.push('<li>' + escapeHtml(t) + '</li>');
            parts.push('</ul>');
        }
        if (data.related && data.related.length) {
            parts.push('<p class="help-bot-related">You can also ask: ');
            parts.push(data.related.map(q =>
                '<button type="button" class="help-bot-chip help-bot-chip--inline" data-q="'
                + escapeHtml(q) + '">' + escapeHtml(q) + '</button>'
            ).join(' '));
            parts.push('</p>');
        }
        if (data.confidence != null && data.matched) {
            parts.push('<p class="help-bot-meta">Confidence '
                + Math.round((data.confidence || 0) * 100) + '% · ' + escapeHtml(data.source || 'AI') + '</p>');
        }
        return parts.join('');
    }

    function createBotBubble() {
        const log = el('help-bot-messages');
        const row = document.createElement('div');
        row.className = 'help-bot-msg help-bot-msg--bot help-bot-msg--streaming';
        row.innerHTML =
            '<div class="help-bot-msg-inner">'
            + '<span class="help-bot-avatar help-bot-avatar--sm" aria-hidden="true">' + botHostHtml('abox-3d-host--sm', 'idle') + '</span>'
            + '<div class="help-bot-bubble">'
            + '<div class="help-bot-msg-label"><span class="help-bot-ai-badge">AI</span> Assistant</div>'
            + '<div class="help-bot-stream-target"></div>'
            + '<span class="help-bot-cursor" aria-hidden="true"></span>'
            + '</div></div>';
        log.appendChild(row);
        mountAvatar(row.querySelector('.help-bot-avatar'));
        scrollLog();
        return {
            row,
            target: row.querySelector('.help-bot-stream-target'),
            cursor: row.querySelector('.help-bot-cursor')
        };
    }

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

    async function streamText(el, text) {
        if (!el || !text) return;
        for (let i = 0; i <= text.length; i += STREAM_CHARS) {
            el.innerHTML = formatAiText(text.slice(0, i));
            scrollLog();
            await delay(STREAM_MS);
        }
    }

    async function revealAnswer(data) {
        const { row, target, cursor } = createBotBubble();

        if (data.aiPreamble) {
            const pre = document.createElement('p');
            pre.className = 'help-bot-preamble';
            target.appendChild(pre);
            await streamText(pre, data.aiPreamble);
        }

        const extra = document.createElement('div');
        extra.className = 'help-bot-extra help-bot-fade-in';
        extra.innerHTML = formatAnswerBody(data);
        target.appendChild(extra);

        row.classList.remove('help-bot-msg--streaming');
        cursor?.remove();
        scrollLog();
    }

    async function fetchAnswer(question) {
        const r = await fetch('/api/help/ask', {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ question })
        });
        if (!r.ok) {
            const err = await r.json().catch(() => ({}));
            throw new Error(err.error || ('HTTP ' + r.status));
        }
        return r.json();
    }

    function enqueue(task) {
        queue = queue.then(task).catch(() => {});
        return queue;
    }

    async function processQuestion(text) {
        const q = (text || '').trim();
        if (!q) return;

        greetToken++;
        removeTyping();

        appendUserMsg(q);
        await delay(50);
        scrollLog();

        setThinking(true);
        appendTyping();

        const thinkMs = THINK_MS_MIN + Math.random() * (THINK_MS_MAX - THINK_MS_MIN);

        try {
            const [data] = await Promise.all([
                fetchAnswer(q),
                delay(thinkMs)
            ]);
            removeTyping();
            await revealAnswer({
                matched: data.matched,
                aiPreamble: data.aiPreamble || (data.matched ? 'Here is what I know:' : ''),
                summary: data.summary,
                steps: data.steps,
                tips: data.tips,
                related: data.related,
                confidence: data.confidence,
                source: data.source,
                pageTab: data.pageTab
            });
        } catch (err) {
            removeTyping();
            await revealAnswer({
                matched: false,
                aiPreamble: 'I could not reach the AI help service right now.',
                summary: err.message + ' — check that the server is running, then press Ctrl+F5.',
                steps: [],
                tips: [],
                related: [],
                pageTab: null
            });
        } finally {
            setThinking(false);
        }
    }

    function submitQuestion(text) {
        if (busy) return;
        enqueue(() => processQuestion(text));
    }

    function setOpen(open) {
        const root = el('help-bot-root');
        const panel = el('help-bot-panel');
        const toggle = el('help-bot-toggle');
        if (!root || !panel || !toggle) return;
        if (!botEnabled) {
            open = false;
        }
        root.classList.toggle('is-open', open);
        panel.hidden = !open;
        toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
        root.setAttribute('aria-hidden', open ? 'false' : 'true');
        syncRobotMood();
        syncPanelPlacement(root);
        if (open) {
            setTimeout(() => el('help-bot-input')?.focus(), 150);
            const log = el('help-bot-messages');
            if (log && !log.dataset.greeted && log.children.length === 0) {
                log.dataset.greeted = '1';
                enqueue(greet);
            }
        }
    }

    async function loadSuggestions() {
        const chips = el('help-bot-chips');
        if (!chips) return;
        try {
            const r = await fetch('/api/help/suggestions', { credentials: 'same-origin' });
            if (r.ok) {
                const data = await r.json();
                const list = data.questions || [];
                chips.innerHTML = list.map(q =>
                    '<button type="button" class="help-bot-chip" data-q="' + escapeHtml(q) + '">' + escapeHtml(q) + '</button>'
                ).join('');
                return;
            }
        } catch { /* fallback */ }
        chips.innerHTML = [
            'List all Report KPIs',
            'Explain all Report charts',
            'Match rate KPI',
            'Hour × Day-of-week heatmap',
            'Live monitor process',
            'YouTube stream not working'
        ].map(q =>
            '<button type="button" class="help-bot-chip" data-q="' + escapeHtml(q) + '">' + escapeHtml(q) + '</button>'
        ).join('');
    }

    async function refreshStatus() {
        const status = el('help-bot-status');
        try {
            const r = await fetch('/api/help/status', { credentials: 'same-origin' });
            if (r.ok) {
                const s = await r.json();
                statusOnline = !!s.online;
                if (status) {
                    status.textContent = statusOnline
                        ? 'AI online · ' + (s.label || 'ready')
                        : 'AI offline';
                    status.classList.toggle('is-offline', !statusOnline);
                }
            }
        } catch {
            statusOnline = false;
            if (status) {
                status.textContent = 'AI reconnecting…';
                status.classList.add('is-offline');
            }
        }
    }

    async function greet() {
        const token = ++greetToken;
        setThinking(true);
        appendTyping();
        await delay(700);
        if (token !== greetToken) return;
        removeTyping();
        await revealAnswer({
            matched: true,
            aiPreamble: 'Hello! I am your **AUDIOBoxx AI assistant**. I explain **every Report KPI, chart, and table**, Overview metrics, live/offline **processes**, and troubleshooting (YouTube, LAN, play kind).',
            summary: 'Ask any metric by name (e.g. "Match rate KPI") or say "List all Report charts" for the full catalog. Your question appears first, then my detailed answer.',
            steps: [],
            tips: ['Try: "List all Report KPIs", "Daily trend chart", or "What is consolidated airplay?"'],
            related: [],
            pageTab: null,
            confidence: 1,
            source: 'AUDIOBoxx AI'
        });
        setThinking(false);
    }

    function init() {
        const toggle = el('help-bot-toggle');
        const close = el('help-bot-close');
        const form = el('help-bot-form');
        const root = el('help-bot-root');
        if (!toggle || !form) return;

        botEnabled = readBotEnabled();
        applyBotEnabledUI();

        el('help-bot-restore')?.addEventListener('click', () => setBotEnabled(true));
        el('help-bot-enabled')?.addEventListener('change', (e) => {
            setBotEnabled(!!e.target.checked);
        });

        const setupAvatars = async () => {
            const fabIcon = el('help-bot-fab-icon');
            const headAv = el('help-bot-head-avatar');
            if (fabIcon) fabIcon.innerHTML = fabShellHtml('abox-3d-host--fab', 'idle');
            if (headAv) headAv.innerHTML = botHostHtml('abox-3d-host--head', 'idle');
            const threeOk = await (window.AboxBuddy3d?.init?.() ?? Promise.resolve(false));
            if (!threeOk) {
                if (fabIcon) fabIcon.innerHTML = botFallbackHtml('abox-3d-host--fab');
                if (headAv) headAv.innerHTML = botFallbackHtml('abox-3d-host--head');
                return;
            }
            await mountAvatarWithFallback(fabIcon);
            await mountAvatarWithFallback(headAv);
            window.setTimeout(() => {
                mountAvatarWithFallback(fabIcon);
                mountAvatarWithFallback(headAv);
                onIdleMoodContextChange();
            }, 600);
        };
        setupAvatars().then(() => onIdleMoodContextChange());

        restoreBuddyPosition(root);
        initBuddyDrag(root, toggle);

        toggle.addEventListener('click', () => {
            if (!botEnabled) return;
            if (toggle.dataset.dragSuppress === '1') return;
            setOpen(!root.classList.contains('is-open'));
        });
        close?.addEventListener('click', () => setOpen(false));

        form.addEventListener('submit', (e) => {
            e.preventDefault();
            const input = el('help-bot-input');
            const v = input?.value || '';
            if (input) input.value = '';
            submitQuestion(v);
        });

        root?.addEventListener('click', (e) => {
            const chip = e.target.closest('.help-bot-chip[data-q]');
            if (chip) {
                e.preventDefault();
                e.stopPropagation();
                submitQuestion(chip.getAttribute('data-q'));
            }
        });

        document.addEventListener('click', (e) => {
            if (!root?.classList.contains('is-open')) return;
            if (e.target.closest('#help-bot-root')) return;
            setOpen(false);
        });

        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') setOpen(false);
        });

        loadSuggestions();
        refreshStatus();
        setInterval(refreshStatus, 45000);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
