// briefing.js — Toast, provider health, credential alerts, sparkline, skills, trends, status
// Extracted from dashboard.html

// ── Toast notification helper ──────────────────────────────
// ── Provider health warning ─────────────────────────────────
function showProviderWarning(warning) {
    const banner = document.getElementById('provider-warning-banner');
    const text = document.getElementById('provider-warning-text');
    const link = document.getElementById('provider-warning-link');
    if (!banner || !warning) return;
    const provider = (warning.provider || 'Provider').replace(/^\w/, c => c.toUpperCase());
    text.textContent = provider + ' account has insufficient credits. Your messages may fail or use a fallback provider.';
    if (warning.billing_url) {
        link.href = warning.billing_url;
        link.style.display = 'inline-block';
    } else {
        link.style.display = 'none';
    }
    banner.style.display = 'block';
}

function checkProviderHealth() {
    apiFetch(API + '/api/provider-health')
        .then(r => r.json())
        .then(data => {
            if (data.warnings && data.warnings.length > 0) {
                showProviderWarning(data.warnings[0]);
            }
        })
        .catch(() => {});
}

// Check provider health on page load (delayed to not block rendering)
setTimeout(checkProviderHealth, APP_CONFIG.delay.providerHealth);

// ── Credential attention alerts ──────────────────────────────
let _credAlertDismissed = new Set();

function checkCredentialAttention() {
    apiFetch(API + '/api/credentials/attention')
        .then(r => r.json())
        .then(data => {
            const banner = document.getElementById('cred-alert-banner');
            if (!banner) return;
            const active = (data.services || []).filter(s => !_credAlertDismissed.has(s.service_key));
            if (active.length === 0) {
                banner.style.display = 'none';
                return;
            }
            const names = active.map(s => s.service_key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()));
            const text = document.getElementById('cred-alert-text');
            if (active.length === 1) {
                text.textContent = names[0] + ' needs re-authorization. Some tools may not work.';
            } else {
                text.textContent = active.length + ' integrations need attention: ' + names.join(', ') + '.';
            }
            banner.style.display = 'block';
        })
        .catch(() => {});
}

function dismissCredAlert() {
    const banner = document.getElementById('cred-alert-banner');
    if (banner) banner.style.display = 'none';
    // Mark all currently visible alerts as dismissed for this session
    apiFetch(API + '/api/credentials/attention')
        .then(r => r.json())
        .then(data => {
            (data.services || []).forEach(s => _credAlertDismissed.add(s.service_key));
        })
        .catch(() => {});
}

// Poll credential attention every 60s, initial check after 5s
setTimeout(checkCredentialAttention, APP_CONFIG.delay.credentialHealth);
PollManager.start('cred-attention', checkCredentialAttention, APP_CONFIG.poll.credentialCheck);

// Pick up onboarding wizard handoff (user clicked Helper Hat during onboarding)
setTimeout(() => {
    const raw = sessionStorage.getItem('familiar_handoff');
    if (raw) {
        sessionStorage.removeItem('familiar_handoff');
        try {
            const handoff = JSON.parse(raw);
            switchPanel('chat');
            apiFetch('/api/wizard/handoff', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(handoff),
            })
            .then(r => r.json())
            .then(data => {
                if (data.response) addMessage(data.response, 'assistant');
            })
            .catch(() => {
                addMessage('Helper handoff from onboarding failed — please try again.', 'assistant');
            });
        } catch (_) {}
    }
}, 1000);

// showToast — defined in core.js

// RoundTableState.seats / .jobClasses / .mode (namespaced above)

function setRtMode(mode) {
    RoundTableState.mode = mode;
    document.getElementById('rt-mode-chairman').style.background = mode === 'chairman' ? 'var(--accent)' : 'none';
    document.getElementById('rt-mode-chairman').style.color = mode === 'chairman' ? '#fff' : 'var(--text-dim)';
    document.getElementById('rt-mode-chairman').style.fontWeight = mode === 'chairman' ? '600' : '400';
    document.getElementById('rt-mode-manual').style.background = mode === 'manual' ? 'var(--accent)' : 'none';
    document.getElementById('rt-mode-manual').style.color = mode === 'manual' ? '#fff' : 'var(--text-dim)';
    document.getElementById('rt-mode-manual').style.fontWeight = mode === 'manual' ? '600' : '400';
    document.getElementById('rt-seats-row').style.display = mode === 'manual' ? '' : 'none';
    document.getElementById('rt-chairman-hint').style.display = mode === 'chairman' ? '' : 'none';
    const inp = document.getElementById('rt-input');
    const btnLabel = document.getElementById('rt-btn-label');
    if (mode === 'chairman') {
        inp.placeholder = 'Ask the Chairman anything...';
        btnLabel.textContent = 'Convene';
    } else {
        inp.placeholder = 'Ask selected advisors a question...';
        btnLabel.textContent = 'Ask';
    }
}

function switchRtTab(tab) {
    ['chat', 'advisors', 'groups'].forEach(t => {
        const panel = document.getElementById('rt-subpanel-' + t);
        const btn = document.getElementById('rt-tab-' + t);
        if (!panel || !btn) return;
        const active = t === tab;
        panel.style.display = active ? (t === 'chat' ? 'flex' : 'block') : 'none';
        btn.classList.toggle('active', active);
    });
    if (tab === 'advisors') renderRtAdvisorCards();
    if (tab === 'groups') loadRtGroups();
}

async function renderRtAdvisorCards() {
    const container = document.getElementById('rt-advisor-cards');
    if (!container) return;
    // Fetch if not loaded yet
    if (!RoundTableState.jobClasses.length) {
        container.innerHTML = '<p style="color:var(--text-dim);font-size:0.85rem;">Loading advisors...</p>';
        try {
            const r = await apiFetch('/api/config/job-class');
            const d = await r.json();
            RoundTableState.jobClasses = d.classes || [];
            _renderRtSeats();
        } catch(e) {
            container.innerHTML = '<p style="color:#f87171;font-size:0.85rem;">Could not load advisors.</p>';
            return;
        }
    }
    container.innerHTML = '';
    RoundTableState.jobClasses.forEach(jc => {
        const seated = RoundTableState.seats.has(jc.key);
        const card = document.createElement('div');
        card.className = 'jc-card' + (seated ? ' active' : '');
        card.id = 'rt-advisor-card-' + jc.key;
        card.innerHTML = `
            <div class="jc-icon"><i class="fas ${jc.icon || 'fa-user'}"></i></div>
            <div class="jc-name">${jc.name}</div>
            <div class="jc-desc">${jc.description || ''}</div>
            <div style="margin-top:8px;">
                <span id="rt-ac-badge-${jc.key}" style="font-size:0.72rem;padding:3px 10px;border-radius:20px;font-weight:600;background:${seated ? 'var(--accent)' : 'transparent'};color:${seated ? '#fff' : 'var(--text-dim)'};border:1px solid ${seated ? 'var(--accent)' : 'var(--border)'};">
                    ${seated ? 'Seated' : 'Seat'}
                </span>
            </div>`;
        card.addEventListener('click', () => {
            const isSat = RoundTableState.seats.has(jc.key);
            if (isSat) { RoundTableState.seats.delete(jc.key); card.classList.remove('active'); }
            else { RoundTableState.seats.add(jc.key); card.classList.add('active'); }
            const nowSat = RoundTableState.seats.has(jc.key);
            const badge = document.getElementById('rt-ac-badge-' + jc.key);
            if (badge) {
                badge.style.background = nowSat ? 'var(--accent)' : 'transparent';
                badge.style.color = nowSat ? '#fff' : 'var(--text-dim)';
                badge.style.borderColor = nowSat ? 'var(--accent)' : 'var(--border)';
                badge.textContent = nowSat ? 'Seated' : 'Seat';
            }
            // Keep manual seat strip in sync
            const seatBtn = document.querySelector('#rt-seats .rt-seat[data-key="' + jc.key + '"]');
            if (seatBtn) { if (nowSat) seatBtn.classList.add('active'); else seatBtn.classList.remove('active'); }
        });
        container.appendChild(card);
    });
    const tip = document.createElement('p');
    tip.style.cssText = 'font-size:0.78rem;color:var(--text-dim);margin:14px 0 0;grid-column:1/-1;';
    tip.innerHTML = 'Click an advisor to seat them, then head to <strong>Chat → Manual</strong> to ask them directly.';
    container.appendChild(tip);
}

