// server.js — Server panel, apps, training/corpus panel
// Extracted from dashboard.html

// ── Server panel ──────────────────────────────────────
let _serverData = null;

async function loadServer() {
    const grid = document.getElementById('sv-overview-grid');
    grid.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const [rtRes, stRes] = await Promise.all([
            apiFetch(API + '/api/server/runtime'),
            apiFetch(API + '/api/server/status')
        ]);
        const runtime = await rtRes.json();
        const statuses = await stRes.json();
        _serverData = {runtime, statuses};
        renderServerRuntime(runtime);
        renderServerOverview(statuses, runtime);
        populateLogSelector(statuses);
    } catch(e) {
        grid.innerHTML = '<div class="sv-no-runtime"><p>Failed to load server status.</p></div>';
    }
}

function switchServerTab(tab) {
    document.querySelectorAll('#server-content .sv-tab').forEach((t,i) => {
        const tabs = ['overview','services','credentials','logs'];
        t.classList.toggle('active', tabs[i]===tab);
    });
    ['sv-overview-tab','sv-services-tab','sv-credentials-tab','sv-logs-tab'].forEach(id => {
        document.getElementById(id).style.display = 'none';
    });
    document.getElementById('sv-'+tab+'-tab').style.display = '';
    if (tab === 'credentials') loadServerCredentials();
}

function renderServerRuntime(rt) {
    const el = document.getElementById('sv-runtime-info');
    if (rt.runtime === 'none') {
        el.innerHTML = `<div class="sv-no-runtime">
            <i class="fas fa-server"></i>
            <h3>No Container Runtime Found</h3>
            <p>Install <a href="https://podman.io/docs/installation" target="_blank">Podman</a>
            or <a href="https://docs.docker.com/get-docker/" target="_blank">Docker</a>
            to provision self-hosted services (including Stalwart Mail).</p>
        </div>`;
        return;
    }
    const rootless = rt.rootless ? ' (rootless)' : '';
    el.innerHTML = `<div class="sv-runtime-banner">
        <div class="sv-rt-icon"><i class="fas fa-cube"></i></div>
        <div>
            <div class="sv-rt-label">${rt.runtime.charAt(0).toUpperCase()+rt.runtime.slice(1)} v${rt.version||'?'}${rootless}</div>
            <div class="sv-rt-detail">Container runtime ready</div>
        </div>
    </div>`;
}

function renderServerOverview(statuses, runtime) {
    const grid = document.getElementById('sv-overview-grid');
    const svcList = document.getElementById('sv-services-list');
    if (!statuses || !statuses.length) {
        grid.innerHTML = '<div class="sv-no-runtime"><p>No services configured.</p></div>';
        svcList.innerHTML = '';
        return;
    }
    grid.innerHTML = statuses.map(s => {
        const st = s.status || 'not_installed';
        const isContainer = s.service_type === 'container';
        const noRuntime = runtime.runtime === 'none' && isContainer;
        let actions = '';
        if (st === 'not_installed') {
            if (noRuntime && isContainer) {
                actions = '<button class="sv-btn" disabled>Requires runtime</button>';
            } else {
                actions = `<button class="sv-btn primary" onclick="provisionService('${s.key}')">Provision</button>`;
            }
        } else if (st === 'stopped') {
            actions = `<button class="sv-btn primary" onclick="startService('${s.key}')">Start</button>
                <button class="sv-btn danger" onclick="removeService('${s.key}')">Remove</button>`;
        } else if (st === 'running') {
            actions = `<button class="sv-btn" onclick="stopService('${s.key}')">Stop</button>
                <button class="sv-btn" onclick="restartService('${s.key}')">Restart</button>
                <button class="sv-btn danger" onclick="removeService('${s.key}')">Remove</button>`;
        } else if (st === 'pulling' || st === 'starting') {
            actions = '<button class="sv-btn" disabled><i class="fas fa-spinner fa-spin"></i> Working...</button>';
        } else if (st === 'error') {
            actions = `<button class="sv-btn primary" onclick="provisionService('${s.key}')">Retry</button>
                <button class="sv-btn danger" onclick="removeService('${s.key}')">Remove</button>`;
        }
        const urlHtml = s.url ? `<div class="sv-card-url"><a href="${_esc(s.url)}" target="_blank">${_esc(s.url)}</a></div>` : '';
        const errHtml = s.error_message ? `<div style="font-size:0.78rem;color:var(--error);margin-bottom:6px">${_esc(s.error_message)}</div>` : '';
        return `<div class="sv-card">
            <div class="sv-card-header"><i class="fas ${_esc(s.icon)}"></i><span class="sv-name">${_esc(s.display_name)}</span></div>
            <div class="sv-card-desc">${_esc(s.description)}</div>
            <div class="sv-card-status"><span class="sv-badge ${st}">${st.replace('_',' ')}</span></div>
            ${urlHtml}${errHtml}
            <div class="sv-card-actions">${actions}</div>
        </div>`;
    }).join('');

    // Services detail tab
    svcList.innerHTML = statuses.map(s => {
        const ports = s.ports ? Object.entries(s.ports).map(([h,c])=>`${h}\u2192${c}`).join(', ') : 'N/A';
        return `<div class="sv-card">
            <div class="sv-card-header"><i class="fas ${_esc(s.icon)}"></i><span class="sv-name">${_esc(s.display_name)}</span>
                <span class="sv-badge ${_esc(s.status)||'not_installed'}" style="margin-left:auto">${(_esc(s.status)||'not_installed').replace('_',' ')}</span>
            </div>
            <div class="sv-card-desc">${_esc(s.description)}</div>
            <div style="font-size:0.82rem;color:var(--text-dim);margin-bottom:4px"><strong>Type:</strong> ${_esc(s.service_type)} &nbsp; <strong>Ports:</strong> ${_esc(ports)}</div>
            ${s.image ? '<div style="font-size:0.82rem;color:var(--text-dim);margin-bottom:4px"><strong>Image:</strong> '+_esc(s.image)+'</div>' : ''}
            ${s.container_name ? '<div style="font-size:0.82rem;color:var(--text-dim)"><strong>Container:</strong> '+_esc(s.container_name)+'</div>' : ''}
        </div>`;
    }).join('');
}

