// email.js — Email triage, inbox, keyboard shortcuts, feedback (EmailState)
// Extracted from dashboard.html (lines 5585-6447)

// EmailState.triageEmails / .collapsedCategories / .currentDetail / .activeTab / .sentLoaded (namespaced above)
// EmailState.accountFilter — selected account email or '' for all accounts
// EmailState.allSortedEmails — unfiltered cache for client-side account filtering

if (typeof EmailState !== 'undefined') {
    EmailState.accountFilter = EmailState.accountFilter || '';
    EmailState.allSortedEmails = EmailState.allSortedEmails || [];
    EmailState.sortedUpdatedAt = EmailState.sortedUpdatedAt || null;
}

// formatDate — defined in core.js

function switchEmailTab(tab) {
    EmailState.activeTab = tab;
    document.getElementById('email-tab-inbox').classList.toggle('active', tab === 'inbox');
    document.getElementById('email-tab-sorted').classList.toggle('active', tab === 'sorted');
    document.getElementById('email-tab-sent').classList.toggle('active', tab === 'sent');
    document.getElementById('email-content').style.display = tab === 'inbox' ? 'block' : 'none';
    document.getElementById('email-sorted-content').style.display = tab === 'sorted' ? 'block' : 'none';
    document.getElementById('email-sent-content').style.display = tab === 'sent' ? 'block' : 'none';
    if (tab === 'sent' && !EmailState.sentLoaded) loadEmailSent();
    if (tab === 'sorted') loadSmartSort();
}

function emailRefreshActive() {
    if (EmailState.activeTab === 'inbox') loadEmailTriage(true);
    else if (EmailState.activeTab === 'sorted') loadSmartSort();
    else { EmailState.sentLoaded = false; loadEmailSent(); }
}

async function loadEmailSent() {
    EmailState.sentLoaded = true;
    const container = document.getElementById('email-sent-content');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/email/sent?limit=25&days_back=30');
        const data = await res.json();
        if (data.error) {
            container.innerHTML = '<div class="card"><p style="color:var(--error);">' + escapeHtml(data.error) + '</p></div>';
            return;
        }
        renderSentEmails(data.emails || []);
    } catch (e) {
        container.innerHTML = '<div class="card"><p>Error loading sent mail. Make sure email is configured.</p></div>';
    }
}

function renderSentEmails(emails) {
    const container = document.getElementById('email-sent-content');
    if (!emails.length) {
        container.innerHTML = '<div class="card"><p class="em-summary">No sent mail found in the last 30 days.</p></div>';
        return;
    }

    let html = `<div class="em-summary">📤 ${emails.length} sent message${emails.length !== 1 ? 's' : ''}</div>`;
    html += '<div class="card" style="padding:0; overflow:hidden;" role="grid" aria-label="Sent emails">';
    emails.forEach((em, i) => {
        const toDisplay = escapeHtml(em.to_name || em.to_email || 'Unknown');
        const dateDisplay = formatDate(em.date);
        html += `<div class="em-row" role="row" onclick="openSentDetail('${escapeHtml(em.id)}','${escapeHtml(em.id_source || 'gmail')}')">`;
        html += `<div class="em-row-inner" role="gridcell">`;
        html += `<i class="fas fa-reply em-reply-icon"></i>`;
        html += `<span class="em-sender">To: ${toDisplay}</span>`;
        html += `<span class="em-subject">${escapeHtml(em.subject || '(no subject)')}</span>`;
        html += `<span class="em-date">${dateDisplay}</span>`;
        html += `</div>`;
        html += `</div>`;
    });
    html += '</div>';
    container.innerHTML = html;
}

async function openSentDetail(msgId, idSource) {
    // Reuse the same email detail overlay
    EmailState.currentDetail = {id: msgId, id_source: idSource, _is_sent: true};
    const overlay = document.getElementById('email-detail-overlay');
    const content = document.getElementById('email-detail-content');
    const replySection = document.getElementById('email-reply-section');

    overlay.style.display = 'block';
    replySection.style.display = 'none';
    content.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    try {
        const res = await apiFetch(API + '/api/email/read/' + encodeURIComponent(msgId) + '?source=' + encodeURIComponent(idSource));
        const data = await res.json();
        if (data.error) {
            content.innerHTML = '<p class="em-error">' + escapeHtml(data.error) + '</p>';
            return;
        }
        const emailText = data.content || 'No content';
        let html = `<div class="em-sent-badge"><i class="fas fa-paper-plane" style="margin-right:6px;"></i>Sent message</div>`;
        html += `<div class="em-detail-body">${escapeHtml(emailText)}</div>`;
        html += `<div class="em-detail-actions">`;
        html += `<button class="btn btn-secondary" onclick="closeEmailDetail()">Close</button>`;
        html += `</div>`;
        content.innerHTML = html;
    } catch (e) {
        content.innerHTML = '<p class="em-error">Failed to load message.</p>';
    }
}

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

    function _connectPrompt() {
        return '<div class="card em-connect-prompt">'
            + '<i class="fas fa-envelope"></i>'
            + '<p>Gmail needs authorization to access your email.</p>'
            + '<button class="btn" onclick="startGoogleOAuth(\'gmail\')"><i class="fab fa-google" style="margin-right:6px;"></i>Connect Gmail</button>'
            + '<p class="em-hint">Uses the same Google account as your calendar and contacts.</p>'
            + '</div>';
    }

    try {
        const qs = refresh ? '?refresh=true' : '';
        const res = await apiFetch(API + '/api/email/triage' + qs);
        const data = await res.json();

        if (data.action === 'connect_gmail') {
            container.innerHTML = _connectPrompt();
            return;
        }
        if (data.error) {
            let msg = data.message || data.error;
            let fixHtml = '';
            if (data.action === 'connect_gmail') {
                fixHtml = '<button class="btn" onclick="startGoogleOAuth(\'gmail\')" style="margin-top:12px">'
                    + '<i class="fab fa-google" style="margin-right:6px;"></i>Connect Gmail</button>';
            } else if (msg.toLowerCase().includes('imap') || msg.toLowerCase().includes('connection') || msg.toLowerCase().includes('server')) {
                fixHtml = '<button class="btn" onclick="startGoogleOAuth(\'gmail\')" style="margin-top:12px">'
                    + '<i class="fas fa-key" style="margin-right:6px;"></i>Reconnect Gmail</button>'
                    + ' <button class="btn btn-secondary" onclick="loadConnectWizard()" style="margin-top:12px">'
                    + '<i class="fas fa-cog" style="margin-right:6px;"></i>Email Settings</button>';
            }
            container.innerHTML = '<div class="card em-connect-prompt">'
                + '<i class="fas fa-envelope" style="color:var(--error)"></i>'
                + '<p>' + escapeHtml(msg) + '</p>'
                + fixHtml + '</div>';
            return;
        }
        if (data.categories) {
            EmailState.triageEmails = data.emails || [];
            renderEmailTriage(data.categories, data.total || 0);
        } else if (data.triage && data.triage.startsWith('🔑')) {
            container.innerHTML = _connectPrompt();
        } else {
            // Fallback to text
            let html = data.triage || 'No email data';
            html = html.replace(/\n/g, '<br>');
            container.innerHTML = '<div class="card">' + html + '</div>';
        }
    } catch (e) {
        container.innerHTML = '<div class="card em-connect-prompt">'
            + '<i class="fas fa-envelope" style="color:var(--error)"></i>'
            + '<p>Email connection failed.</p>'
            + '<button class="btn" onclick="startGoogleOAuth(\'gmail\')" style="margin-top:12px">'
            + '<i class="fas fa-key" style="margin-right:6px;"></i>Reconnect Gmail</button>'
            + ' <button class="btn btn-secondary" onclick="loadConnectWizard()" style="margin-top:12px">'
            + '<i class="fas fa-cog" style="margin-right:6px;"></i>Email Settings</button>'
            + '</div>';
    }
}

EmailState.density = localStorage.getItem('em-density') || 'comfortable';
// EmailState.focusedIdx / .flatRows (namespaced above)

function _emSetDensity(mode) {
    EmailState.density = mode;
    localStorage.setItem('em-density', mode);
    const container = document.getElementById('email-content');
    if (container) {
        container.classList.remove('em-density-compact','em-density-comfortable','em-density-spacious');
        container.classList.add('em-density-' + mode);
    }
    document.querySelectorAll('.em-density-toggle button').forEach(b => {
        b.classList.toggle('active', b.dataset.density === mode);
    });
}