async function loadRtGroups() {
    const list = document.getElementById('rt-groups-list');
    if (!list) return;
    list.innerHTML = '<p style="color:var(--text-dim);font-size:0.85rem;">Loading...</p>';
    try {
        const r = await apiFetch('/api/roundtable/groups');
        const data = await r.json();
        const groups = data.groups || [];
        if (!groups.length) {
            list.innerHTML = '<p style="color:var(--text-dim);font-size:0.85rem;">No saved groups yet. In Chat mode, seat some advisors then click <strong>Save Group</strong>.</p>';
            return;
        }
        list.innerHTML = '';
        groups.forEach(g => {
            const row = document.createElement('div');
            row.style.cssText = 'background:var(--bg-dark);border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin-bottom:10px;display:flex;align-items:center;gap:12px;';
            const names = (g.seats || []).map(key => {
                const jc = RoundTableState.jobClasses.find(j => j.key === key);
                return jc ? jc.name : key;
            }).join(', ');
            row.innerHTML = `
                <div style="flex:1;min-width:0;">
                    <div style="font-weight:700;font-size:0.9rem;color:var(--text);margin-bottom:2px;">${g.name}</div>
                    <div style="font-size:0.78rem;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${names || 'No advisors'}</div>
                </div>
                <button onclick="_applyRtGroup(${JSON.stringify(g.seats || [])})" style="font-size:0.78rem;background:var(--accent);color:#fff;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;font-weight:600;white-space:nowrap;">Use</button>
                <button onclick="deleteRtGroup(${JSON.stringify(g.name)})" style="font-size:0.78rem;background:none;border:1px solid var(--border);color:var(--text-dim);border-radius:6px;padding:6px 10px;cursor:pointer;" title="Delete group"><i class="fas fa-trash"></i></button>`;
            list.appendChild(row);
        });
    } catch(e) {
        list.innerHTML = '<p style="color:#f87171;font-size:0.85rem;">Failed to load groups.</p>';
    }
}

function _applyRtGroup(seats) {
    RoundTableState.seats = new Set(seats);
    _renderRtSeats();
    setRtMode('manual');
    switchRtTab('chat');
    showToast('Group loaded — ' + seats.length + ' advisor' + (seats.length !== 1 ? 's' : '') + ' seated.');
}

function showSaveGroupDialog() {
    const dialog = document.getElementById('rt-save-dialog');
    const label = document.getElementById('rt-save-dialog-seats-label');
    const nameInput = document.getElementById('rt-save-dialog-name');
    const seated = [...RoundTableState.seats];
    if (!seated.length) { showToast('No advisors seated yet.'); return; }
    const names = seated.map(key => {
        const jc = RoundTableState.jobClasses.find(j => j.key === key);
        return jc ? jc.name : key;
    }).join(', ');
    label.textContent = 'Advisors: ' + names;
    nameInput.value = '';
    dialog.classList.add('show');
    setTimeout(() => nameInput.focus(), 50);
}

async function saveRtGroup() {
    const nameInput = document.getElementById('rt-save-dialog-name');
    const name = (nameInput.value || '').trim();
    if (!name) { nameInput.focus(); return; }
    const seats = [...RoundTableState.seats];
    try {
        const r = await apiFetch('/api/roundtable/groups', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ name, seats })
        });
        const data = await r.json();
        document.getElementById('rt-save-dialog').classList.remove('show');
        if (data.error) { showToast('Error: ' + data.error); return; }
        showToast('Group "' + name + '" saved!');
    } catch(e) {
        showToast('Save failed: ' + e.message);
    }
}

async function deleteRtGroup(name) {
    if (!confirm('Delete group "' + name + '"?')) return;
    try {
        await apiFetch('/api/roundtable/groups/' + encodeURIComponent(name), {
            method: 'DELETE'
        });
        loadRtGroups();
    } catch(e) {
        showToast('Delete failed: ' + e.message);
    }
}

async function initRoundTable() {
    if (RoundTableState.jobClasses.length) return;
    try {
        const r = await apiFetch('/api/config/job-class');
        const d = await r.json();
        RoundTableState.jobClasses = d.classes || [];
        _renderRtSeats();
    } catch(e) {
        console.warn('Round Table: could not load job classes', e);
    }
}

function _renderRtSeats() {
    const el = document.getElementById('rt-seats');
    if (!el) return;
    el.innerHTML = '';
    RoundTableState.jobClasses.forEach(jc => {
        const btn = document.createElement('div');
        btn.className = 'rt-seat' + (RoundTableState.seats.has(jc.key) ? ' active' : '');
        btn.dataset.key = jc.key;
        btn.innerHTML = `<i class="fas ${jc.icon || 'fa-user'}"></i><span>${jc.name}</span>`;
        btn.addEventListener('click', () => {
            if (RoundTableState.seats.has(jc.key)) { RoundTableState.seats.delete(jc.key); btn.classList.remove('active'); }
            else { RoundTableState.seats.add(jc.key); btn.classList.add('active'); }
        });
        el.appendChild(btn);
    });
    const controls = document.createElement('div');
    controls.style.cssText = 'margin-left:auto; display:flex; gap:6px; align-items:center;';
    controls.innerHTML = `
        <button onclick="_rtSelectAll()" style="font-size:0.76rem; background:none; border:1px solid var(--border); color:var(--text-dim); border-radius:6px; padding:4px 8px; cursor:pointer;">All</button>
        <button onclick="_rtClearSeats()" style="font-size:0.76rem; background:none; border:1px solid var(--border); color:var(--text-dim); border-radius:6px; padding:4px 8px; cursor:pointer;">None</button>
    `;
    el.appendChild(controls);
}

function _rtSelectAll() {
    RoundTableState.jobClasses.forEach(jc => RoundTableState.seats.add(jc.key));
    document.querySelectorAll('#rt-seats .rt-seat').forEach(b => b.classList.add('active'));
}
function _rtClearSeats() {
    RoundTableState.seats.clear();
    document.querySelectorAll('#rt-seats .rt-seat').forEach(b => b.classList.remove('active'));
}

function _rtClear() {
    document.getElementById('rt-empty').style.display = 'none';
    document.getElementById('rt-local-warn').style.display = 'none';
    document.getElementById('rt-chairman-card').style.display = 'none';
    document.getElementById('rt-synthesis-card').style.display = 'none';
    document.getElementById('rt-advisor-grid').innerHTML = '';
}

function _rtMd(text) {
    if (!text) return '';
    // Escape HTML entities first so raw <script> tags can't execute,
    // then apply markdown — **bold** etc. still works on escaped text.
    let safe = escapeHtml(text);
    if (typeof marked !== 'undefined') {
        return marked.parse(safe);
    }
    return safe.replace(/\n/g, '<br>');
}

function _rtAdvisorCard(key, name, icon) {
    const grid = document.getElementById('rt-advisor-grid');
    const card = document.createElement('div');
    card.className = 'rt-card';
    card.id = 'rt-card-' + key;
    card.innerHTML = `
        <div class="rt-card-header">
            <i class="fas ${icon || 'fa-user'}"></i>
            <span class="rt-card-name">${name}</span>
        </div>
        <div class="rt-card-body loading" id="rt-body-${key}">
            <span class="rt-spinner"></span>Thinking...
        </div>`;
    grid.appendChild(card);
}

async function sendRoundTable(msgText) {
    const input = document.getElementById('rt-input');
    const message = msgText != null ? msgText.trim() : (input.value || '').trim();
    if (!message) return;
    const btn = document.getElementById('rt-send-btn');
    input.value = '';
    btn.disabled = true;
    _rtClear();

    if (RoundTableState.mode === 'chairman') {
        await _sendChairmanConvene(message);
    } else {
        if (RoundTableState.seats.size === 0) { showToast('Select at least one advisor first.'); btn.disabled = false; return; }
        await _sendManualRoundTable(message);
    }
    btn.disabled = false;
}