function populateLogSelector(statuses) {
    const sel = document.getElementById('sv-log-service');
    const current = sel.value;
    sel.innerHTML = '<option value="">Select a service...</option>';
    statuses.forEach(s => {
        if (s.service_type === 'container' && s.status !== 'not_installed') {
            sel.innerHTML += `<option value="${s.key}">${s.display_name}</option>`;
        }
    });
    if (current) sel.value = current;
}

async function provisionService(key) {
    if (!confirm('Provision this service? This will pull the container image.')) return;
    try {
        const res = await apiFetch(API + '/api/server/provision/'+key, {method:'POST'});
        const data = await res.json();
        if (!data.success) alert('Error: ' + (data.error||'Unknown error'));
    } catch(e) { alert('Failed: '+e.message); }
    loadServer();
}

async function startService(key) {
    try {
        const res = await apiFetch(API + '/api/server/start/'+key, {method:'POST'});
        const data = await res.json();
        if (!data.success) alert('Error: ' + (data.error||'Unknown error'));
    } catch(e) { alert('Failed: '+e.message); }
    loadServer();
}

async function stopService(key) {
    try {
        const res = await apiFetch(API + '/api/server/stop/'+key, {method:'POST'});
        const data = await res.json();
        if (!data.success) alert('Error: ' + (data.error||'Unknown error'));
    } catch(e) { alert('Failed: '+e.message); }
    loadServer();
}

async function restartService(key) {
    try {
        const res = await apiFetch(API + '/api/server/restart/'+key, {method:'POST'});
        const data = await res.json();
        if (!data.success) alert('Error: ' + (data.error||'Unknown error'));
    } catch(e) { alert('Failed: '+e.message); }
    loadServer();
}

async function removeService(key) {
    const removeData = confirm('Also delete all service data? Click OK to delete data, Cancel to keep it.');
    if (!confirm('Remove this service container?')) return;
    try {
        const res = await apiFetch(API + '/api/server/remove/'+key, {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({remove_data: removeData})
        });
        const data = await res.json();
        if (!data.success) alert('Error: ' + (data.error||'Unknown error'));
    } catch(e) { alert('Failed: '+e.message); }
    loadServer();
}

async function loadServerLogs() {
    const key = document.getElementById('sv-log-service').value;
    const box = document.getElementById('sv-log-output');
    if (!key) { box.textContent = 'Select a service to view logs.'; return; }
    box.textContent = 'Loading...';
    try {
        const res = await apiFetch(API + '/api/server/logs/'+key);
        const data = await res.json();
        box.textContent = data.logs || '(no logs)';
        box.scrollTop = box.scrollHeight;
    } catch(e) {
        box.textContent = 'Failed to load logs: '+e.message;
    }
}