function _emFocusRow(idx) {
    // Remove old focus
    const old = document.querySelector('.em-row.em-focused');
    if (old) old.classList.remove('em-focused');
    if (idx < 0 || idx >= EmailState.flatRows.length) return;
    EmailState.focusedIdx = idx;
    const row = EmailState.flatRows[idx];
    const el = document.getElementById(row.rowId);
    if (el) {
        el.classList.add('em-focused');
        el.scrollIntoView({block: 'nearest', behavior: 'smooth'});
    }
}

function renderEmailTriage(categories, total) {
    const container = document.getElementById('email-content');
    const priorityEmoji = {urgent: '🔴', action: '🟡', fyi: '🟢'};
    const catOrder = ['Campaign','Trans Pride','GJL Operations','Staff','Board','Coalitions','Donors','Grants','Media','Volunteers','Notifications','Promotional','Other'];

    let urgent = 0, action = 0, promo = 0;
    let calCount = 0;
    EmailState.triageEmails.forEach(e => {
        if (e.priority === 'urgent') urgent++;
        if (e.priority === 'action') action++;
        if (e.category === 'Promotional') promo++;
        if (e.has_calendar_event) calCount++;
    });

    // Update nav badge
    const badgeCount = urgent + action;
    const emBadge = document.getElementById('em-badge');
    if (emBadge) {
        if (badgeCount > 0) { emBadge.textContent = badgeCount; emBadge.style.display = ''; }
        else { emBadge.style.display = 'none'; }
    }

    // Reset keyboard nav
    EmailState.flatRows = [];
    EmailState.focusedIdx = -1;

    let html = '<div class="em-summary">';
    html += `📬 ${total} emails`;
    if (urgent) html += ` • <span class="em-urgent">${urgent} urgent</span>`;
    if (action) html += ` • <span class="em-action">${action} action</span>`;
    if (calCount) html += ` • ${calCount} 📅`;
    if (promo) html += ` • ${promo} promotional`;
    html += '<span class="em-density-toggle">';
    html += '<button data-density="compact" title="Compact"' + (EmailState.density === 'compact' ? ' class="active"' : '') + ' onclick="_emSetDensity(\'compact\')"><i class="fas fa-bars"></i></button>';
    html += '<button data-density="comfortable" title="Comfortable"' + (EmailState.density === 'comfortable' ? ' class="active"' : '') + ' onclick="_emSetDensity(\'comfortable\')"><i class="fas fa-grip-lines"></i></button>';
    html += '<button data-density="spacious" title="Spacious"' + (EmailState.density === 'spacious' ? ' class="active"' : '') + ' onclick="_emSetDensity(\'spacious\')"><i class="fas fa-th-list"></i></button>';
    html += '</span>';
    html += '</div>';

    catOrder.forEach(cat => {
        const emails = categories[cat] || [];
        if (!emails.length) return;

        const collapsed = EmailState.collapsedCategories.has(cat);
        const icon = collapsed ? '▶' : '▼';
        const catCls = cat === 'Promotional' ? ' em-promo' : (cat === 'Notifications' ? ' em-notif' : '');

        html += `<div class="card" style="margin-bottom:8px; padding:0; overflow:hidden;">`;
        html += `<div class="em-category-hdr" onclick="toggleTriageCategory('${cat}')">`;
        html += `<span class="em-category-label${catCls}">${icon} ${cat.toUpperCase()} (${emails.length})</span>`;

        const actionCount = emails.filter(e => e.priority === 'action' || e.priority === 'urgent').length;
        if (actionCount) html += `<span class="em-category-action">${actionCount} need action</span>`;
        html += '</div>';

        if (!collapsed) {
            html += '<div role="grid" aria-label="' + escapeHtml(cat) + ' emails" style="padding:4px 0;">';
            emails.forEach(em => {
                const emoji = priorityEmoji[em.priority] || '⚪';
                const dimCls = cat === 'Promotional' ? ' em-row-dim' : '';

                const triageRowId = 'triage-row-' + (em.id || em.index);
                EmailState.flatRows.push({rowId: triageRowId, id: em.id, id_source: em.id_source || 'gmail'});
                html += `<div id="${triageRowId}" class="em-row${dimCls}" role="row" tabindex="-1" data-em-idx="${EmailState.flatRows.length - 1}" onclick="openEmailDetail('${em.id}', '${em.id_source || 'gmail'}')">`;
                html += `<div class="em-row-inner" role="gridcell">`;
                html += `<span class="em-priority">${emoji}</span>`;
                if (em.has_calendar_event) {
                    html += `<span class="em-priority" style="cursor:pointer" title="Schedule event" onclick="event.stopPropagation(); suggestCalendarFromEmail(${em.index})">📅</span>`;
                }
                html += `<span class="em-sender">${escapeHtml(em.from_name)}</span>`;
                html += `<span class="em-subject">${escapeHtml(em.subject)}</span>`;
                html += `<button class="em-trash" onclick="event.stopPropagation(); wsTrashEmail('${escapeHtml(em.id || '')}','${escapeHtml(em.id_source || 'gmail')}','${triageRowId}')" title="Move to Trash" aria-label="Move to Trash"><i class="fas fa-trash"></i></button>`;
                html += `<span class="em-index">#${em.index}</span>`;
                html += '</div>';
                html += '</div>';
            });
            html += '</div>';
        }
        html += '</div>';
    });

    container.innerHTML = html;
    container.classList.add('em-density-' + EmailState.density);
}

function toggleTriageCategory(cat) {
    if (EmailState.collapsedCategories.has(cat)) {
        EmailState.collapsedCategories.delete(cat);
    } else {
        EmailState.collapsedCategories.add(cat);
    }
    // Re-render from cached data
    const categories = {};
    EmailState.triageEmails.forEach(em => {
        categories[em.category] = categories[em.category] || [];
        categories[em.category].push(em);
    });
    renderEmailTriage(categories, EmailState.triageEmails.length);
}

// ── Email keyboard shortcuts ──
// EmailState.kbdHelpVisible (namespaced above)

function _isEmailPanelActive() {
    const active = document.querySelector('.nav-item.active');
    return active && active.dataset.panel === 'email';
}

function _emShowKbdHelp() {
    let el = document.getElementById('em-kbd-help');
    if (!el) {
        el = document.createElement('div');
        el.id = 'em-kbd-help';
        el.className = 'em-kbd-help';
        el.innerHTML = '<div style="margin-bottom:6px;font-weight:600;color:var(--text)">Keyboard Shortcuts</div>'
            + '<div><kbd>j</kbd> / <kbd>k</kbd> Next / previous email</div>'
            + '<div><kbd>Enter</kbd> Open focused email</div>'
            + '<div><kbd>e</kbd> Trash focused email</div>'
            + '<div><kbd>Esc</kbd> Close detail overlay</div>'
            + '<div><kbd>?</kbd> Toggle this help</div>';
        document.body.appendChild(el);
    }
    EmailState.kbdHelpVisible = !EmailState.kbdHelpVisible;
    el.style.display = EmailState.kbdHelpVisible ? 'block' : 'none';
}

document.addEventListener('keydown', function(e) {
    // Only handle when email panel is active and no input is focused
    if (!_isEmailPanelActive()) return;
    const tag = (e.target.tagName || '').toLowerCase();
    if (tag === 'input' || tag === 'textarea' || tag === 'select' || e.target.isContentEditable) return;
    // Don't interfere with modifier keys
    if (e.ctrlKey || e.metaKey || e.altKey) return;

    // Email detail overlay is open — only Escape applies
    const overlay = document.getElementById('email-detail-overlay');
    if (overlay && overlay.style.display !== 'none') {
        if (e.key === 'Escape') { closeEmailDetail(); e.preventDefault(); }
        return;
    }

    switch (e.key) {
        case 'j': // Next
            _emFocusRow(Math.min(EmailState.focusedIdx + 1, EmailState.flatRows.length - 1));
            e.preventDefault();
            break;
        case 'k': // Previous
            _emFocusRow(Math.max(EmailState.focusedIdx - 1, 0));
            e.preventDefault();
            break;
        case 'Enter': // Open focused
            if (EmailState.focusedIdx >= 0 && EmailState.focusedIdx < EmailState.flatRows.length) {
                const row = EmailState.flatRows[EmailState.focusedIdx];
                openEmailDetail(row.id, row.id_source);
                e.preventDefault();
            }
            break;
        case 'e': // Trash focused
            if (EmailState.focusedIdx >= 0 && EmailState.focusedIdx < EmailState.flatRows.length) {
                const row = EmailState.flatRows[EmailState.focusedIdx];
                wsTrashEmail(row.id, row.id_source, row.rowId);
                // Move focus to next row
                if (EmailState.focusedIdx < EmailState.flatRows.length - 1) _emFocusRow(EmailState.focusedIdx + 1);
                else if (EmailState.focusedIdx > 0) _emFocusRow(EmailState.focusedIdx - 1);
                e.preventDefault();
            }
            break;
        case '?': // Help
            _emShowKbdHelp();
            e.preventDefault();
            break;
        case 'Escape':
            // Clear focus
            EmailState.focusedIdx = -1;
            const old = document.querySelector('.em-row.em-focused');
            if (old) old.classList.remove('em-focused');
            // Hide kbd help
            if (EmailState.kbdHelpVisible) _emShowKbdHelp();
            break;
    }
});