async function _sendChairmanConvene(message) {
    // Show Chairman card in loading state
    const chairCard = document.getElementById('rt-chairman-card');
    const chairBody = document.getElementById('rt-chairman-body');
    const chairLabel = document.getElementById('rt-chairman-label');
    chairCard.style.display = '';
    chairLabel.textContent = 'routing...';
    chairBody.innerHTML = '<span class="rt-spinner"></span>The Chairman is reviewing your question...';

    try {
        const r = await apiFetch('/api/roundtable/convene', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ message })
        });
        const data = await r.json();

        if (data.mode === 'direct') {
            chairLabel.textContent = 'direct answer';
            chairBody.innerHTML = _rtMd(data.chairman_intro || '');
            return;
        }

        // Show intro
        chairLabel.textContent = 'convening ' + (data.responses || []).length + ' advisor' + ((data.responses||[]).length !== 1 ? 's' : '');
        chairBody.innerHTML = _rtMd(data.chairman_intro || '');

        // Render advisor responses
        (data.responses || []).forEach(resp => {
            _rtAdvisorCard(resp.job_class_key, resp.name, resp.icon);
            const body = document.getElementById('rt-body-' + resp.job_class_key);
            if (!body) return;
            if (resp.error) {
                body.className = 'rt-card-body error';
                body.textContent = 'Error: ' + resp.error;
            } else {
                body.className = 'rt-card-body';
                body.innerHTML = _rtMd(resp.response || '');
            }
        });

        // Synthesis
        if (data.chairman_synthesis) {
            const synCard = document.getElementById('rt-synthesis-card');
            const synBody = document.getElementById('rt-synthesis-body');
            synBody.innerHTML = _rtMd(data.chairman_synthesis);
            synCard.style.display = '';
        }

        if (data.is_local) document.getElementById('rt-local-warn').style.display = '';

    } catch(e) {
        chairLabel.textContent = 'error';
        chairBody.innerHTML = '<span style="color:var(--error)">Request failed: ' + e.message + '</span>';
    }
}

async function _sendManualRoundTable(message) {
    const seats = [...RoundTableState.seats];
    // Show loading cards immediately
    seats.forEach(key => {
        const jc = RoundTableState.jobClasses.find(j => j.key === key) || { key, name: key, icon: 'fa-user' };
        _rtAdvisorCard(key, jc.name, jc.icon);
    });
    try {
        const r = await apiFetch('/api/roundtable', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ message, seats })
        });
        const data = await r.json();
        (data.responses || []).forEach(resp => {
            const body = document.getElementById('rt-body-' + resp.job_class_key);
            if (!body) return;
            if (resp.error) {
                body.className = 'rt-card-body error';
                body.textContent = 'Error: ' + resp.error;
            } else {
                body.className = 'rt-card-body';
                body.innerHTML = _rtMd(resp.response || '');
            }
        });
        if (data.is_local) document.getElementById('rt-local-warn').style.display = '';
    } catch(e) {
        seats.forEach(key => {
            const body = document.getElementById('rt-body-' + key);
            if (body) { body.className = 'rt-card-body error'; body.textContent = 'Request failed: ' + e.message; }
        });
    }
}

// ============================================================
// Activity Monitor
// ============================================================
_activityVisible = false;
// _activityTimer — managed by PollManager('activity')

// Tab switching
document.getElementById('activity-tabs').addEventListener('click', e => {
    const btn = e.target.closest('[data-atab]');
    if (!btn) return;
    document.querySelectorAll('#activity-tabs .wizard-tab').forEach(t => t.classList.remove('active'));
    btn.classList.add('active');
    ['calllog','tools','llm','health','errors','alerts','skills','trends'].forEach(t => {
        document.getElementById('atab-' + t).style.display = t === btn.dataset.atab ? '' : 'none';
    });
    if (btn.dataset.atab === 'calllog') loadCallLog(1);
});

function _startActivityPolling() {
    PollManager.start('activity', () => {
        if (_activityVisible) loadActivity();
        else PollManager.stop('activity');
    }, APP_CONFIG.poll.activity);
}

function _fmtTokens(n) {
    if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
    if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
    return String(n);
}

function _rateClass(rate) {
    if (rate >= 0.95) return 'activity-status-ok';
    if (rate >= 0.80) return 'activity-status-warn';
    return 'activity-status-err';
}

function _progressColor(pct) {
    if (pct > 90) return 'var(--error)';
    if (pct > 70) return 'var(--warning)';
    return 'var(--success)';
}

async function loadActivity() {
    try {
        const [mRes, eRes, hRes, kRes, sRes] = await Promise.all([
            apiFetch(API + '/api/activity/metrics'),
            apiFetch(API + '/api/activity/errors'),
            apiFetch(API + '/api/activity/health'),
            apiFetch(API + '/api/activity/kpis'),
            apiFetch(API + '/api/activity/monthly-spend'),
        ]);
        const metrics = await mRes.json();
        const errors = await eRes.json();
        const health = await hRes.json();
        const kpis = await kRes.json();
        const spend = await sRes.json();

        renderPersistentKPIs(kpis, spend);
        renderMonthlySpend(spend, kpis);
        renderActivityMetrics(metrics);
        renderActivityErrors(errors);
        renderActivityHealth(health);

        // Lazy-load tabs
        loadCallLog(1);
        loadSkillCosts();
        loadTrends();

        document.getElementById('activity-updated').textContent =
            'Updated ' + formatDate(new Date(), 'timestamp');
    } catch (e) {
        console.error('Activity load error', e);
    }
}

function renderPersistentKPIs(kpis, spend) {
    const t = kpis.today || {};
    const m = kpis.month || {};
    const a = kpis.all_time || {};

    document.getElementById('kpi-today-cost').textContent = '$' + (t.cost || 0).toFixed(4);
    document.getElementById('kpi-today-calls').textContent = (t.calls || 0) + ' calls';
    document.getElementById('kpi-month-cost').textContent = '$' + (m.cost || 0).toFixed(4);
    document.getElementById('kpi-month-calls').textContent = (m.calls || 0) + ' calls';
    document.getElementById('kpi-projected').textContent = '$' + (spend.projected || 0).toFixed(2);
    document.getElementById('kpi-daily-avg').textContent = '$' + (spend.daily_avg || 0).toFixed(4) + '/day avg';
    document.getElementById('kpi-alltime-cost').textContent = '$' + (a.cost || 0).toFixed(4) + ' all time';

    const model = kpis.active_model || '—';
    // Shorten model name for display
    const shortModel = model.replace('claude-', '').replace('gemini-', 'gem-').replace('-preview', '');
    document.getElementById('kpi-active-model').textContent = shortModel;
    document.getElementById('kpi-active-model').title = model;

    document.getElementById('kpi-today-tokens').textContent = _fmtTokens((t.tokens_in || 0) + (t.tokens_out || 0));
    document.getElementById('kpi-month-tokens').textContent = _fmtTokens((m.tokens_in || 0) + (m.tokens_out || 0));
}

function renderMonthlySpend(spend, kpis) {
    const total = spend.total || 0;
    const proactive = spend.proactive_cost || 0;
    const interactive = spend.interactive_cost || 0;

    // Spend bar proportions
    const barTotal = Math.max(total, 0.001);
    document.getElementById('spend-bar-interactive').style.width = ((interactive / barTotal) * 100) + '%';
    document.getElementById('spend-bar-proactive').style.width = ((proactive / barTotal) * 100) + '%';

    document.getElementById('spend-interactive').textContent = '$' + interactive.toFixed(4);
    document.getElementById('spend-proactive').textContent = '$' + proactive.toFixed(4);

    // vs last month
    const lm = spend.last_month_total || 0;
    const pct = spend.change_pct || 0;
    const vsEl = document.getElementById('spend-vs-last');
    if (lm > 0) {
        const arrow = pct > 0 ? '&#9650;' : pct < 0 ? '&#9660;' : '&#9654;';
        const color = pct > 10 ? 'var(--error)' : pct < -10 ? 'var(--success)' : 'var(--text-dim)';
        vsEl.innerHTML = '<span style="color:' + color + ';">' + arrow + ' ' + Math.abs(pct).toFixed(0) + '%</span> vs last month ($' + lm.toFixed(2) + ')';
    } else {
        vsEl.textContent = 'First month tracking';
    }
}