async function loadServerCredentials() {
    const el = document.getElementById('sv-credentials-list');
    el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + '/api/server/credentials');
        const data = await res.json();
        const services = data.services || [];

        if (!services.length) {
            el.innerHTML = '<div class="sv-cred-empty">' +
                '<i class="fas fa-key"></i><br>' +
                '<strong>No credentials yet</strong><br>' +
                'Credentials will appear here when you provision services.</div>';
            return;
        }

        let html = '<div class="sv-cred-warn"><i class="fas fa-shield-alt"></i>' +
            'Credentials are stored locally in <code>~/.familiar/services/secrets.json</code> (chmod 600). ' +
            'Change default passwords after first login.</div>';

        for (const svc of services) {
            html += '<div class="sv-cred-card">';
            html += '<div class="sv-cred-header">' +
                '<i class="fas ' + escapeHtml(svc.icon || 'fa-server') + '"></i> ' +
                escapeHtml(svc.display_name);
            if (svc.url) {
                html += '<a href="' + escapeHtml(svc.url) + '" target="_blank">Open ' + escapeHtml(svc.display_name) + ' &rarr;</a>';
            }
            html += '</div>';

            // Default credentials
            if (svc.default_credentials) {
                for (const cred of svc.default_credentials) {
                    if (cred.username) {
                        html += '<div class="sv-cred-row">' +
                            '<span class="sv-cred-label">' + escapeHtml(cred.label || 'Login') + ' user</span>' +
                            '<span class="sv-cred-value">' + escapeHtml(cred.username) + '</span>' +
                            '<button class="sv-cred-copy" onclick="navigator.clipboard.writeText(\'' + escapeHtml(cred.username) + '\')"><i class="fas fa-copy"></i></button>' +
                            '</div>';
                    }
                    if (cred.password && !cred.password.startsWith('(auto')) {
                        html += '<div class="sv-cred-row">' +
                            '<span class="sv-cred-label">' + escapeHtml(cred.label || 'Login') + ' pass</span>' +
                            '<span class="sv-cred-value">' + escapeHtml(cred.password) + '</span>' +
                            '<button class="sv-cred-copy" onclick="navigator.clipboard.writeText(\'' + escapeHtml(cred.password) + '\')"><i class="fas fa-copy"></i></button>' +
                            '</div>';
                    }
                }
            }

            // Generated credentials
            if (svc.generated_credentials) {
                for (const [key, val] of Object.entries(svc.generated_credentials)) {
                    html += '<div class="sv-cred-row">' +
                        '<span class="sv-cred-label">' + escapeHtml(key) + '</span>' +
                        '<span class="sv-cred-value">' + escapeHtml(val) + '</span>' +
                        '<button class="sv-cred-copy" onclick="navigator.clipboard.writeText(\'' + escapeHtml(val) + '\')"><i class="fas fa-copy"></i></button>' +
                        '</div>';
                }
            }

            // Setup notes
            if (svc.setup_notes) {
                html += '<div class="sv-cred-notes"><i class="fas fa-info-circle" style="margin-right:6px"></i>' +
                    escapeHtml(svc.setup_notes) + '</div>';
            }

            html += '</div>';
        }

        el.innerHTML = html;
    } catch (e) {
        el.innerHTML = '<div class="sv-cred-empty">Failed to load credentials: ' + escapeHtml(e.message) + '</div>';
    }
}

function loadMarketplace() {
    loadCatalogStats();
    loadCatalog();
}

function loadCatalogStats() {
    apiFetch('/api/marketplace/catalog/stats').then(r => r.json()).then(data => {
        const bar = document.getElementById('mp-stats-bar');
        const s = data.stats || {};
        bar.innerHTML = Object.entries(s).map(([k, v]) =>
            `<div class="mp-stat"><strong>${_esc(String(v))}</strong>${_esc(k)}</div>`
        ).join('');
    }).catch(() => {});
}

function loadCatalog() {
    const search = (document.getElementById('mp-catalog-search') || {}).value || '';
    let url = '/api/marketplace/catalog/browse?limit=100';
    if (_mpCatalogType) url += '&entity_type=' + encodeURIComponent(_mpCatalogType);
    if (search) url += '&search=' + encodeURIComponent(search);
    apiFetch(url).then(r => r.json()).then(data => {
        renderCatalogGrid(data.entries || []);
    }).catch(() => {
        document.getElementById('mp-catalog-grid').innerHTML = '<div class="mp-empty">Failed to load catalog</div>';
    });
}

function renderCatalogGrid(entries) {
    const grid = document.getElementById('mp-catalog-grid');
    if (!entries.length) {
        grid.innerHTML = '<div class="mp-empty">No entries found</div>';
        return;
    }
    let html = '';
    entries.forEach(e => {
        const cls = e.source === 'builtin' ? 'mp-card builtin' : 'mp-card custom';
        const tags = (e.tags || []).slice(0, 4).map(t => `<span class="mp-card-tag">${esc(t)}</span>`).join('');
        const meta = [e.version, e.author, e.source].filter(Boolean).join(' · ');
        html += `<div class="${cls}" onclick="showCatalogDetail('${esc(e.entity_type)}','${esc(e.id)}')">
            <div class="mp-card-header">
                <span class="mp-card-name">${esc(e.name)}</span>
                <span class="mp-card-type">${esc(e.entity_type)}</span>
            </div>
            <div class="mp-card-desc">${esc((e.description || '').slice(0, 120))}</div>
            ${tags ? '<div class="mp-card-tags">' + tags + '</div>' : ''}
            <div class="mp-card-meta">${esc(meta)}</div>
        </div>`;
    });
    grid.innerHTML = html;
}

function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }

function filterCatalog(btn, entityType) {
    _mpCatalogType = entityType;
    document.querySelectorAll('#mp-catalog-filters .mp-filter-btn').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    loadCatalog();
}