async function openEmailDetail(msgId, idSource) {
    EmailState.currentDetail = EmailState.triageEmails.find(e => e.id === msgId) || {id: msgId};
    const overlay = document.getElementById('email-detail-overlay');
    const content = document.getElementById('email-detail-content');
    const replySection = document.getElementById('email-reply-section');

    overlay.style.display = 'block';
    replySection.style.display = 'none';
    content.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    // Record engagement: opened
    const _em = EmailState.currentDetail;
    if (_em && _em.from_email) {
        apiFetch(API + '/api/email/engagement/record', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({sender: _em.from_email, event: 'opened'}),
        }).catch(() => {});
    }

    try {
        const res = await apiFetch(API + '/api/email/read/' + encodeURIComponent(msgId) + '?source=' + encodeURIComponent(idSource));
        const data = await res.json();

        if (data.error) {
            content.innerHTML = '<p class="em-error">' + escapeHtml(data.error) + '</p>';
            return;
        }

        const emailText = data.content || 'No content';
        const em = EmailState.currentDetail;
        // Store body so showReplySection() can include it in the draft prompt
        if (em) em.body = emailText;

        let html = '';
        html += `<div class="em-detail-meta">`;
        html += `<h3 class="em-detail-subject">${escapeHtml(em.subject || 'Email')}</h3>`;
        html += `<div>From: ${escapeHtml(em.from_name || '')} &lt;${escapeHtml(em.from_email || '')}&gt;</div>`;
        html += `</div>`;
        html += `<div class="em-detail-body">${escapeHtml(emailText)}</div>`;
        html += `<div class="em-detail-actions">`;
        html += `<button class="btn btn-primary" onclick="showReplySection()"><i class="fas fa-reply"></i> Reply</button>`;
        html += `<button class="btn btn-secondary" onclick="closeEmailDetail()">Close</button>`;
        html += `</div>`;

        content.innerHTML = html;
    } catch (e) {
        content.innerHTML = '<p class="em-error">Failed to load email. Check connection.</p>';
    }
}

function closeEmailDetail() {
    document.getElementById('email-detail-overlay').style.display = 'none';
    EmailState.currentDetail = null;
}

function suggestCalendarFromEmail(idx) {
    sendMessage('schedule from email #' + idx);
}

async function showReplySection() {
    const section = document.getElementById('email-reply-section');
    const textarea = document.getElementById('email-reply-body');
    section.style.display = 'block';
    textarea.value = '';
    textarea.placeholder = 'Drafting reply...';
    textarea.disabled = true;

    // Populate send-from dropdown
    const sendFrom = document.getElementById('email-send-from');
    if (sendFrom) {
        try {
            const sfRes = await apiFetch(API + '/api/accounts/send-from');
            const sfData = await sfRes.json();
            const accounts = sfData.accounts || [];
            if (accounts.length > 1) {
                sendFrom.innerHTML = '';
                accounts.forEach(a => {
                    const opt = document.createElement('option');
                    opt.value = a.address;
                    opt.textContent = a.address + (a.label ? ' (' + a.label + ')' : '');
                    if (a.is_primary) opt.selected = true;
                    sendFrom.appendChild(opt);
                });
                sendFrom.style.display = '';
            } else if (accounts.length === 1) {
                sendFrom.innerHTML = '<option value="' + escapeHtml(accounts[0].address) + '">' + escapeHtml(accounts[0].address) + '</option>';
                sendFrom.style.display = 'none';
            } else {
                sendFrom.style.display = 'none';
            }
        } catch(e) {
            sendFrom.style.display = 'none';
        }
    }

    // Ask the agent to draft a reply — include the full email body so the
    // agent can actually answer what was asked, not just acknowledge receipt.
    const em = EmailState.currentDetail || {};
    const bodyContext = em.body ? '\n\nEmail body:\n"""\n' + em.body + '\n"""' : '';
    const prompt = 'Draft a short, professional reply to this email from '
        + (em.from_name || em.from_email || 'sender')
        + ' with subject "' + (em.subject || '') + '".'
        + bodyContext
        + '\n\nRead the email carefully and answer any specific questions asked. '
        + 'Just the reply body text, no subject line or greeting header.';

    try {
        const res = await apiFetch(API + '/api/chat', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({message: prompt, force_agent: true})
        });
        const data = await res.json();
        textarea.value = data.response || data.message || '';
    } catch (e) {
        // Silently fall back to blank — user can still type manually
    }
    textarea.disabled = false;
    textarea.placeholder = 'Edit the draft or write your own reply...';
    textarea.focus();
}

function closeReplySection() {
    document.getElementById('email-reply-section').style.display = 'none';
}

async function sendEmailReply() {
    const body = document.getElementById('email-reply-body').value.trim();
    if (!body) return;

    const btn = document.getElementById('email-send-btn');
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';

    try {
        const em = EmailState.currentDetail || {};
        const sendFromEl = document.getElementById('email-send-from');
        const sendFrom = sendFromEl ? sendFromEl.value : '';
        const res = await apiFetch(API + '/api/email/reply', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                index: '#' + (em.index || ''),
                id: em.id,
                id_source: em.id_source || 'gmail',
                thread_id: em.thread_id,
                to: em.from_email,
                subject: 'Re: ' + (em.subject || ''),
                body: body,
                send_from: sendFrom,
            })
        });
        const data = await res.json();

        if (data.error) {
            btn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Failed';
            setTimeout(() => { btn.innerHTML = '<i class="fas fa-paper-plane"></i> Send'; btn.disabled = false; }, 2000);
        } else {
            btn.innerHTML = '<i class="fas fa-check"></i> Sent!';
            setTimeout(() => {
                closeReplySection();
                closeEmailDetail();
                btn.innerHTML = '<i class="fas fa-paper-plane"></i> Send';
                btn.disabled = false;
            }, 1500);
        }
    } catch (e) {
        btn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Error';
        setTimeout(() => { btn.innerHTML = '<i class="fas fa-paper-plane"></i> Send'; btn.disabled = false; }, 2000);
    }
}

// escapeHtml defined above

async function loadTasks() {
    const container = document.getElementById('tasks-list');
    
    try {
        const res = await apiFetch(API + '/api/tasks');
        const data = await res.json();
        
        if (!data.tasks || data.tasks.length === 0) {
            container.innerHTML = '<p style="color:var(--text-dim)">No tasks yet</p>';
            return;
        }
        
        container.innerHTML = data.tasks.filter(t => !t.completed).map(t => `
            <div class="list-item">
                <div class="task-checkbox ${t.completed ? 'done' : ''}" onclick="completeTask('${t.id}')">
                    ${t.completed ? '<i class="fas fa-check"></i>' : ''}
                </div>
                <div class="content">
                    <div class="title">${t.title}</div>
                    <div class="meta">
                        <span class="priority-${t.priority}">${t.priority}</span>
                        ${t.due_date ? ' • Due: ' + t.due_date : ''}
                        ${t.category ? ' • ' + t.category : ''}
                    </div>
                </div>
            </div>
        `).join('');
    } catch (e) {
        container.innerHTML = '<p>Error loading tasks</p>';
    }
}

async function completeTask(id) {
    await apiFetch(API + '/api/tasks/' + id + '/complete', {method: 'POST'});
    loadTasks();
}

let _memActiveTab = 'brain';
const _mtTabs = ['brain', 'working', 'knowledge', 'notes', 'files', 'downloads'];