// CalendarState.callLogTrigger (namespaced above)
async function loadCallLog(page) {
    page = page || 1;
    try {
        let url = API + '/api/activity/call-log?page=' + page + '&per_page=30';
        if (CalendarState.callLogTrigger) url += '&trigger=' + CalendarState.callLogTrigger;
        const resp = await apiFetch(url);
        const data = await resp.json();
        const calls = data.calls || [];
        const total = data.total || 0;
        const pages = data.pages || 1;

        let h = '<div class="act-filter-bar">';
        h += '<select id="calllog-trigger" onchange="CalendarState.callLogTrigger=this.value;loadCallLog(1)" class="act-filter-select">';
        h += '<option value=""' + (CalendarState.callLogTrigger === '' ? ' selected' : '') + '>All calls</option>';
        h += '<option value="interactive"' + (CalendarState.callLogTrigger === 'interactive' ? ' selected' : '') + '>Interactive</option>';
        h += '<option value="proactive"' + (CalendarState.callLogTrigger === 'proactive' ? ' selected' : '') + '>Proactive</option>';
        h += '</select>';
        h += '<span class="act-filter-count">' + total + ' total calls</span>';
        h += '</div>';

        h += '<table class="activity-table"><thead><tr><th>Time</th><th>Trigger</th><th>Source</th><th>Model</th><th>Tokens In</th><th>Tokens Out</th><th>Cost</th><th>Latency</th><th>Status</th></tr></thead><tbody>';
        if (!calls.length) h += '<tr><td colspan="9" class="act-empty">No LLM calls recorded yet</td></tr>';
        for (const c of calls) {
            const ts = c.timestamp ? formatDate(c.timestamp, 'datetime') : '—';
            const trigCls = c.trigger === 'proactive' ? 'act-trigger-proactive' : 'act-trigger-interactive';
            const trigLabel = c.trigger === 'proactive' ? 'proactive' : 'interactive';
            const statusIcon = c.success ? '<span class="activity-status-ok">&#10003;</span>' : '<span class="activity-status-err" title="' + _esc(c.error || '') + '">&#10007;</span>';
            const shortModel = (c.model || '—').replace('claude-', '').replace('gemini-', 'gem-').replace('-preview', '');
            h += '<tr>'
                + '<td class="act-td-nowrap">' + ts + '</td>'
                + '<td><span class="act-trigger ' + trigCls + '">' + trigLabel + '</span></td>'
                + '<td class="act-td-small">' + _esc(c.source || '—') + '</td>'
                + '<td class="act-td-small" title="' + _esc(c.model || '') + '">' + _esc(shortModel) + '</td>'
                + '<td>' + _fmtTokens(c.tokens_in || 0) + '</td>'
                + '<td>' + _fmtTokens(c.tokens_out || 0) + '</td>'
                + '<td>$' + (c.cost_usd || 0).toFixed(4) + '</td>'
                + '<td>' + ((c.latency_ms || 0) / 1000).toFixed(1) + 's</td>'
                + '<td>' + statusIcon + '</td>'
                + '</tr>';
        }
        h += '</tbody></table>';

        // Pagination
        if (pages > 1) {
            h += '<div class="act-pagination">';
            h += '<button class="btn btn-secondary" onclick="loadCallLog(' + Math.max(1, page - 1) + ')"' + (page <= 1 ? ' disabled' : '') + '>&laquo; Prev</button>';
            h += '<span class="act-pagination-info">Page ' + page + ' / ' + pages + '</span>';
            h += '<button class="btn btn-secondary" onclick="loadCallLog(' + Math.min(pages, page + 1) + ')"' + (page >= pages ? ' disabled' : '') + '>Next &raquo;</button>';
            h += '</div>';
        }

        document.getElementById('atab-calllog').innerHTML = h;
    } catch(e) {
        document.getElementById('atab-calllog').innerHTML = '<div class="act-empty">Could not load call log.</div>';
    }
}

function renderActivityMetrics(data) {
    // Tools table
    const tools = data.tools || [];
    const maxCalls = Math.max(1, ...tools.map(t => t.total_calls));
    let th = '<table class="activity-table"><thead><tr><th>Tool</th><th>Calls</th><th>Success</th><th>Avg ms</th><th>P95 ms</th><th>Trend</th><th>Timeouts</th><th>Last Error</th></tr></thead><tbody>';
    if (!tools.length) th += '<tr><td colspan="8" class="act-empty">No tool calls yet</td></tr>';
    for (const t of tools) {
        const barW = Math.round((t.total_calls / maxCalls) * 80);
        th += '<tr>'
            + '<td>' + _esc(t.tool_name) + '</td>'
            + '<td>' + t.total_calls + ' <span class="activity-bar" style="width:' + barW + 'px"></span></td>'
            + '<td class="' + _rateClass(t.success_rate) + '">' + (t.success_rate * 100).toFixed(1) + '%</td>'
            + '<td>' + t.avg_duration_ms.toFixed(0) + '</td>'
            + '<td>' + t.p95_duration_ms.toFixed(0) + '</td>'
            + '<td>' + _sparkline(t.recent_durations) + '</td>'
            + '<td>' + (t.timeout_count || 0) + '</td>'
            + '<td class="act-td-ellipsis" title="' + _esc(t.last_error || '') + '">' + _esc(t.last_error || '—') + '</td>'
            + '</tr>';
    }
    th += '</tbody></table>';
    document.getElementById('atab-tools').innerHTML = th;

    // LLM table
    const llm = data.llm_providers || [];
    let lh = '<table class="activity-table"><thead><tr><th>Provider / Model</th><th>Calls</th><th>Success</th><th>Avg ms</th><th>P95 ms</th><th>Trend</th><th>Tool Use</th><th>Tokens In</th><th>Tokens Out</th><th>Cost</th><th>Rate Limits</th><th>Last Error</th></tr></thead><tbody>';
    if (!llm.length) lh += '<tr><td colspan="12" class="act-empty">No LLM calls yet</td></tr>';
    for (const m of llm) {
        const mTcr = m.tool_call_rate != null ? (m.tool_call_rate * 100).toFixed(0) + '%' : '—';
        const mRetries = m.empty_tool_retries > 0 ? ' <span class="activity-badge amber">' + m.empty_tool_retries + ' retries</span>' : '';
        lh += '<tr>'
            + '<td>' + _esc(m.provider) + ' / ' + _esc(m.model) + '</td>'
            + '<td>' + m.total_calls + '</td>'
            + '<td class="' + _rateClass(m.success_rate) + '">' + (m.success_rate * 100).toFixed(1) + '%</td>'
            + '<td>' + m.avg_duration_ms.toFixed(0) + '</td>'
            + '<td>' + m.p95_duration_ms.toFixed(0) + '</td>'
            + '<td>' + _sparkline(m.recent_durations) + '</td>'
            + '<td>' + mTcr + mRetries + '</td>'
            + '<td>' + _fmtTokens(m.total_prompt_tokens) + '</td>'
            + '<td>' + _fmtTokens(m.total_completion_tokens) + '</td>'
            + '<td>$' + m.total_cost_usd.toFixed(4) + '</td>'
            + '<td>' + (m.rate_limit_errors || 0) + '</td>'
            + '<td class="act-td-ellipsis" title="' + _esc(m.last_error || '') + '">' + _esc(m.last_error || '—') + '</td>'
            + '</tr>';
    }
    lh += '</tbody></table>';
    document.getElementById('atab-llm').innerHTML = lh;

    // Alerts
    const alerts = data.alerts || [];
    const alertBadge = document.getElementById('activity-alert-badge');
    if (alerts.length) {
        alertBadge.className = 'activity-badge amber';
        alertBadge.textContent = alerts.length;
    } else {
        alertBadge.className = '';
        alertBadge.textContent = '';
    }
    let ah = '';
    if (!alerts.length) ah = '<p class="act-empty">No active alerts</p>';
    for (const a of alerts) {
        const sev = (a.severity || 'warning').toLowerCase();
        const sevCls = sev === 'critical' ? 'act-alert-critical' : sev === 'warning' ? 'act-alert-warning' : '';
        const sevColor = sev === 'critical' ? 'var(--error)' : sev === 'warning' ? 'var(--warning)' : 'var(--text-dim)';
        ah += '<div class="act-alert ' + sevCls + '">'
            + '<div class="act-alert-title">' + _esc(a.name || a.metric_name || 'Alert') + ' <span class="act-alert-sev" style="color:' + sevColor + '">' + _esc(sev) + '</span></div>'
            + '<div class="act-alert-msg">' + _esc(a.message || '') + '</div>'
            + '<div class="act-alert-threshold">Threshold: ' + (a.threshold ?? '—') + ' | Current: ' + (a.current_value ?? '—') + '</div>'
            + '</div>';
    }
    document.getElementById('atab-alerts').innerHTML = ah;
}