function debounceCatalogSearch() {
    clearTimeout(_mpCatalogTimer);
    _mpCatalogTimer = setTimeout(loadCatalog, 300);
}

function showCatalogDetail(type, id) {
    apiFetch(`/api/marketplace/catalog/entry/${encodeURIComponent(type)}/${encodeURIComponent(id)}`).then(r => r.json()).then(entry => {
        if (entry.error) return;
        const ed = entry.entity_data || {};
        let detail = '';
        if (type === 'species') {
            detail = `<p><b>Personality:</b> ${esc(ed.personality || '')} </p><p><b>Voice:</b> ${esc(ed.voice || '')}</p>`;
        } else if (type === 'job') {
            detail = `<p><b>Icon:</b> ${esc(ed.icon || '')} </p><p><b>Connects:</b> ${esc((ed.connects || []).join(', '))}</p>`;
        } else if (type === 'skill') {
            detail = `<p><b>Category:</b> ${esc(ed.category || '')} </p><p><b>Tools:</b> ${esc((ed.tools || []).join(', '))}</p>`;
        } else if (type === 'tool') {
            detail = `<p><b>Skill:</b> ${esc(ed.skill || '')} </p><p><b>Category:</b> ${esc(ed.category || '')}</p>`;
        }
        const tags = (entry.tags || []).map(t => `<span class="mp-card-tag">${esc(t)}</span>`).join('');
        const modal = document.createElement('div');
        modal.className = 'skill-modal-overlay';
        modal.onclick = function(ev) { if (ev.target === modal) modal.remove(); };
        modal.innerHTML = `<div class="skill-modal">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
                <h3 style="margin:0">${esc(entry.name)}</h3>
                <button onclick="this.closest('.skill-modal-overlay').remove()" style="background:none;border:none;color:var(--text);font-size:1.3rem;cursor:pointer">&times;</button>
            </div>
            <p style="color:var(--text-dim);margin:0 0 8px"><span class="mp-card-type">${esc(entry.entity_type)}</span></p>
            <p>${esc(entry.description || '')}</p>
            ${tags ? '<div class="mp-card-tags" style="margin-bottom:12px">' + tags + '</div>' : ''}
            ${detail}
            <div class="mp-card-meta" style="margin-top:12px">${[entry.version, entry.author, entry.source].filter(Boolean).join(' · ')}</div>
        </div>`;
        document.body.appendChild(modal);
    }).catch(() => {});
}

function loadPlugins() {
    const search = (document.getElementById('mp-plugin-search') || {}).value || '';
    let url = '/api/marketplace/plugins?';
    if (_mpPluginCategory) url += 'category=' + encodeURIComponent(_mpPluginCategory) + '&';
    if (search) url += 'search=' + encodeURIComponent(search);
    apiFetch(url).then(r => r.json()).then(data => {
        // Render category buttons
        const filterBar = document.getElementById('mp-plugin-filters');
        let btns = '<button class="mp-filter-btn' + (!_mpPluginCategory ? ' active' : '') + '" onclick="filterPlugins(this, \'\')">All</button>';
        (data.categories || []).forEach(c => {
            btns += `<button class="mp-filter-btn${_mpPluginCategory === c ? ' active' : ''}" onclick="filterPlugins(this, '${esc(c)}')">${esc(c)}</button>`;
        });
        btns += '<input class="mp-search" placeholder="Search plugins..." oninput="debouncePluginSearch()" id="mp-plugin-search" value="' + esc(search) + '">';
        filterBar.innerHTML = btns;
        renderPluginsGrid(data.plugins || []);
    }).catch(() => {
        document.getElementById('mp-plugins-grid').innerHTML = '<div class="mp-empty">Failed to load plugins</div>';
    });
}

function renderPluginsGrid(plugins) {
    const grid = document.getElementById('mp-plugins-grid');
    if (!plugins.length) {
        grid.innerHTML = '<div class="mp-empty">No plugins found</div>';
        return;
    }
    let html = '';
    plugins.forEach(p => {
        const stars = p.rating ? '★'.repeat(Math.round(p.rating)) + '☆'.repeat(5 - Math.round(p.rating)) : '';
        const downloads = p.downloads ? `${p.downloads} downloads` : '';
        const meta = [stars, downloads, p.author, p.version].filter(Boolean).join(' · ');
        let actions = '';
        if (p.installed) {
            actions += `<button class="mp-btn-installed">Installed</button>`;
            actions += `<button class="mp-btn-uninstall" onclick="event.stopPropagation();uninstallPlugin('${esc(p.id)}', this)">Uninstall</button>`;
            actions += `<button class="mp-btn-update" onclick="event.stopPropagation();updatePlugin('${esc(p.id)}', this)">Update</button>`;
        } else {
            actions += `<button class="mp-btn-install" onclick="event.stopPropagation();installPlugin('${esc(p.id)}', this)">Install</button>`;
        }
        html += `<div class="mp-card">
            <div class="mp-card-header">
                <span class="mp-card-name">${esc(p.name)}</span>
                <span class="mp-card-type">${esc(p.category || '')}</span>
            </div>
            <div class="mp-card-desc">${esc((p.description || '').slice(0, 120))}</div>
            <div class="mp-card-meta">${esc(meta)}</div>
            <div class="mp-plugin-actions">${actions}</div>
        </div>`;
    });
    grid.innerHTML = html;
}