function memSwitchTab(tab) {
    _memActiveTab = tab;
    // Toggle sections
    _mtTabs.forEach(t => {
        const el = document.getElementById('mt-' + t);
        if (el) el.style.display = t === tab ? '' : 'none';
    });
    // Toggle tab buttons
    _mtTabs.forEach(t => {
        const btn = document.getElementById('mt-tab-' + t);
        if (btn) btn.classList.toggle('active', t === tab);
    });
    // Update action button
    const actionBtn = document.getElementById('mem-action-btn');
    const actionLabel = document.getElementById('mem-action-label');
    if (tab === 'brain') {
        actionBtn.style.display = '';
        actionLabel.textContent = 'Add Memory';
        actionBtn.onclick = showAddMemoryModal;
    } else if (tab === 'knowledge') {
        actionBtn.style.display = '';
        actionLabel.textContent = 'Add Topic';
        actionBtn.onclick = () => mtShowTopicModal('knowledge');
    } else if (tab === 'notes') {
        actionBtn.style.display = '';
        actionLabel.textContent = 'Add Note';
        actionBtn.onclick = () => mtShowTopicModal('notes');
    } else {
        actionBtn.style.display = 'none';
    }
    // Load data on tab switch
    if (tab === 'working') mtLoadWorking();
    else if (tab === 'knowledge') mtLoadKnowledge();
    else if (tab === 'notes') mtLoadNotes();
    else if (tab === 'files') memLoadFiles();
    else if (tab === 'downloads') mtLoadDownloads();
}

function memActionBtn() {
    if (_memActiveTab === 'brain') showAddMemoryModal();
    else if (_memActiveTab === 'knowledge') mtShowTopicModal('knowledge');
    else if (_memActiveTab === 'notes') mtShowTopicModal('notes');
}

async function mtUnifiedSearch() {
    const q = (document.getElementById('mt-unified-search').value || '').trim();
    if (!q) return;
    const resultsEl = document.getElementById('mt-search-results');
    const tabBar = document.getElementById('mt-tab-bar');
    const clearBtn = document.getElementById('mt-search-clear');

    // Hide tabs and sections, show search results
    tabBar.style.display = 'none';
    _mtTabs.forEach(t => { const el = document.getElementById('mt-' + t); if (el) el.style.display = 'none'; });
    resultsEl.style.display = '';
    clearBtn.style.display = '';
    resultsEl.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    try {
        const res = await apiFetch(API + '/api/memory/search?q=' + encodeURIComponent(q));
        const data = await res.json();
        const results = data.results || [];

        if (!results.length) {
            resultsEl.innerHTML = '<p style="color:var(--text-dim);text-align:center;padding:20px">No results for "' + escapeHtml(q) + '"</p>';
            return;
        }

        const typeIcons = {episodic: 'fa-brain', markdown: 'fa-book', file: 'fa-file'};
        const typeColors = {episodic: 'var(--accent-violet)', markdown: 'var(--info)', file: '#10b981'};
        const typeLabels = {episodic: 'Brain', markdown: 'Knowledge', file: 'File'};

        resultsEl.innerHTML = '<div style="font-size:0.8em;color:var(--text-dim);margin-bottom:8px">' + results.length + ' result' + (results.length !== 1 ? 's' : '') + ' for "' + escapeHtml(q) + '"</div>' +
            results.map(r => {
                const icon = typeIcons[r.type] || 'fa-circle';
                const color = typeColors[r.type] || 'var(--text-dim)';
                const label = typeLabels[r.type] || r.type;
                let onclick = '';
                if (r.type === 'markdown') onclick = "mtClearSearch();memSwitchTab('knowledge');mtShowTopic('" + escapeHtml(r.title) + "','')";
                else if (r.type === 'file') onclick = "window.open(API+'/api/files/" + escapeHtml(r.file_id) + "','_blank')";

                return '<div class="list-item" style="cursor:pointer;padding:8px 12px"' + (onclick ? ' onclick="' + onclick + '"' : '') + '>' +
                    '<div class="icon" style="color:' + color + '"><i class="fas ' + icon + '"></i></div>' +
                    '<div class="content"><div class="title">' + escapeHtml(r.title) + '</div>' +
                    '<div class="meta"><span style="background:' + color + '22;color:' + color + ';padding:1px 6px;border-radius:4px;font-size:0.75em">' + label + '</span> ' +
                    escapeHtml(r.preview || '') + '</div></div></div>';
            }).join('');
    } catch (e) {
        resultsEl.innerHTML = '<p style="color:var(--error-strong)">Search failed.</p>';
    }
}

function mtClearSearch() {
    document.getElementById('mt-unified-search').value = '';
    document.getElementById('mt-search-results').style.display = 'none';
    document.getElementById('mt-search-clear').style.display = 'none';
    document.getElementById('mt-tab-bar').style.display = '';
    memSwitchTab(_memActiveTab);
}

async function mtLoadWorking() {
    const container = document.getElementById('mt-working-list');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/memory/working');
        const data = await res.json();
        const items = data.items || [];
        if (!items.length) {
            container.innerHTML = '<div style="text-align:center;padding:30px 20px"><i class="fas fa-bolt" style="font-size:2rem;color:var(--text-dim);opacity:0.4;margin-bottom:10px;display:block"></i><p style="color:var(--text-dim)">Working memory is empty.<br>Short-term context items appear here during conversations.</p></div>';
            return;
        }
        const typeIcons = {observation:'fa-eye',decision:'fa-gavel',fact:'fa-check-circle',goal:'fa-bullseye',context:'fa-puzzle-piece',question:'fa-question-circle'};
        const typeColors = {observation:'var(--accent-violet)',decision:'var(--warning-dim)',fact:'#10b981',goal:'var(--info)',context:'#6366f1',question:'var(--error-strong)'};
        container.innerHTML = '<div style="font-size:0.8em;color:var(--text-dim);margin-bottom:8px">' + items.length + ' item' + (items.length !== 1 ? 's' : '') + ' in working memory</div>' +
            items.map(item => {
                const icon = typeIcons[item.type] || 'fa-circle';
                const color = typeColors[item.type] || 'var(--text-dim)';
                const imp = item.importance || 0;
                const impDots = imp >= 0.8 ? '<span title="High importance" style="color:var(--error-strong)">&#9679;&#9679;&#9679;</span>' :
                                imp >= 0.5 ? '<span title="Medium importance" style="color:var(--warning-dim)">&#9679;&#9679;</span>' :
                                             '<span title="Low importance" style="color:var(--text-dim)">&#9679;</span>';
                return '<div class="list-item" style="padding:8px 12px">' +
                    '<div class="icon" style="color:' + color + '"><i class="fas ' + icon + '"></i></div>' +
                    '<div class="content"><div class="title">' + escapeHtml(item.content || '') + '</div>' +
                    '<div class="meta"><span style="background:' + color + '22;color:' + color + ';padding:1px 6px;border-radius:4px;font-size:0.75em">' + escapeHtml(item.type || 'note') + '</span> ' +
                    impDots + ' <span style="color:var(--text-dim);font-size:0.8em">' + (item.created ? formatDate(item.created) : '') + '</span></div></div></div>';
            }).join('');
    } catch (e) {
        container.innerHTML = '<p style="color:var(--text-dim);text-align:center;padding:20px">Could not load working memory.</p>';
    }
}

async function mtLoadDownloads() {
    const container = document.getElementById('mt-downloads-list');
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/downloads');
        const data = await res.json();
        const items = data.downloads || [];
        if (!items.length) {
            container.innerHTML = '<div style="text-align:center;padding:30px 20px"><i class="fas fa-download" style="font-size:2rem;color:var(--text-dim);opacity:0.4;margin-bottom:10px;display:block"></i><p style="color:var(--text-dim)">No downloads yet.<br>Generated files (exports, charts, reports) appear here.</p></div>';
            return;
        }
        const typeIcons = {csv:'fa-file-csv',pdf:'fa-file-pdf',docx:'fa-file-word',pptx:'fa-file-powerpoint',chart:'fa-chart-bar',zip:'fa-file-archive',export:'fa-file-export'};
        container.innerHTML = '<div style="font-size:0.8em;color:var(--text-dim);margin-bottom:8px">' + items.length + ' download' + (items.length !== 1 ? 's' : '') + '</div>' +
            items.map(item => {
                const ext = (item.filename || '').split('.').pop().toLowerCase();
                const icon = typeIcons[item.type] || typeIcons[ext] || 'fa-file';
                return '<div class="list-item" style="padding:8px 12px">' +
                    '<div class="icon" style="color:#10b981"><i class="fas ' + icon + '"></i></div>' +
                    '<div class="content"><div class="title">' + escapeHtml(item.filename || 'download') + '</div>' +
                    '<div class="meta">' + escapeHtml(item.type || ext || 'file') + ' &middot; ' +
                    (item.size ? (item.size < 1024 ? item.size + ' B' : item.size < 1048576 ? (item.size / 1024).toFixed(1) + ' KB' : (item.size / 1048576).toFixed(1) + ' MB') : '') +
                    ' &middot; ' + (item.created ? formatDate(item.created) : '') + '</div></div>' +
                    '<div style="display:flex;gap:6px;align-items:center">' +
                    (item.url ? '<a href="' + escapeHtml(item.url) + '" download class="btn btn-secondary" style="font-size:0.75em;padding:4px 8px" title="Download"><i class="fas fa-download"></i></a>' : '') +
                    '<button class="btn btn-secondary" style="font-size:0.75em;padding:4px 8px;color:var(--error-strong)" title="Remove" onclick="mtDeleteDownload(\'' + escapeHtml(item.id) + '\')"><i class="fas fa-trash"></i></button>' +
                    '</div></div>';
            }).join('');
    } catch (e) {
        container.innerHTML = '<p style="color:var(--text-dim);text-align:center;padding:20px">Could not load downloads.</p>';
    }
}