function renderActivityErrors(data) {
    const errors = data.errors || [];
    const badge = document.getElementById('activity-error-badge');
    if (errors.length) {
        badge.className = 'activity-badge';
        badge.textContent = errors.length;
    } else {
        badge.className = '';
        badge.textContent = '';
    }
    let h = '<table class="activity-table"><thead><tr><th>Time</th><th>Request</th><th>Skill</th><th>Channel</th><th>Model</th><th>Latency</th><th>Error</th></tr></thead><tbody>';
    if (!errors.length) h += '<tr><td colspan="7" class="act-empty">No errors recorded</td></tr>';
    for (const e of errors) {
        const ts = e.timestamp ? formatDate(e.timestamp, 'datetime') : '—';
        const rid = e.request_id ? '<code class="act-rid">' + _esc(e.request_id.slice(0,8)) + '</code>' : '—';
        h += '<tr>'
            + '<td class="act-td-nowrap">' + ts + '</td>'
            + '<td>' + rid + '</td>'
            + '<td>' + _esc(e.skill || '—') + '</td>'
            + '<td>' + _esc(e.channel || '—') + '</td>'
            + '<td>' + _esc(e.model || '—') + '</td>'
            + '<td>' + (e.latency_ms || '—') + '</td>'
            + '<td class="act-td-error" title="' + _esc(e.error_message || '') + '">' + _esc(e.error_message || '—') + '</td>'
            + '</tr>';
    }
    h += '</tbody></table>';
    document.getElementById('atab-errors').innerHTML = h;
}

// ── Sparkline renderer ──────────────────────────────
function _sparkline(durations) {
    if (!durations || durations.length < 2) return '';
    const max = Math.max(...durations) || 1;
    const w = durations.length * 4;
    let bars = '';
    for (let i = 0; i < durations.length; i++) {
        const h = Math.max(1, Math.round((durations[i] / max) * 16));
        const x = i * 4;
        bars += '<rect x="' + x + '" y="' + (16 - h) + '" width="3" height="' + h + '" fill="var(--accent)" opacity="0.7"/>';
    }
    return '<svg width="' + w + '" height="16" style="vertical-align:middle;">' + bars + '</svg>';
}

// ── Skills tab ───────────────────────────────────
async function loadSkillCosts() {
    try {
        const resp = await apiFetch('/api/activity/skill-costs');
        const data = await resp.json();
        const skills = data.skills || [];
        let h = '<table class="activity-table"><thead><tr><th>Skill</th><th>Calls</th><th>Cost (USD)</th><th>Avg Latency</th></tr></thead><tbody>';
        if (!skills.length) h += '<tr><td colspan="4" class="act-empty">No skill data yet</td></tr>';
        const maxCalls = Math.max(...skills.map(s => s.calls), 1);
        for (const s of skills) {
            const barW = Math.round((s.calls / maxCalls) * 100);
            h += '<tr>'
                + '<td>' + _esc(s.skill) + '</td>'
                + '<td><div class="activity-bar" style="width:' + barW + '%;">' + s.calls + '</div></td>'
                + '<td>$' + s.cost.toFixed(4) + '</td>'
                + '<td>' + s.avg_latency.toFixed(0) + ' ms</td>'
                + '</tr>';
        }
        h += '</tbody></table>';
        document.getElementById('atab-skills').innerHTML = h;
    } catch(e) {
        document.getElementById('atab-skills').innerHTML = '<div class="act-empty">Could not load skill costs.</div>';
    }
}

// ── Trends tab ───────────────────────────────────
async function loadTrends() {
    try {
        const resp = await apiFetch('/api/activity/timeseries?days=30');
        const data = await resp.json();
        const costs = data.costs || [];
        const tokens = data.tokens || [];
        let h = '<div class="act-chart-wrap">';

        // Cost chart
        h += '<div class="act-chart-col"><h4 class="act-chart-title">Daily Cost (USD)</h4>';
        if (costs.length) {
            const maxCost = Math.max(...costs.map(c => c[1]), 0.001);
            h += '<div class="act-chart-bars">';
            for (const [date, cost] of costs) {
                const pct = Math.max(2, Math.round((cost / maxCost) * 100));
                const label = date.slice(5);
                h += '<div title="' + label + ': $' + cost.toFixed(4) + '" class="act-chart-bar act-chart-bar-accent" style="height:' + pct + '%;"></div>';
            }
            h += '</div>';
            h += '<div class="act-chart-axis"><span>' + (costs[0] || [''])[0].slice(5) + '</span><span>' + (costs[costs.length-1] || [''])[0].slice(5) + '</span></div>';
        } else {
            h += '<div class="act-empty" style="padding:30px 0;">No cost data</div>';
        }
        h += '</div>';

        // Token chart
        h += '<div class="act-chart-col"><h4 class="act-chart-title">Daily Tokens</h4>';
        if (tokens.length) {
            const maxTok = Math.max(...tokens.map(t => t[1] + t[2]), 1);
            h += '<div class="act-chart-bars">';
            for (const [date, tin, tout] of tokens) {
                const total = tin + tout;
                const pct = Math.max(2, Math.round((total / maxTok) * 100));
                const inPct = total > 0 ? Math.round((tin / total) * 100) : 50;
                const label = date.slice(5);
                h += '<div title="' + label + ': ' + _fmtTokens(tin) + ' in / ' + _fmtTokens(tout) + ' out" class="act-chart-bar act-chart-bar-stacked" style="height:' + pct + '%;">'
                    + '<div style="height:' + (100 - inPct) + '%;background:var(--success);opacity:0.7;"></div>'
                    + '<div style="height:' + inPct + '%;background:var(--accent);opacity:0.7;"></div>'
                    + '</div>';
            }
            h += '</div>';
            h += '<div class="act-chart-axis"><span>' + (tokens[0] || [''])[0].slice(5) + '</span><span>' + (tokens[tokens.length-1] || [''])[0].slice(5) + '</span></div>';
            h += '<div class="act-chart-legend"><span class="act-trigger-interactive">&#9632;</span> input <span class="activity-status-ok">&#9632;</span> output</div>';
        } else {
            h += '<div class="act-empty" style="padding:30px 0;">No token data</div>';
        }
        h += '</div></div>';

        document.getElementById('atab-trends').innerHTML = h;
    } catch(e) {
        document.getElementById('atab-trends').innerHTML = '<div class="act-empty">Could not load trends.</div>';
    }
}