function filterPlugins(btn, category) {
    _mpPluginCategory = category;
    document.querySelectorAll('#mp-plugin-filters .mp-filter-btn').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    loadPlugins();
}

function debouncePluginSearch() {
    clearTimeout(_mpPluginTimer);
    _mpPluginTimer = setTimeout(loadPlugins, 300);
}

function installPlugin(id, btn) {
    const orig = btn.textContent;
    btn.textContent = 'Installing...';
    btn.disabled = true;
    apiFetch(`/api/marketplace/plugins/${encodeURIComponent(id)}/install`, {
        method: 'POST'
    }).then(r => r.json()).then(data => {
        btn.textContent = data.result || 'Done';
        setTimeout(loadPlugins, 1500);
    }).catch(() => {
        btn.textContent = 'Error';
        setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 2000);
    });
}

function uninstallPlugin(id, btn) {
    if (!confirm('Uninstall this plugin?')) return;
    btn.textContent = 'Removing...';
    btn.disabled = true;
    apiFetch(`/api/marketplace/plugins/${encodeURIComponent(id)}/uninstall`, {
        method: 'POST'
    }).then(r => r.json()).then(() => {
        loadPlugins();
    }).catch(() => {
        btn.textContent = 'Error';
        setTimeout(() => { btn.textContent = 'Uninstall'; btn.disabled = false; }, 2000);
    });
}

function updatePlugin(id, btn) {
    btn.textContent = 'Updating...';
    btn.disabled = true;
    apiFetch(`/api/marketplace/plugins/${encodeURIComponent(id)}/update`, {
        method: 'POST'
    }).then(r => r.json()).then(data => {
        btn.textContent = data.result || 'Done';
        setTimeout(loadPlugins, 1500);
    }).catch(() => {
        btn.textContent = 'Error';
        setTimeout(() => { btn.textContent = 'Update'; btn.disabled = false; }, 2000);
    });
}

function showImportDialog() {
    const modal = document.createElement('div');
    modal.className = 'skill-modal-overlay';
    modal.onclick = function(ev) { if (ev.target === modal) modal.remove(); };
    modal.innerHTML = `<div class="skill-modal">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
            <h3 style="margin:0">Import Package</h3>
            <button onclick="this.closest('.skill-modal-overlay').remove()" style="background:none;border:none;color:var(--text);font-size:1.3rem;cursor:pointer">&times;</button>
        </div>
        <p style="color:var(--text-dim)">Upload a .json package file or paste JSON below:</p>
        <input type="file" accept=".json" onchange="loadImportFile(this)" style="margin-bottom:12px;color:var(--text)">
        <textarea id="mp-import-json" rows="10" style="width:100%;background:var(--bg-input);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px;font-family:monospace;font-size:0.85rem;resize:vertical" placeholder='{"schema_version":"1","type":"skill",...}'></textarea>
        <div id="mp-import-error" style="color:var(--error);font-size:0.85rem;margin-top:8px;display:none"></div>
        <div id="mp-import-success" style="color:var(--success);font-size:0.85rem;margin-top:8px;display:none"></div>
        <button class="mp-btn-install" style="margin-top:12px" onclick="submitImportPackage()">Install Package</button>
    </div>`;
    document.body.appendChild(modal);
}

function loadImportFile(input) {
    const file = input.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = function(e) {
        document.getElementById('mp-import-json').value = e.target.result;
    };
    reader.readAsText(file);
}

function submitImportPackage() {
    const errDiv = document.getElementById('mp-import-error');
    const okDiv = document.getElementById('mp-import-success');
    errDiv.style.display = 'none';
    okDiv.style.display = 'none';
    const raw = document.getElementById('mp-import-json').value.trim();
    let pkg;
    try {
        pkg = JSON.parse(raw);
    } catch (e) {
        errDiv.textContent = 'Invalid JSON: ' + e.message;
        errDiv.style.display = '';
        return;
    }
    apiFetch('/api/marketplace/catalog/install', {
        method: 'POST',
        body: JSON.stringify(pkg)
    }).then(r => r.json()).then(data => {
        if (data.error) {
            errDiv.textContent = data.error;
            errDiv.style.display = '';
        } else {
            okDiv.textContent = data.result || 'Package installed successfully';
            okDiv.style.display = '';
            loadCatalogStats();
            loadCatalog();
        }
    }).catch(e => {
        errDiv.textContent = 'Request failed: ' + e.message;
        errDiv.style.display = '';
    });
}