async function mtDeleteDownload(dlId) {
    try {
        await apiFetch(API + '/api/downloads/' + dlId, {method: 'DELETE'});
        mtLoadDownloads();
    } catch (e) {
        showNotification('Failed to remove download', 'error');
    }
}

async function memLoadFiles() {
    const container = document.getElementById('mem-files-list');
    const statsEl = document.getElementById('mem-files-stats');
    const query = document.getElementById('mem-files-search').value.trim();
    const jobClass = document.getElementById('mem-files-job-filter').value;

    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    try {
        // Load stats
        const statsRes = await apiFetch(API + '/api/files/stats');
        const stats = await statsRes.json();
        const totalSize = stats.total_bytes < 1048576
            ? (stats.total_bytes / 1024).toFixed(1) + ' KB'
            : (stats.total_bytes / 1048576).toFixed(1) + ' MB';
        statsEl.textContent = stats.total_files + ' files \u00b7 ' + totalSize + ' total';

        // Load file list
        let url = API + '/api/files?';
        if (query) url += 'q=' + encodeURIComponent(query) + '&';
        if (jobClass) url += 'job_class=' + encodeURIComponent(jobClass) + '&';

        const res = await apiFetch(url);
        const data = await res.json();
        const files = data.files || [];

        if (!files.length) {
            container.innerHTML = '<p style="color:var(--text-dim);text-align:center;padding:20px">No files yet. Upload files here or attach them to records in your workspace.</p>';
            return;
        }

        const mimeIcons = {
            'application/pdf': 'fa-file-pdf',
            'image/': 'fa-file-image',
            'text/': 'fa-file-lines',
            'application/vnd.openxmlformats': 'fa-file-word',
            'application/zip': 'fa-file-zipper',
            'audio/': 'fa-file-audio',
            'video/': 'fa-file-video',
        };

        let html = '';
        for (const f of files) {
            let icon = 'fa-file';
            for (const [prefix, ic] of Object.entries(mimeIcons)) {
                if ((f.mime_type || '').startsWith(prefix)) { icon = ic; break; }
            }
            const size = f.size_bytes < 1024 ? f.size_bytes + ' B' :
                f.size_bytes < 1048576 ? (f.size_bytes / 1024).toFixed(1) + ' KB' :
                (f.size_bytes / 1048576).toFixed(1) + ' MB';
            const date = (f.stored_at || '').slice(0, 10);
            const recordLabel = f.record_type && f.record_id !== 'unlinked'
                ? f.record_type + '/' + f.record_id.slice(0, 8)
                : '';

            html += '<div class="list-item" style="padding:8px 12px">';
            html += '<div class="icon"><i class="fas ' + icon + '"></i></div>';
            html += '<div class="content" style="min-width:0">';
            html += '<div class="title" style="display:flex;align-items:center;gap:6px">';
            html += '<a href="' + API + '/api/files/' + f.file_id + '" target="_blank" style="color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeHtml(f.filename) + '">' + escapeHtml(f.filename) + '</a>';
            html += '</div>';
            html += '<div class="meta">';
            html += '<span style="background:var(--bg-dark);padding:1px 6px;border-radius:4px;font-size:0.75em">' + escapeHtml(f.job_class) + '</span> ';
            if (recordLabel) html += '<span style="font-size:0.8em">' + escapeHtml(recordLabel) + '</span> ';
            html += size + ' \u00b7 ' + date;
            if (f.description) html += ' \u00b7 ' + escapeHtml(f.description);
            html += '</div>';
            html += '</div>';
            html += '<button onclick="memDeleteFile(\'' + f.file_id + '\')" style="background:none;border:none;color:var(--text-dim);cursor:pointer;padding:4px;font-size:0.85em" title="Delete" onmouseover="this.style.color=\'var(--error-strong)\'" onmouseout="this.style.color=\'var(--text-dim)\'"><i class="fas fa-trash"></i></button>';
            html += '</div>';
        }
        container.innerHTML = html;
    } catch (e) {
        container.innerHTML = '<p style="color:var(--error-strong)">Failed to load files.</p>';
    }
}

async function memDeleteFile(fileId) {
    if (!confirm('Delete this file? This cannot be undone.')) return;
    try {
        await apiFetch(API + '/api/files/' + fileId, {method: 'DELETE'});
        showToast('File deleted', 'success');
        memLoadFiles();
    } catch (e) {
        showToast('Delete failed', 'error');
    }
}

async function memUploadGeneralFiles() {
    const input = document.getElementById('mem-files-upload-input');
    if (!input || !input.files.length) return;
    let uploaded = 0;
    for (const file of input.files) {
        const form = new FormData();
        form.append('file', file);
        form.append('record_type', 'general');
        form.append('record_id', 'unlinked');
        form.append('job_class', 'general');
        try {
            const res = await apiFetch(API + '/api/files/upload', {method: 'POST', body: form});
            const data = await res.json();
            if (data.status === 'ok') uploaded++;
            else showToast('Upload failed: ' + (data.error || file.name), 'error');
        } catch (e) {
            showToast('Upload failed: ' + file.name, 'error');
        }
    }
    input.value = '';
    if (uploaded) {
        showToast(uploaded + ' file' + (uploaded > 1 ? 's' : '') + ' uploaded', 'success');
        memLoadFiles();
    }
}


// ── Smart Sort Tab ──────────────────────────────────────────────