function renderActivityHealth(data) {
    let h = '';

    // Resources — nested: resources.cpu.percent, resources.memory.percent, etc.
    const res = data.resources || {};
    h += '<h4 class="act-section-title" style="margin-top:0;">Resources</h4>';
    const cpu = res.cpu || {};
    const mem = res.memory || {};
    const disk = res.disk || {};
    const proc = res.process || {};
    const bars = [
        ['CPU', cpu.percent],
        ['Memory (' + (mem.used_mb ? mem.used_mb.toFixed(0) + ' MB used' : '') + ')', mem.percent],
        ['Disk (' + (disk.used_gb ? disk.used_gb.toFixed(1) + ' / ' + ((disk.used_gb || 0) + (disk.free_gb || 0)).toFixed(1) + ' GB' : '') + ')', disk.percent],
    ];
    for (const [label, pct] of bars) {
        const p = pct != null ? pct : 0;
        h += '<div class="act-resource-row">'
            + '<div class="act-resource-label"><span>' + label + '</span><span class="' + (p > 90 ? 'activity-status-err' : p > 70 ? 'activity-status-warn' : 'activity-status-ok') + '">' + p.toFixed(1) + '%</span></div>'
            + '<div class="activity-progress-bar"><div class="activity-progress-fill" style="width:' + Math.min(p, 100) + '%;background:' + _progressColor(p) + ';"></div></div>'
            + '</div>';
    }
    if (proc.thread_count != null) {
        h += '<div class="act-resource-meta">Threads: ' + proc.thread_count + (proc.open_files != null ? ' | Open files: ' + proc.open_files : '') + '</div>';
    }

    // Circuit Breakers
    const circuits = data.circuits || {};
    const circuitNames = Object.keys(circuits);
    if (circuitNames.length) {
        h += '<h4 class="act-section-title">Circuit Breakers</h4>';
        for (const name of circuitNames) {
            const cb = circuits[name];
            const state = (cb.state || 'closed').toLowerCase();
            const stateClass = state === 'closed' ? 'activity-status-ok' : state === 'half_open' ? 'activity-status-warn' : 'activity-status-err';
            h += '<div class="activity-dep-row">'
                + '<div class="dep-dot" style="background:' + (state === 'closed' ? 'var(--success)' : state === 'half_open' ? 'var(--warning)' : 'var(--error)') + ';"></div>'
                + '<span class="act-dep-name">' + _esc(name) + '</span>'
                + '<span class="act-dep-status ' + stateClass + '">' + state + '</span>'
                + '<span class="act-cb-failures">failures: ' + (cb.failure_count || 0) + '</span>'
                + '</div>';
        }
    }

    // Provider Routing
    const routing = data.routing || {};
    const activeProvider = (routing.active_provider || '').toLowerCase();
    const fallbackChain = routing.fallback_chain || [];
    const rateLimits = data.rate_limits || {};
    if (routing.active_provider) {
        h += '<h4 class="act-section-title">Provider Routing</h4>';
        h += '<div class="act-routing-info">'
            + '<span class="act-routing-active">Active:</span> ' + _esc(routing.active_provider)
            + (routing.active_model ? ' <span class="act-routing-model">(' + _esc(routing.active_model) + ')</span>' : '')
            + '</div>';
        if (fallbackChain.length) {
            h += '<div class="act-routing-chain">'
                + 'Fallback chain: ' + fallbackChain.map(f => _esc(f)).join(' → ')
                + '</div>';
        }
    }

    // Dependencies — split into LLM providers vs infrastructure
    const deps = data.dependencies || [];
    const llmDeps = deps.filter(d => d.type === 'llm_provider');
    const infraDeps = deps.filter(d => d.type !== 'llm_provider');

    if (llmDeps.length) {
        h += '<h4 class="act-section-title">LLM Providers</h4>';
        for (const d of llmDeps) {
            const ok = d.healthy;
            const isActive = activeProvider && d.name.toLowerCase().includes(activeProvider.toLowerCase().split(' ')[0]);
            const isFallback = fallbackChain.some(f => d.name.toLowerCase().includes(f.split('/')[0].split('-')[0]));
            // Determine status label
            let statusLabel, statusColor;
            if (ok) {
                statusLabel = isActive ? 'ACTIVE' : isFallback ? 'FALLBACK (READY)' : 'READY';
                statusColor = isActive ? 'var(--success)' : 'var(--text-dim)';
            } else if (d.message && d.message.includes('401')) {
                statusLabel = 'INVALID API KEY';
                statusColor = 'var(--warning)';
            } else if (d.message && d.message.includes('not installed')) {
                statusLabel = 'NOT INSTALLED';
                statusColor = 'var(--text-dim)';
            } else {
                statusLabel = 'UNREACHABLE';
                statusColor = 'var(--error)';
            }
            // Check rate limit status for this provider
            const rl = rateLimits[d.name.toLowerCase()] || {};
            if (rl.is_rate_limited) {
                statusLabel = 'RATE LIMITED';
                statusColor = 'var(--error)';
            }
            let rlBadge = '';
            if (rl.events_in_window > 0) {
                const rlColor = rl.is_rate_limited ? 'var(--error)' : 'var(--warning)';
                rlBadge = '<span class="act-rate-badge" style="color:' + rlColor + ';" title="' + rl.events_in_window + ' rate limit hit(s) in ' + (rl.window_seconds || 300) + 's window (threshold: ' + (rl.threshold || 2) + ')">'
                    + (rl.is_rate_limited ? '&#x26A0; ' : '') + rl.events_in_window + '/' + (rl.threshold || 2) + ' rate limits</span>';
            }
            const dotColor = rl.is_rate_limited ? 'var(--error)' : ok ? 'var(--success)' : d.message && d.message.includes('401') ? 'var(--warning)' : 'var(--error)';
            h += '<div class="activity-dep-row">'
                + '<div class="dep-dot" style="background:' + dotColor + ';"></div>'
                + '<span class="' + (isActive ? 'act-dep-name-active' : 'act-dep-name') + '">' + _esc(d.name) + '</span>'
                + '<span class="act-dep-status" style="color:' + statusColor + ';">' + statusLabel + '</span>'
                + rlBadge
                + (d.latency_ms != null && ok ? '<span class="act-dep-latency">' + d.latency_ms.toFixed(0) + ' ms</span>' : '')
                + '</div>';
        }
    }

    if (infraDeps.length) {
        h += '<h4 class="act-section-title">Infrastructure</h4>';
        for (const d of infraDeps) {
            const ok = d.healthy;
            h += '<div class="activity-dep-row">'
                + '<div class="dep-dot" style="background:' + (ok ? 'var(--success)' : 'var(--error)') + ';"></div>'
                + '<span class="act-dep-name">' + _esc(d.name) + '</span>'
                + '<span class="act-dep-latency">' + (d.latency_ms != null ? d.latency_ms.toFixed(0) + ' ms' : '') + '</span>'
                + (d.message && !ok ? '<span class="act-dep-error">' + _esc(d.message) + '</span>' : '')
                + '</div>';
        }
    }

    // Uptime
    if (data.uptime_seconds) {
        const mins = Math.floor(data.uptime_seconds / 60);
        const hrs = Math.floor(mins / 60);
        const upStr = hrs > 0 ? hrs + 'h ' + (mins % 60) + 'm' : mins + 'm';
        h += '<div class="act-uptime">Uptime: ' + upStr + ' | Status: <span class="' + (data.status === 'healthy' ? 'activity-status-ok' : 'activity-status-warn') + '">' + _esc(data.status || 'unknown') + '</span></div>';
    }

    document.getElementById('atab-health').innerHTML = h;
}

function _esc(s) {
    if (!s) return '';
    const d = document.createElement('div');
    d.textContent = s;
    return d.innerHTML;
}

// _formatBriefingTime removed — use formatDate(str, 'time')