// Check for updates on load
async function checkForUpdate() {
    try {
        const res = await apiFetch(API + '/api/version/check');
        const data = await res.json();
        if (!data.update_available) return;
        const latest = data.latest_version;
        if (sessionStorage.getItem('upd-dismissed-' + latest)) return;
        const slot = document.getElementById('update-banner-slot');
        if (!slot) return;
        slot.innerHTML = `<div class="upd-banner">
            <span class="upd-label">Update available: v${latest}</span>
            <span class="upd-detail" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px;" onclick="shellRunCommand('pip install --upgrade familiar-agent')" title="Click to run in Shell">pip install --upgrade familiar-agent</span>
            <span class="upd-close" onclick="document.getElementById('update-banner-slot').innerHTML='';sessionStorage.setItem('upd-dismissed-${latest}','1')">&times;</span>
        </div>`;
    } catch(e) {}
}
checkForUpdate();

// Periodic refreshes (these use apiFetch which handles 401 gracefully)
PollManager.start('status', loadStatus, APP_CONFIG.poll.status);
PollManager.start('nodes', loadNodes, APP_CONFIG.poll.nodes);
// Background unread badge update
PollManager.start('unread-badge', async () => {
    try {
        const r = await apiFetch('/api/messages/unread');
        const d = await r.json();
        const badge = document.getElementById('msg-badge');
        if (d.unread > 0) { badge.textContent = d.unread > 99 ? '99+' : d.unread; badge.style.display = ''; }
        else { badge.style.display = 'none'; }
    } catch(e) {}
}, APP_CONFIG.poll.unreadBadge);

// ── Training / Corpus Panel ──────────────────────────────────
let _trMemories = [];
let _trEntriesOffset = 0;
const TR_ENTRIES_PER_PAGE = 10;

async function loadTraining() {
    await Promise.all([loadCorpusStatus(), loadCorpusMemories()]);
}

function switchTrainingTab(tab) {
    document.querySelectorAll('#training-content .tr-tab').forEach((t, i) => {
        t.classList.toggle('active', ['overview','memories','entries','coops'][i] === tab);
    });
    ['tr-overview-tab','tr-memories-tab','tr-entries-tab','tr-coops-tab'].forEach(id => {
        document.getElementById(id).style.display = 'none';
    });
    document.getElementById('tr-' + tab + '-tab').style.display = '';
    if (tab === 'entries') loadCorpusEntries();
}

async function loadCorpusStatus() {
    try {
        const res = await apiFetch(API + '/api/corpus/status');
        const data = await res.json();
        renderConsentCard(data);
        renderRunStats(data);
        renderPhiRejections(data);
    } catch(e) {
        document.getElementById('tr-consent-block').innerHTML =
            '<div class="tr-empty"><i class="fas fa-exclamation-circle"></i>Failed to load corpus status.</div>';
    }
}

function renderConsentCard(data) {
    const c = data.consent || {};
    const active = c.active === true;
    const chain = c.chain_block_hash
        ? `<div class="tr-consent-chain"><i class="fas fa-link"></i> Chain: ${c.chain_block_hash.slice(0,16)}…</div>` : '';
    const grantedAt = c.granted_at ? ` · Granted ${c.granted_at.slice(0,10)}` : '';
    document.getElementById('tr-consent-block').innerHTML = `
        <div class="tr-consent-card">
            <div class="tr-consent-header">
                <span style="font-size:1.3rem">🛡️</span>
                <span class="tr-consent-title">Co-op Corpus Consent</span>
                <span class="tr-consent-status ${active ? 'active' : 'inactive'}">
                    <i class="fas fa-${active ? 'check-circle' : 'times-circle'}"></i>
                    ${active ? 'Active' : 'Not granted'}
                </span>
            </div>
            <div style="font-size:0.82rem;color:var(--text-dim);line-height:1.5;">
                ${active
                    ? `Consent version <strong>${c.consent_version || data.consent_version}</strong>${grantedAt}. Memories marked <code>training_eligible: true</code> will be included in corpus exports and earn <strong>Dross</strong> when used by co-op peers.`
                    : `Grant consent to contribute your memories to community co-op LLM training. Each use earns <strong>Dross</strong> tokens. You can revoke at any time.`}
            </div>
            ${chain}
            <div class="tr-consent-actions">
                ${active
                    ? `<button class="tr-btn danger" onclick="setCorpusConsent('revoke', event)"><i class="fas fa-ban"></i> Revoke consent</button>`
                    : `<button class="tr-btn primary" onclick="setCorpusConsent('grant', event)"><i class="fas fa-check"></i> Grant consent v${data.consent_version}</button>`}
            </div>
        </div>`;
}