async function loadSmartSort() {
    const container = document.getElementById('email-sorted-content');
    if (!container) return;
    container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';

    try {
        const res = await apiFetch(API + '/api/email/sorted');
        const data = await res.json();
        const allEmails = data.emails || [];
        const updatedAt = data.updated_at;

        // Store unfiltered emails for client-side account filtering
        EmailState.allSortedEmails = allEmails;
        EmailState.sortedUpdatedAt = updatedAt;

        // Apply account filter
        const emails = EmailState.accountFilter
            ? allEmails.filter(em => em.account === EmailState.accountFilter)
            : allEmails;
        const summary = data.summary || {};

        if (!allEmails.length) {
            // Check if a sort is running
            let sortHtml = '<div style="text-align:center;padding:32px;color:var(--text-dim)">'
                + '<i class="fas fa-brain" style="font-size:2em;opacity:0.3;margin-bottom:12px;display:block"></i>'
                + '<p style="font-weight:500;margin:0 0 8px">No sorted emails yet</p>'
                + '<p style="font-size:0.85em;margin:0 0 16px">Click below to sort your inbox using rule-based classification.<br>Enable Smart Mode in Settings to use AI for ambiguous senders.</p>'
                + '<button class="btn btn-primary" id="sort-inbox-btn" onclick="triggerSortNow(this)"><i class="fas fa-play"></i> Sort My Inbox</button>'
                + '</div>';
            container.innerHTML = sortHtml;
            // Check if sort is already running
            try {
                const sr = await apiFetch(API + '/api/email/sort-inbox/status');
                const ss = await sr.json();
                if (ss.running) {
                    _pollSortProgress(document.getElementById('sort-inbox-btn'));
                }
            } catch(e) {}
            return;
        }

        // Group by category (primary), sorted by importance
        const categoryOrder = [
            'Work', 'Family', 'Personal', 'Financial', 'Dev/CI', 'Events',
            'News/Reading', 'Job Search', 'Political', 'Services',
            'Other', 'Shopping/Deals', 'Spam',
            // Legacy categories still in data
            'Notifications', 'Grants', 'Campaign', 'Promotional',
        ];
        const categoryMeta = {
            'Work':            {emoji: '\ud83d\udcbc', collapse: false},
            'Family':          {emoji: '\ud83c\udfe0', collapse: false},
            'Personal':        {emoji: '\ud83d\udc9a', collapse: false},
            'Financial':       {emoji: '\ud83c\udfe6', collapse: false},
            'Dev/CI':          {emoji: '\u2699\ufe0f', collapse: false},
            'Events':          {emoji: '\ud83c\udf9f\ufe0f', collapse: false},
            'News/Reading':    {emoji: '\ud83d\udcf0', collapse: false},
            'Job Search':      {emoji: '\ud83d\udcbc', collapse: true},
            'Political':       {emoji: '\ud83c\udfdb\ufe0f', collapse: true},
            'Services':        {emoji: '\ud83d\udd27', collapse: true},
            'Other':           {emoji: '\u2753',       collapse: false},
            'Shopping/Deals':  {emoji: '\ud83d\udecf\ufe0f', collapse: true},
            'Spam':            {emoji: '\u26d4',       collapse: true},
            // Legacy
            'Notifications':   {emoji: '\ud83d\udd14', collapse: false},
            'Grants':          {emoji: '\ud83c\udfe6', collapse: false},
            'Campaign':        {emoji: '\ud83d\udce3', collapse: true},
            'Promotional':     {emoji: '\ud83d\udce2', collapse: true},
        };

        // Load custom categories to add to display
        try {
            const ccRes = await apiFetch(API + '/api/email/custom-categories');
            const ccData = await ccRes.json();
            (ccData.categories || []).forEach(cc => {
                if (!categoryMeta[cc.name]) {
                    categoryMeta[cc.name] = {
                        emoji: '\ud83c\udff7\ufe0f',
                        collapse: cc.collapse || false,
                    };
                    // Insert before Other in the order
                    const otherIdx = categoryOrder.indexOf('Other');
                    if (otherIdx >= 0) categoryOrder.splice(otherIdx, 0, cc.name);
                    else categoryOrder.push(cc.name);
                }
            });
        } catch(e) {}

        // Group emails
        const groups = {};
        emails.forEach(em => {
            const cat = em.category || 'Other';
            if (!groups[cat]) groups[cat] = [];
            groups[cat].push(em);
        });

        // Sort within each group: urgent first, then action, then fyi
        const priOrder = {urgent: 0, action: 1, fyi: 2};
        for (const cat in groups) {
            groups[cat].sort((a, b) => (priOrder[a.priority] || 2) - (priOrder[b.priority] || 2));
        }

        let html = '';

        // Account filter dropdown — extract unique accounts from all emails
        const accountSet = new Set();
        allEmails.forEach(em => { if (em.account) accountSet.add(em.account); });
        const accounts = Array.from(accountSet).sort();

        // Summary bar with account filter + category counts
        html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;padding:0 4px;flex-wrap:wrap;gap:6px">';
        html += '<div style="display:flex;align-items:center;gap:8px">';

        // Account filter (only show if more than 1 account)
        if (accounts.length > 1) {
            html += '<select id="ss-account-filter" onchange="filterSmartSortByAccount(this.value)" style="font-size:0.78rem;padding:3px 8px;border-radius:4px;border:1px solid var(--border,rgba(255,255,255,0.1));background:var(--bg-input);color:var(--text);cursor:pointer">';
            html += '<option value=""' + (!EmailState.accountFilter ? ' selected' : '') + '>All Accounts (' + allEmails.length + ')</option>';
            accounts.forEach(acct => {
                const acctCount = allEmails.filter(em => em.account === acct).length;
                const label = acct.length > 25 ? acct.substring(0, 22) + '...' : acct;
                html += '<option value="' + escapeHtml(acct) + '"' + (EmailState.accountFilter === acct ? ' selected' : '') + '>' + escapeHtml(label) + ' (' + acctCount + ')</option>';
            });
            html += '</select>';
        }

        html += '<span style="font-size:0.82rem;color:var(--text-dim)">'
              + emails.length + ' emails' + (EmailState.accountFilter ? ' (filtered)' : ' sorted');
        if (updatedAt) html += ' &bull; ' + formatDate(updatedAt, 'time');
        html += '</span>';
        html += '</div>';
        html += '<button class="btn btn-secondary" style="font-size:0.78rem;padding:4px 10px" onclick="triggerSortNow(this)"><i class="fas fa-sync"></i> Sort Now</button>';
        html += '</div>';

        // Category summary chips
        html += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;padding:0 4px">';
        categoryOrder.forEach(cat => {
            const count = (groups[cat] || []).length;
            if (!count) return;
            const meta = categoryMeta[cat] || categoryMeta['Other'];
            const urgentCount = (groups[cat] || []).filter(e => e.priority === 'urgent' || e.priority === 'action').length;
            html += '<span style="font-size:0.78rem;padding:2px 8px;border-radius:10px;background:var(--bg-input);cursor:pointer" onclick="document.getElementById(\'ss-cat-' + cat + '\').scrollIntoView({behavior:\'smooth\'})">';
            html += meta.emoji + ' ' + count;
            if (urgentCount) html += ' <span style="color:var(--warning)">(' + urgentCount + ')</span>';
            html += '</span>';
        });
        html += '</div>';

        // Render each category group
        categoryOrder.forEach(cat => {
            const catEmails = groups[cat];
            if (!catEmails || !catEmails.length) return;
            const meta = categoryMeta[cat] || categoryMeta['Other'];
            const urgentCount = catEmails.filter(e => e.priority === 'urgent').length;
            const actionCount = catEmails.filter(e => e.priority === 'action').length;

            html += _renderCategoryGroup(cat, catEmails, meta, urgentCount, actionCount, accounts);
        });

        container.innerHTML = html;
    } catch(e) {
        container.innerHTML = '<p style="color:var(--error);padding:16px">Failed to load sorted emails: ' + escapeHtml(e.message) + '</p>';
    }
}

function filterSmartSortByAccount(account) {
    EmailState.accountFilter = account;
    // Re-render Smart Sort with the stored data (no network request)
    _rerenderSmartSort();
}

function _rerenderSmartSort() {
    // Re-run loadSmartSort which will use the cached allSortedEmails + accountFilter
    loadSmartSort();
}

function _accountBadgeHtml(em, accounts) {
    // Only show account badge if multiple accounts exist
    if (!accounts || accounts.length <= 1 || !em.account) return '';
    // Use label from account if available, otherwise shorten email
    let label = em.account;
    if (label.includes('@')) {
        const parts = label.split('@');
        label = parts[0].length > 10 ? parts[0].substring(0, 8) + '..' : parts[0];
    }
    const providerIcon = {
        gmail: 'fab fa-google', outlook: 'fab fa-microsoft',
        yahoo: 'fab fa-yahoo', proton: 'fas fa-shield-alt',
        stalwart: 'fas fa-server', custom: 'fas fa-envelope',
    }[em.provider] || 'fas fa-envelope';
    return '<span style="font-size:0.65rem;padding:1px 5px;border-radius:3px;background:var(--bg-input);color:var(--text-dim);white-space:nowrap;flex-shrink:0" title="' + escapeHtml(em.account) + '"><i class="' + providerIcon + '" style="margin-right:2px"></i>' + escapeHtml(label) + '</span>';
}