function renderBriefingEvent(ev) {
    let timeStr = ev.all_day ? 'All day' : formatDate(ev.start, 'time');
    if (!ev.all_day && ev.end) {
        timeStr += ' – ' + formatDate(ev.end, 'time');
    }
    const summary = escapeHtml(ev.summary || '(No title)');
    const evId = 'bev-' + Math.random().toString(36).slice(2, 8);

    let html = '<div class="briefing-event" id="' + evId + '">';
    html += '<div class="briefing-event-time">' + escapeHtml(timeStr) + '</div>';
    html += '<div class="briefing-event-title">' + summary + '</div>';

    // Meta line: location + attendee count
    let meta = '';
    if (ev.location) meta += '<span>📍 ' + escapeHtml(ev.location) + '</span>';
    const attendees = ev.attendees || [];
    if (attendees.length) meta += '<span>👥 ' + attendees.length + ' attendee' + (attendees.length !== 1 ? 's' : '') + '</span>';
    if (meta) html += '<div class="briefing-event-meta">' + meta + '</div>';

    // Action buttons if attendees exist
    if (attendees.length) {
        const evData = escapeHtml(JSON.stringify(ev));
        html += '<div class="briefing-event-actions">';
        html += '<button class="briefing-action-btn" data-action="running_late" data-ev="' + evData + '" data-evid="' + evId + '">Running late</button>';
        html += '<button class="briefing-action-btn" data-action="cancel" data-ev="' + evData + '" data-evid="' + evId + '">Cancel</button>';
        html += '</div>';
    }

    html += '</div>';
    return html;
}

function renderBriefingSection(title, innerHtml, count) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    let badgeHtml = count ? '<span class="badge">' + count + '</span>' : '';
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> ' + escapeHtml(title) + badgeHtml + '</div>';
    html += '<div class="briefing-section-body" id="' + secId + '">' + innerHtml + '</div>';
    html += '</div>';
    return html;
}

function renderBriefingEmailSection(text) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    const lines = text.split('\n').filter(l => l.trim());
    const header = lines[0] || '';
    const rows = [];
    let maxCount = 0;
    for (let i = 1; i < lines.length; i++) {
        const m = lines[i].match(/^(.*?):\s*(\d+)$/);
        if (m) {
            const count = parseInt(m[2]);
            rows.push({cat: m[1].trim(), count});
            if (count > maxCount) maxCount = count;
        }
    }
    let inner = `<div class="em-summary" style="font-size:0.82rem; margin-bottom:10px;">${escapeHtml(header)}</div>`;
    if (rows.length) {
        inner += '<div class="em-briefing-grid">';
        rows.forEach(r => {
            const pct = maxCount > 0 ? Math.round((r.count / maxCount) * 100) : 0;
            const isUrgent = r.cat.toLowerCase().includes('urgent');
            const fillCls = isUrgent ? ' em-bar-urgent' : '';
            inner += `<span class="em-briefing-label" title="${escapeHtml(r.cat)}">${escapeHtml(r.cat)}</span>`;
            inner += `<div class="em-briefing-bar"><div class="em-briefing-fill${fillCls}" style="width:${pct}%; background:${isUrgent ? 'var(--email-urgent)' : 'var(--accent)'}"></div></div>`;
            inner += `<span class="em-briefing-count">${r.count}</span>`;
        });
        inner += '</div>';
    }
    let hdrExtra = '<span class="link-btn" onclick="event.stopPropagation(); switchPanel(\'email\')">View →</span>';
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> Email' + hdrExtra + '</div>';
    html += '<div class="briefing-section-body" id="' + secId + '">' + inner + '</div>';
    html += '</div>';
    return html;
}

function renderBriefingTextSection(title, text, linkPanel) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    let hdrExtra = '';
    if (linkPanel) {
        hdrExtra = '<span class="link-btn" onclick="event.stopPropagation(); switchPanel(\'' + escapeHtml(linkPanel) + '\')">View →</span>';
    }
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> ' + escapeHtml(title) + hdrExtra + '</div>';
    let safeText = escapeHtml(text).replace(/\n/g, '<br>');
    html += '<div class="briefing-section-body" id="' + secId + '"><div class="briefing-text-content">' + safeText + '</div></div>';
    html += '</div>';
    return html;
}

function renderBriefingWeatherSection(forecasts) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
    let inner = '';
    forecasts.forEach((loc, li) => {
        if (li > 0) inner += '<div style="margin-top:14px;border-top:1px solid var(--border);padding-top:10px;"></div>';
        inner += `<div style="font-size:0.78rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px;">${escapeHtml(loc.name)}</div>`;
        inner += '<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:6px;text-align:center;">';
        (loc.days || []).slice(0, 5).forEach(d => {
            const dt = new Date(d.date + 'T12:00:00');
            const dayLabel = days[dt.getDay()];
            const hi = d.high != null ? Math.round(d.high) + d.unit : '–';
            const lo = d.low != null ? Math.round(d.low) + d.unit : '–';
            const rain = d.rain_pct != null ? `<div style="font-size:0.7rem;color:var(--email-info);margin-top:2px;">💧${d.rain_pct}%</div>` : '';
            inner += `<div style="background:var(--bg-input);border-radius:8px;padding:8px 4px;">`;
            inner += `<div style="font-size:0.75rem;color:var(--text-dim);font-weight:600;">${dayLabel}</div>`;
            inner += `<div style="font-size:1.4rem;line-height:1.2;margin:4px 0;">${d.emoji}</div>`;
            inner += `<div style="font-size:0.78rem;font-weight:600;">${hi}</div>`;
            inner += `<div style="font-size:0.73rem;color:var(--text-dim);">${lo}</div>`;
            inner += rain;
            inner += `</div>`;
        });
        inner += '</div>';
    });
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> <i class="fas fa-cloud-sun" style="margin-right:5px;opacity:0.7;"></i>Weather</div>';
    html += '<div class="briefing-section-body" id="' + secId + '">' + inner + '</div>';
    html += '</div>';
    return html;
}

function renderBriefingNewsSection(news) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    let inner = '';

    const top = news.top || [];
    if (top.length) {
        inner += '<div style="font-size:0.78rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:6px;">Top Headlines</div>';
        top.forEach((item, i) => {
            const src = item.source ? `<span style="color:var(--accent);margin-left:4px;">${escapeHtml(item.source)}</span>` : '';
            const link = item.url ? `href="${escapeHtml(item.url)}" target="_blank" rel="noopener"` : '';
            inner += `<div style="padding:5px 0;${i < top.length-1 ? 'border-bottom:1px solid var(--border);' : ''}">`;
            inner += `<a ${link} style="color:var(--text-primary);text-decoration:none;font-size:0.88rem;font-weight:500;line-height:1.3;">${escapeHtml(item.title)}</a>${src}`;
            inner += `</div>`;
        });
    }

    const topics = news.topics || {};
    const topicKeys = Object.keys(topics);
    if (topicKeys.length) {
        inner += '<div style="margin-top:10px;">';
        topicKeys.forEach(topic => {
            inner += `<div style="font-size:0.78rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;margin:8px 0 4px;">${escapeHtml(topic)}</div>`;
            (topics[topic] || []).forEach(item => {
                const link = item.url ? `href="${escapeHtml(item.url)}" target="_blank" rel="noopener"` : '';
                inner += `<div style="padding:3px 0;font-size:0.85rem;"><a ${link} style="color:var(--text-secondary);text-decoration:none;">${escapeHtml(item.title)}</a></div>`;
            });
        });
        inner += '</div>';
    }

    if (!inner) return '';
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> <i class="fas fa-newspaper" style="margin-right:5px;opacity:0.7;"></i>News</div>';
    html += '<div class="briefing-section-body" id="' + secId + '">' + inner + '</div>';
    html += '</div>';
    return html;
}

function renderBriefingPodcastSection(podcasts) {
    const secId = 'bsec-' + Math.random().toString(36).slice(2, 8);
    let inner = '';
    podcasts.forEach(pod => {
        inner += `<div style="margin-bottom:10px;">`;
        inner += `<div style="font-weight:600;font-size:0.88rem;margin-bottom:4px;">${escapeHtml(pod.name)}</div>`;
        (pod.new_episodes || []).forEach(ep => {
            const dur = ep.duration ? `<span style="color:var(--text-dim);font-size:0.78rem;margin-left:6px;">${escapeHtml(ep.duration)}</span>` : '';
            inner += `<div style="font-size:0.83rem;color:var(--text-secondary);padding:2px 0 2px 10px;">▸ ${escapeHtml(ep.title)}${dur}</div>`;
        });
        inner += `</div>`;
    });
    let html = '<div class="briefing-section">';
    html += '<div class="briefing-section-hdr" onclick="toggleBriefingSection(\'' + secId + '\', this)">';
    html += '<span class="toggle">▼</span> <i class="fas fa-headphones" style="margin-right:5px;opacity:0.7;"></i>New Episodes<span class="badge">' + podcasts.length + '</span></div>';
    html += '<div class="briefing-section-body" id="' + secId + '">' + inner + '</div>';
    html += '</div>';
    return html;
}