function renderRunStats(data) {
    const lr = data.latest_run;
    if (!lr) {
        document.getElementById('tr-run-stats').innerHTML = `
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
                <div class="tr-section-title">Pipeline</div>
                <button class="tr-btn primary" onclick="runCorpusPipeline(event)"><i class="fas fa-play"></i> Run now</button>
            </div>
            <div class="tr-empty" style="padding:20px"><i class="fas fa-database"></i>No runs yet. Grant consent and click Run now.</div>`;
        return;
    }
    const ts = lr.timestamp ? lr.timestamp.slice(0,16).replace('T',' ') : '';
    const total = data.total_exported_entries || 0;
    document.getElementById('tr-run-stats').innerHTML = `
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div class="tr-section-title">Pipeline · <span style="font-weight:400;text-transform:none;">${ts} UTC</span></div>
            <button class="tr-btn primary" onclick="runCorpusPipeline(event)"><i class="fas fa-play"></i> Run now</button>
        </div>
        <div class="tr-stat-grid">
            <div class="tr-stat-card"><div class="tr-stat-num">${lr.total_scanned||0}</div><div class="tr-stat-label">Scanned</div></div>
            <div class="tr-stat-card"><div class="tr-stat-num">${lr.eligible||0}</div><div class="tr-stat-label">Eligible</div></div>
            <div class="tr-stat-card"><div class="tr-stat-num">${lr.exported||0}</div><div class="tr-stat-label">Exported</div></div>
            <div class="tr-stat-card ${(lr.phi_rejected||0)>0?'danger':''}"><div class="tr-stat-num">${lr.phi_rejected||0}</div><div class="tr-stat-label">PHI rejected</div></div>
            <div class="tr-stat-card ${(lr.dedup_skipped||0)>0?'warn':''}"><div class="tr-stat-num">${lr.dedup_skipped||0}</div><div class="tr-stat-label">Dedup skipped</div></div>
            <div class="tr-stat-card"><div class="tr-stat-num">${total}</div><div class="tr-stat-label">Total corpus</div></div>
        </div>`;
}

function renderPhiRejections(data) {
    const rejections = (data.latest_run && data.latest_run.phi_rejection_files) || [];
    if (!rejections.length) { document.getElementById('tr-phi-block').innerHTML = ''; return; }
    document.getElementById('tr-phi-block').innerHTML = `
        <div class="tr-section-title">⚠️ PHI Rejections — last run</div>
        <div style="font-size:0.8rem;color:var(--text-dim);margin-bottom:6px;">
            These files contained HIPAA-identifiable data and were excluded. Edit the files to remove sensitive information, then re-run.
        </div>
        <div class="tr-phi-list">
            ${rejections.map(f => `<div class="tr-phi-item"><i class="fas fa-shield-virus"></i> <strong>${escapeHtml(f)}</strong></div>`).join('')}
        </div>`;
}

async function setCorpusConsent(action, event) {
    const btn = event.target.closest('button');
    btn.disabled = true;
    try {
        const res = await apiFetch(API + '/api/corpus/consent', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({action})
        });
        const data = await res.json();
        if (data.ok) await loadCorpusStatus();
        else alert('Error: ' + (data.error || 'Unknown'));
    } catch(e) { alert('Request failed.'); }
    finally { btn.disabled = false; }
}

async function runCorpusPipeline(event) {
    const btn = event.target.closest('button');
    const orig = btn.innerHTML;
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Running…';
    try {
        const res = await apiFetch(API + '/api/corpus/run', {method:'POST'});
        const data = await res.json();
        if (data.ok) { await loadCorpusStatus(); await loadCorpusMemories(); }
        else alert('Pipeline error: ' + (data.error || 'Unknown'));
    } catch(e) { alert('Request failed.'); }
    finally { btn.disabled = false; btn.innerHTML = orig; }
}

async function loadCorpusMemories() {
    try {
        const res = await apiFetch(API + '/api/corpus/memories');
        const data = await res.json();
        _trMemories = data.memories || [];
        renderFilteredMemories();
    } catch(e) {
        document.getElementById('tr-memory-list').innerHTML =
            '<div class="tr-empty"><i class="fas fa-exclamation-circle"></i>Failed to load memories.</div>';
    }
}

function renderFilteredMemories() {
    const search = (document.getElementById('tr-mem-search')?.value || '').toLowerCase();
    const filter = document.getElementById('tr-mem-filter')?.value || 'all';
    const mems = _trMemories.filter(m => {
        if (filter === 'eligible' && !m.training_eligible) return false;
        if (filter === 'ineligible' && m.training_eligible) return false;
        if (search && !m.filename.toLowerCase().includes(search) && !(m.topic||'').toLowerCase().includes(search)) return false;
        return true;
    });
    const el = document.getElementById('tr-memory-list');
    if (!mems.length) {
        el.innerHTML = '<div class="tr-empty"><i class="fas fa-file-alt"></i>No memories found.</div>';
        return;
    }
    el.innerHTML = '<div class="tr-memory-list">' + mems.map(m => {
        const domain = m.domain ? `<span class="tr-badge domain">${escapeHtml(m.domain)}</span>` : '';
        const qualStr = m.quality > 0 ? `<span style="font-size:0.72rem;color:var(--text-dim)">Q ${m.quality.toFixed(2)}</span>` : '';
        return `<div class="tr-memory-row">
            <label style="display:flex;align-items:center;cursor:pointer;margin:0;">
                <input type="checkbox" ${m.training_eligible?'checked':''} onchange="toggleMemoryEligible('${escapeHtml(m.filename)}', this.checked)" style="margin-right:8px;cursor:pointer;">
            </label>
            <div style="flex:1;min-width:0;">
                <div class="tr-memory-name" title="${escapeHtml(m.filename)}">${escapeHtml(m.filename)}</div>
                ${m.topic?`<div class="tr-memory-topic">${escapeHtml(m.topic)}</div>`:''}
            </div>
            <div class="tr-memory-meta">
                ${domain}
                <span class="tr-badge ${m.training_eligible?'eligible':'ineligible'}">${m.training_eligible?'Eligible':'Not eligible'}</span>
                ${qualStr}
            </div>
        </div>`;
    }).join('') + '</div>';
}