function _renderCategoryGroup(category, emails, meta, urgentCount, actionCount, accounts) {
    const isCollapsed = meta.collapse;
    const groupId = 'ss-cat-' + category;

    let html = '<div style="margin-bottom:12px" id="' + groupId + '">';

    // Header — clickable to expand/collapse
    const _toggleFn = "var b=this.nextElementSibling;var vis=b.style.display==='none';b.style.display=vis?'':'none';this.querySelector('.ss-chevron').classList.toggle('fa-chevron-down',vis);this.querySelector('.ss-chevron').classList.toggle('fa-chevron-right',!vis);this.querySelector('.ss-hint').textContent=vis?'':'Click to show " + emails.length + " emails'";
    html += '<div style="font-weight:600;font-size:0.85rem;padding:10px 12px;background:var(--bg-input);border-radius:' + (isCollapsed ? '6px' : '6px 6px 0 0') + ';display:flex;justify-content:space-between;align-items:center;cursor:pointer;user-select:none" onclick="' + _toggleFn + '">';
    html += '<span>' + meta.emoji + ' ' + escapeHtml(category) + ' <span style="font-weight:400;color:var(--text-dim)">(' + emails.length + ')</span>';
    html += ' <span class="ss-hint" style="font-size:0.75rem;font-weight:400;color:var(--text-dim);font-style:italic">' + (isCollapsed ? 'Click to show ' + emails.length + ' emails' : '') + '</span>';
    html += '</span>';
    html += '<span style="display:flex;align-items:center;gap:8px">';
    if (urgentCount) html += '<span style="font-size:0.75rem;color:var(--error)">\ud83d\udd34 ' + urgentCount + '</span>';
    if (actionCount) html += '<span style="font-size:0.75rem;color:var(--warning)">\ud83d\udfe1 ' + actionCount + '</span>';
    html += '<i class="fas ss-chevron ' + (isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down') + '" style="font-size:0.8rem;color:var(--accent)"></i>';
    html += '</span></div>';

    // Body — collapsible
    html += '<div style="border:1px solid var(--border, rgba(255,255,255,0.05));border-top:none;border-radius:0 0 6px 6px;overflow:hidden;' + (isCollapsed ? 'display:none' : '') + '">';

    emails.forEach(em => {
        const fromName = escapeHtml(em.from_name || em.from_email || '');
        const subject = escapeHtml(em.subject || '(no subject)');
        const category = escapeHtml(em.category || '');
        const hasId = em.id && em.id_source;
        const clickAttr = hasId
            ? ' style="cursor:pointer" onclick="openEmailDetail(\'' + escapeHtml(em.id) + '\',\'' + escapeHtml(em.id_source) + '\')"'
            : '';

        html += '<div class="em-row"' + clickAttr + '>';
        html += '<div class="em-row-inner">';
        // Priority indicator
        const priEmoji = em.priority === 'urgent' ? '\ud83d\udd34' : em.priority === 'action' ? '\ud83d\udfe1' : '';
        if (priEmoji) html += '<span style="font-size:0.7rem;flex-shrink:0" title="' + em.priority + '">' + priEmoji + '</span>';

        html += '<span class="em-sender">' + fromName + '</span>';
        html += '<span class="em-subject">' + subject + '</span>';

        // Category badge — click for reclassify dropdown, long-press for signal breakdown
        if (category) {
            const conf = em.confidence ? Math.round(em.confidence * 100) : 0;
            const confColor = conf > 70 ? 'var(--success,#2ecc71)' : conf > 40 ? 'var(--warning,#f39c12)' : 'var(--text-dim)';
            const _femail = escapeHtml(em.from_email || '').replace(/'/g, "\\'");
            const _fname = escapeHtml(em.from_name || '').replace(/'/g, "\\'");
            const _fsubj = escapeHtml(em.subject || '').replace(/'/g, "\\'");
            html += '<span class="ss-cat-badge" style="font-size:0.7rem;padding:1px 6px;border-radius:3px;background:var(--bg-input);color:var(--text-dim);white-space:nowrap;flex-shrink:0;cursor:pointer;position:relative" onclick="event.stopPropagation();showReclassifyDropdown(\'' + _femail + '\',\'' + category + '\',this)" title="Click to reclassify">' + category;
            if (conf > 0) html += ' <span style="color:' + confColor + '">' + conf + '%</span>';
            html += ' <i class="fas fa-caret-down" style="font-size:0.6rem;opacity:0.5"></i>';
            html += '</span>';
        }
        if (em.smart_classified) {
            html += '<span style="font-size:0.65rem;padding:1px 5px;border-radius:3px;background:var(--accent);color:#fff;white-space:nowrap;flex-shrink:0" title="Classified by AI">AI</span>';
        }
        // Account badge (only shown when multiple accounts)
        html += _accountBadgeHtml(em, accounts);
        // Engagement bar
        if (em.engagement_score !== undefined && em.engagement_score > 0) {
            const engPct = Math.min(100, Math.round(em.engagement_score * 25));
            const engColor = em.engagement_score > 2 ? 'var(--success,#2ecc71)' : em.engagement_score > 0.5 ? 'var(--accent)' : 'var(--text-dim)';
            html += '<span style="display:inline-block;width:20px;height:8px;background:var(--bg-input);border-radius:2px;overflow:hidden;vertical-align:middle;flex-shrink:0" title="Engagement: ' + em.engagement_score.toFixed(1) + '">'
                  + '<span style="display:block;height:100%;width:' + engPct + '%;background:' + engColor + '"></span></span>';
        }

        // Thumbs up/down + VIP + Feedback + Trash
        if (hasId) {
            html += '<button class="browse-item-action" onclick="event.stopPropagation(); rateEmail(\'' + escapeHtml(em.from_email || '') + '\',\'' + escapeHtml(category) + '\',true,this)" title="Correct — this classification is right" style="color:var(--success,#2ecc71)"><i class="fas fa-thumbs-up"></i></button>';
            html += '<button class="browse-item-action" onclick="event.stopPropagation(); browseEmailFeedback(\'' + escapeHtml(em.from_email || '') + '\',\'' + escapeHtml(category) + '\', this)" title="Wrong — correct this classification"><i class="fas fa-thumbs-down"></i></button>';
            html += '<button class="browse-item-action" onclick="event.stopPropagation(); quickAddVip(\'' + escapeHtml(em.from_email || '') + '\', this)" title="Mark as VIP sender"><i class="fas fa-star"></i></button>';
            html += '<button class="browse-item-action danger" onclick="event.stopPropagation(); smartSortTrash(\'' + escapeHtml(em.id) + '\',\'' + escapeHtml(em.id_source) + '\', this)" title="Trash"><i class="fas fa-trash"></i></button>';
        }

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

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

async function triggerSortNow(btn) {
    if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...'; }
    try {
        const r = await apiFetch(API + '/api/email/sort-inbox', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
        });
        const d = await r.json();
        if (d.error) {
            showToast(d.error, 'info');
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-sync"></i> Sort Now'; }
            return;
        }
        showToast('Sorting inbox...', 'success');
        // Poll for progress
        _pollSortProgress(btn);
    } catch(e) {
        showToast('Sort failed: ' + e.message, 'error');
        if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-sync"></i> Sort Now'; }
    }
}

function _pollSortProgress(btn) {
    const progressEl = document.getElementById('sort-progress-bar');
    const poll = setInterval(async () => {
        try {
            const r = await apiFetch(API + '/api/email/sort-inbox/status');
            const s = await r.json();

            // Update button with progress
            if (btn) {
                if (s.running) {
                    const display = s.total > 0 ? s.sorted + '/' + Math.max(s.sorted, s.total) : s.sorted;
                    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + display + ' sorted'
                        + ' <button onclick="event.stopPropagation();stopSortInbox()" style="margin-left:6px;font-size:0.75rem;padding:2px 6px;background:none;border:1px solid currentColor;border-radius:3px;color:inherit;cursor:pointer">Stop</button>';
                } else {
                    btn.disabled = false;
                    btn.innerHTML = '<i class="fas fa-sync"></i> Sort Now';
                }
            }

            if (!s.running) {
                clearInterval(poll);
                if (s.sorted > 0) {
                    showToast(s.sorted + ' emails sorted: ' + s.urgent + ' urgent, ' + s.action + ' action, ' + s.fyi + ' fyi', 'success');
                }
                loadSmartSort();
            }
        } catch(e) {
            clearInterval(poll);
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-sync"></i> Sort Now'; }
        }
    }, 2000);
}

async function stopSortInbox() {
    try {
        await apiFetch(API + '/api/email/sort-inbox/stop', {method: 'POST'});
        showToast('Sort cancelled', 'info');
    } catch(e) { showToast('Failed to cancel: ' + e.message, 'error'); }
}

async function smartSortTrash(msgId, source, btn) {
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
    try {
        await apiFetch(API + '/api/email/delete/' + encodeURIComponent(msgId), {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({id_source: source}),
        });
        // Record engagement: trashed
        const row = btn.closest('.em-row');
        const senderEl = row ? row.querySelector('.em-sender') : null;
        if (senderEl) {
            apiFetch(API + '/api/email/engagement/record', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({sender: senderEl.textContent.trim(), event: 'trashed'}),
            }).catch(() => {});
        }
        if (row) { row.style.opacity = '0'; setTimeout(() => row.remove(), 300); }
    } catch(e) { showToast('Trash failed: ' + e.message, 'error'); btn.disabled = false; btn.innerHTML = '<i class="fas fa-trash"></i>'; }
}

async function quickAddVip(fromEmail, btn) {
    if (!fromEmail) { showToast('No sender email', 'error'); return; }
    try {
        await apiFetch(API + '/api/email/vip-senders', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({sender: fromEmail}),
        });
        btn.innerHTML = '<i class="fas fa-star" style="color:var(--accent)"></i>';
        btn.disabled = true;
        showToast(fromEmail + ' added as VIP', 'success');
    } catch(e) { showToast('Failed: ' + e.message, 'error'); }
}

async function rateEmail(fromEmail, category, isCorrect, btn) {
    try {
        await apiFetch(API + '/api/email/rate', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                from_email: fromEmail,
                category: category,
                correct: isCorrect,
            }),
        });
        btn.innerHTML = '<i class="fas fa-check" style="color:var(--success)"></i>';
        btn.disabled = true;
    } catch(e) { showToast('Rating failed: ' + e.message, 'error'); }
}

// ── Signal Breakdown (Transparency UI) ───────────────────────────

async function showSignalBreakdown(fromEmail, fromName, subject, badge) {
    // Toggle — close if already open
    const existing = badge.parentElement.querySelector('.ss-signal-panel');
    if (existing) { existing.remove(); return; }

    // Close any other open panels
    document.querySelectorAll('.ss-signal-panel').forEach(p => p.remove());

    const panel = document.createElement('div');
    panel.className = 'ss-signal-panel';
    panel.style.cssText = 'position:absolute;right:0;top:100%;z-index:100;background:var(--bg-card,#1e1e2e);border:1px solid var(--border,rgba(255,255,255,0.1));border-radius:8px;padding:12px;min-width:300px;max-width:400px;box-shadow:0 4px 16px rgba(0,0,0,0.4);font-size:0.8rem;margin-top:4px';
    panel.innerHTML = '<div style="text-align:center;padding:8px"><i class="fas fa-spinner fa-spin"></i> Scoring...</div>';

    // Ensure parent is positioned
    badge.parentElement.style.position = 'relative';
    badge.parentElement.appendChild(panel);

    try {
        const r = await apiFetch(API + '/api/email/score', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({sender_email: fromEmail, sender_name: fromName, subject: subject}),
        });
        const data = await r.json();
        const signals = data.signals || {};

        const signalMeta = {
            header:     {label: 'Headers',     icon: 'fa-envelope-open', weight: '15%'},
            sender:     {label: 'Sender',      icon: 'fa-user',         weight: '20%'},
            content:    {label: 'Content',      icon: 'fa-file-alt',     weight: '15%'},
            engagement: {label: 'Engagement',   icon: 'fa-chart-line',   weight: '25%'},
            correction: {label: 'Corrections',  icon: 'fa-pencil-alt',   weight: '20%'},
            contact:    {label: 'Contacts',     icon: 'fa-address-book', weight: '5%'},
        };

        let html = '<div style="font-weight:600;margin-bottom:8px;display:flex;justify-content:space-between">';
        html += '<span>Signal Breakdown</span>';
        html += '<span style="font-weight:400;color:var(--text-dim)">' + escapeHtml(data.category || '') + ' ' + Math.round((data.confidence || 0) * 100) + '%</span>';
        html += '</div>';

        for (const [key, meta] of Object.entries(signalMeta)) {
            const sig = signals[key] || {};
            const conf = sig.confidence || 0;
            const reason = sig.reason || 'No data';
            const cat = sig.category || '—';
            const active = conf > 0;
            const barPct = Math.round(conf * 100);

            html += '<div style="margin-bottom:6px;opacity:' + (active ? '1' : '0.4') + '">';
            html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px">';
            html += '<span><i class="fas ' + meta.icon + '" style="width:16px;color:var(--accent);margin-right:4px"></i>' + meta.label + ' <span style="color:var(--text-dim)">(' + meta.weight + ')</span></span>';
            if (active) html += '<span style="font-weight:500">' + escapeHtml(cat) + ' ' + barPct + '%</span>';
            else html += '<span style="color:var(--text-dim)">—</span>';
            html += '</div>';

            // Confidence bar
            html += '<div style="background:var(--bg-input);border-radius:3px;height:4px;overflow:hidden;margin-bottom:2px">';
            if (active) {
                const barColor = barPct > 70 ? 'var(--success,#2ecc71)' : barPct > 40 ? 'var(--warning,#f39c12)' : 'var(--text-dim)';
                html += '<div style="height:100%;width:' + barPct + '%;background:' + barColor + ';transition:width 0.3s"></div>';
            }
            html += '</div>';

            // Reason
            html += '<div style="font-size:0.72rem;color:var(--text-dim)">' + escapeHtml(reason) + '</div>';
            html += '</div>';
        }

        panel.innerHTML = html;
    } catch(e) {
        panel.innerHTML = '<div style="color:var(--error);padding:8px">Failed to load signals: ' + escapeHtml(e.message) + '</div>';
    }

    // Close on outside click
    setTimeout(() => {
        document.addEventListener('click', function _close(e) {
            if (!panel.contains(e.target) && e.target !== badge) {
                panel.remove();
                document.removeEventListener('click', _close);
            }
        });
    }, 10);
}