function toggleBriefingSection(secId, hdr) {
    const body = document.getElementById(secId);
    if (!body) return;
    const hidden = body.classList.toggle('hidden');
    if (hdr) hdr.classList.toggle('collapsed', hidden);
}

// Delegated click handler for briefing action buttons
document.addEventListener('click', function(e) {
    const btn = e.target.closest('.briefing-action-btn[data-action]');
    if (!btn) return;
    briefingAction(btn.dataset.action, btn.dataset.ev, btn.dataset.evid);
});

async function briefingAction(action, evJson, evId) {
    let ev;
    try { ev = JSON.parse(evJson); } catch (e) { return; }

    const container = document.getElementById(evId);
    if (!container) return;
    const btns = container.querySelectorAll('.briefing-action-btn');
    btns.forEach(b => b.disabled = true);

    try {
        const res = await apiFetch(API + '/api/briefing/action', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                action: action,
                event_summary: ev.summary || '',
                attendees: ev.attendees || [],
                event_time: ev.start || ''
            })
        });
        const data = await res.json();
        // Show inline confirmation
        let msgDiv = container.querySelector('.briefing-action-result');
        if (!msgDiv) {
            msgDiv = document.createElement('div');
            msgDiv.className = 'briefing-action-result';
            msgDiv.style.cssText = 'font-size:0.82rem; color:var(--success, #a6e3a1); margin-top:4px;';
            container.appendChild(msgDiv);
        }
        msgDiv.textContent = data.response || 'Done';
        btns.forEach(b => { if (b.textContent.toLowerCase().includes(action.replace('_', ' '))) b.classList.add('sent'); });
    } catch (e) {
        btns.forEach(b => b.disabled = false);
    }
}

async function loadBriefing() {
    const container = document.getElementById('briefing-content');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    try {
        const res = await apiFetch(API + '/api/briefing');
        const data = await res.json();

        let html = '';

        // LLM summary card at top
        if (data.summary) {
            let summaryHtml = escapeHtml(data.summary).replace(/\n/g, '<br>');
            html += '<div class="briefing-summary">' + summaryHtml + '</div>';
        }

        // Calendar section — structured cards with action buttons
        const events = (data.sections && data.sections.calendar) || [];
        if (events.length) {
            html += renderBriefingSection('Schedule', events.map(function(ev) {
                return renderBriefingEvent(ev);
            }).join(''), events.length);
        }

        // Tasks section
        if (data.sections && data.sections.tasks) {
            html += renderBriefingTextSection('Tasks', data.sections.tasks);
        }

        // Email section — rendered as CSS bar chart
        if (data.sections && data.sections.email) {
            html += renderBriefingEmailSection(data.sections.email);
        } else if (data.sections && data.sections.email_error) {
            const eErr = data.sections.email_error;
            if (eErr.includes('authorization') || eErr.includes('🔑')) {
                html += '<div style="font-size:0.85rem; margin-bottom:8px; padding:10px 14px; background:rgba(249,226,175,0.08); border-radius:6px; text-align:center;">'
                     + '<i class="fas fa-envelope" style="margin-right:5px; color:var(--email-action);"></i>'
                     + 'Gmail needs authorization — '
                     + '<a style="color:var(--email-action);text-decoration:underline;cursor:pointer;font-weight:600;" onclick="startGoogleOAuth(\'gmail\')">Connect Gmail</a></div>';
            } else {
                html += '<div style="font-size:0.8rem; color:var(--email-urgent); margin-bottom:8px; padding:6px 10px; background:rgba(243,139,168,0.08); border-radius:6px;">'
                     + '<i class="fas fa-exclamation-circle" style="margin-right:5px;"></i>'
                     + escapeHtml(eErr)
                     + ' — <a style="color:inherit;text-decoration:underline;cursor:pointer;" onclick="loadPanel(\'email\')">check email settings</a></div>';
            }
        }

        // Grants section (nonprofit_director)
        if (data.sections && data.sections.grants) {
            html += renderBriefingTextSection('Grant Deadlines', data.sections.grants);
        }

        // Donors section (nonprofit_director)
        if (data.sections && data.sections.donors) {
            html += renderBriefingTextSection('Donor Follow-ups', data.sections.donors);
        }

        // Chef — daily prep
        if (data.sections && data.sections.prep) {
            html += renderBriefingTextSection('Kitchen Prep', data.sections.prep);
        }

        // Artist — upcoming deadlines
        if (data.sections && data.sections.deadlines) {
            html += renderBriefingTextSection('Upcoming Deadlines', data.sections.deadlines);
        }

        // Social Worker — flagged cases
        if (data.sections && data.sections.cases) {
            html += renderBriefingTextSection('Flagged Cases', data.sections.cases);
        }

        // Business Buddy — daily snapshot
        if (data.sections && data.sections.snapshot) {
            html += renderBriefingTextSection("Today's Snapshot", data.sections.snapshot);
        }

        // Weather forecast
        if (data.sections && data.sections.weather && data.sections.weather.length) {
            html += renderBriefingWeatherSection(data.sections.weather);
        }

        // News brief
        if (data.sections && data.sections.news && (
            (data.sections.news.top && data.sections.news.top.length) ||
            Object.keys(data.sections.news.topics || {}).length
        )) {
            html += renderBriefingNewsSection(data.sections.news);
        }

        // New podcast episodes
        if (data.sections && data.sections.podcasts && data.sections.podcasts.length) {
            html += renderBriefingPodcastSection(data.sections.podcasts);
        }

        // Timestamp
        if (data.timestamp) {
            html += '<div class="briefing-timestamp">Generated ' + formatDate(data.timestamp, 'time') + '</div>';
        }

        // Fallback if nothing rendered
        if (!html) {
            html = '<p>' + escapeHtml(data.raw || 'No briefing available') + '</p>';
        }

        container.innerHTML = html;
    } catch (e) {
        container.innerHTML = '<p>Error loading briefing</p>';
    }
}

async function forceRefreshBriefing() {
    const container = document.getElementById('briefing-content');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>'
        + '<p style="text-align:center;font-size:0.85rem;color:var(--text-secondary);">'
        + 'Generating fresh briefing… this may take a moment on local models.</p>';
    try {
        const res = await apiFetch(API + '/api/briefing/refresh', {method: 'POST'});
        const data = await res.json();
        if (data.ok) {
            await loadBriefing();
        } else {
            container.innerHTML = '<p>Briefing refresh failed: '
                + escapeHtml(data.error || 'Unknown error') + '</p>';
        }
    } catch (e) {
        container.innerHTML = '<p>Error refreshing briefing</p>';
    }
}

// ============================================================


// ── Email Intelligence badge + urgent toast ──────────────────────────

async function updateEmailBadge() {
    try {
        const res = await apiFetch(API + '/api/email/monitor/stats');
        const stats = await res.json();
        const badge = document.getElementById('em-badge');
        if (!badge) return;

        const urgent = stats.urgent || 0;
        const action = stats.action || 0;
        const total = urgent + action;

        if (total > 0) {
            badge.textContent = total > 99 ? '99+' : total;
            badge.style.display = '';
            badge.style.background = urgent > 0 ? 'var(--error, #e74c3c)' : 'var(--warning, #f39c12)';
        } else {
            badge.style.display = 'none';
        }
    } catch(e) {}
}

// Run badge update on load and periodically
if (typeof _SERVER !== 'undefined') {
    setTimeout(updateEmailBadge, 5000);
    setInterval(updateEmailBadge, 60000);
}