async function toggleMemoryEligible(filename, eligible) {
    try {
        const res = await apiFetch(API + '/api/corpus/memories/toggle', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({filename, eligible})
        });
        const data = await res.json();
        if (data.ok) {
            const m = _trMemories.find(m => m.filename === filename);
            if (m) m.training_eligible = eligible;
        } else {
            alert('Error: ' + (data.error || 'Unknown'));
            await loadCorpusMemories();
        }
    } catch(e) { await loadCorpusMemories(); }
}

async function loadCorpusEntries() {
    const el = document.getElementById('tr-entry-list');
    el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
    try {
        const res = await apiFetch(API + `/api/corpus/entries?limit=${TR_ENTRIES_PER_PAGE}&offset=${_trEntriesOffset}`);
        const data = await res.json();
        const total = data.total || 0;
        const entries = data.entries || [];
        const page = Math.floor(_trEntriesOffset / TR_ENTRIES_PER_PAGE) + 1;
        const totalPages = Math.ceil(total / TR_ENTRIES_PER_PAGE) || 1;
        document.getElementById('tr-entries-count').textContent = `${total} entries · page ${page} of ${totalPages}`;
        if (!entries.length) {
            el.innerHTML = '<div class="tr-empty"><i class="fas fa-database"></i>No corpus entries yet. Run the pipeline to populate.</div>';
            return;
        }
        el.innerHTML = '<div class="tr-entry-list">' + entries.map(e => {
            const q = (e.quality_score || 0).toFixed(2);
            const qColor = e.quality_score >= 0.7 ? 'var(--success)' : e.quality_score >= 0.5 ? '#f1c40f' : 'var(--error)';
            const phiBadge = e.phi_scrubbed ? '<span class="tr-badge phi"><i class="fas fa-shield-virus"></i> PHI scrubbed</span>' : '';
            return `<div class="tr-entry-card">
                <div class="tr-entry-header">
                    <span class="tr-entry-id">${escapeHtml(e.id||'')}</span>
                    <span class="tr-badge domain">${escapeHtml(e.domain||'general')}</span>
                    ${phiBadge}
                    <span class="tr-entry-q" style="color:${qColor}">Q ${q}</span>
                    <button class="tr-btn danger" style="margin-left:auto;padding:4px 10px;" onclick="deleteCorpusEntry('${escapeHtml(e.id||'')}', this)"><i class="fas fa-trash"></i></button>
                </div>
                <div class="tr-entry-instruction">${escapeHtml(e.instruction||'')}</div>
                <div class="tr-entry-response">${escapeHtml((e.response||'').slice(0,300))}</div>
                <div class="tr-entry-footer">
                    <span class="tr-entry-hash">contributor: ${escapeHtml(e.contributor_hash||'—')}</span>
                    <span class="tr-badge ${e.consent_version?'domain':'ineligible'}">consent ${escapeHtml(e.consent_version||'?')}</span>
                </div>
            </div>`;
        }).join('') + '</div>';
    } catch(e) {
        el.innerHTML = '<div class="tr-empty"><i class="fas fa-exclamation-circle"></i>Failed to load entries.</div>';
    }
}

function trEntriesPage(delta) {
    _trEntriesOffset = Math.max(0, _trEntriesOffset + delta * TR_ENTRIES_PER_PAGE);
    loadCorpusEntries();
}

async function deleteCorpusEntry(id, btn) {
    if (!confirm('Remove this entry from the corpus?')) return;
    btn.disabled = true;
    try {
        const res = await apiFetch(API + '/api/corpus/entries/' + encodeURIComponent(id), {
            method:'DELETE'
        });
        const data = await res.json();
        if (data.ok) { await loadCorpusEntries(); await loadCorpusStatus(); }
        else alert('Error: ' + (data.error || 'Unknown'));
    } catch(e) { alert('Request failed.'); }
    finally { btn.disabled = false; }
}

// escapeHtml — defined in core.js
// ── end Training / Corpus Panel ──────────────────────────────