// ── Reclassify Dropdown ───────────────────────────────

async function showReclassifyDropdown(fromEmail, currentCat, badge) {
    // Toggle — close any existing dropdown
    const existing = document.querySelector('.ss-reclass-dd');
    if (existing) { existing.remove(); return; }

    // Build category list
    let cats = ['Work', 'Family', 'Personal', 'Financial', 'Dev/CI', 'Events', 'News/Reading', 'Job Search', 'Political', 'Shopping/Deals', 'Services', 'Spam', 'Other'];
    try {
        const r = await apiFetch(API + '/api/email/custom-categories');
        const d = await r.json();
        (d.categories || []).forEach(cc => {
            if (!cats.includes(cc.name)) cats.push(cc.name);
        });
    } catch(e) {}

    const dd = document.createElement('div');
    dd.className = 'ss-reclass-dd';

    // Use fixed positioning so it's never clipped by overflow:hidden parents
    const badgeRect = badge.getBoundingClientRect();
    const spaceBelow = window.innerHeight - badgeRect.bottom;
    const openUp = spaceBelow < 320;

    dd.style.cssText = 'position:fixed;z-index:200;background:var(--bg-card,#1e1e2e);border:1px solid var(--border,rgba(255,255,255,0.1));border-radius:6px;padding:4px;min-width:160px;max-height:300px;overflow-y:auto;box-shadow:0 4px 16px rgba(0,0,0,0.4);'
        + 'left:' + Math.min(badgeRect.left, window.innerWidth - 180) + 'px;'
        + (openUp
            ? 'bottom:' + (window.innerHeight - badgeRect.top + 4) + 'px;'
            : 'top:' + (badgeRect.bottom + 4) + 'px;');

    let html = '';
    cats.forEach(cat => {
        const isCurrent = cat === currentCat;
        const style = isCurrent
            ? 'background:var(--accent);color:#fff;font-weight:600'
            : 'background:none;color:var(--text-dim)';
        html += '<button style="display:block;width:100%;text-align:left;border:none;padding:4px 8px;font-size:0.78rem;border-radius:3px;cursor:pointer;' + style + '" '
              + 'onmouseover="this.style.background=\'' + (isCurrent ? '' : 'rgba(255,255,255,0.05)') + '\'" '
              + 'onmouseout="this.style.background=\'' + (isCurrent ? 'var(--accent)' : 'none') + '\'" '
              + 'onclick="event.stopPropagation();reclassifyEmail(\'' + escapeHtml(fromEmail).replace(/'/g, "\\'") + '\',\'' + escapeHtml(cat).replace(/'/g, "\\'") + '\')">'
              + (isCurrent ? '\u2713 ' : '') + escapeHtml(cat)
              + '</button>';
    });

    // Signal breakdown link at bottom
    html += '<hr style="border:none;border-top:1px solid var(--border,rgba(255,255,255,0.05));margin:4px 0">';
    html += '<button style="display:block;width:100%;text-align:left;border:none;padding:4px 8px;font-size:0.72rem;border-radius:3px;cursor:pointer;background:none;color:var(--accent)" '
          + 'onclick="event.stopPropagation();document.querySelectorAll(\'.ss-reclass-dd\').forEach(d=>d.remove());showSignalBreakdown(\'' + escapeHtml(fromEmail).replace(/'/g, "\\'") + '\',\'\',\'\',this.closest(\'.ss-cat-badge\'))">'
          + '<i class="fas fa-info-circle"></i> Why this category?</button>';

    dd.innerHTML = html;
    document.body.appendChild(dd);

    // Close on outside click or scroll
    setTimeout(() => {
        function _close(e) {
            if (!dd.contains(e.target) && e.target !== badge) {
                dd.remove();
                document.removeEventListener('click', _close);
                document.removeEventListener('scroll', _close, true);
            }
        }
        document.addEventListener('click', _close);
        document.addEventListener('scroll', _close, true);
    }, 10);
}

async function reclassifyEmail(fromEmail, newCategory) {
    document.querySelectorAll('.ss-reclass-dd').forEach(d => d.remove());
    try {
        await apiFetch(API + '/api/email/feedback', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                from_email: fromEmail,
                correct_category: newCategory,
                apply_to_sender: true,
            }),
        });
        showToast(fromEmail.split('@')[0] + ' → ' + newCategory, 'success');
        // Reload to show the email in its new category
        setTimeout(() => loadSmartSort(), 500);
    } catch(e) { showToast('Failed: ' + e.message, 'error'); }
}
