// mesh.js — Mesh network panel, topology, trust
// Extracted from dashboard.html (lines 7073-7968)

// ── Mesh Network Panel ─────────────────────────────────
// _meshPollInterval — managed by PollManager('mesh')

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

    try {
        const [statusRes, nodesRes, peersRes, trustRes, memRes, modelsRes, usageRes] =
            await Promise.allSettled([
                apiFetch(API + '/api/mesh/status').then(r => r.json()),
                apiFetch(API + '/api/nodes').then(r => r.json()),
                apiFetch(API + '/api/mesh/peers').then(r => r.json()),
                apiFetch(API + '/api/mesh/trust').then(r => r.json()),
                apiFetch(API + '/api/mesh/memory').then(r => r.json()),
                apiFetch(API + '/api/mesh/models').then(r => r.json()),
                apiFetch(API + '/api/mesh/usage').then(r => r.json()),
            ]);

        const status = statusRes.status === 'fulfilled' ? statusRes.value : {};
        const nodes = nodesRes.status === 'fulfilled' ? nodesRes.value : {};
        const peers = peersRes.status === 'fulfilled' ? peersRes.value : {};
        const trust = trustRes.status === 'fulfilled' ? trustRes.value : {};
        const memory = memRes.status === 'fulfilled' ? memRes.value : {};
        const models = modelsRes.status === 'fulfilled' ? modelsRes.value : {};
        const usage = usageRes.status === 'fulfilled' ? usageRes.value : {};

        container.innerHTML = '';
        renderMeshTopology(status, nodes, peers, trust);
        renderTransports(status);
        renderMeshNodes(nodes, trust);
        renderMeshPeers(peers);
        renderMeshTrust(trust);
        renderMeshMemory(memory);
        renderMeshModels(models);
        renderMeshDross(usage);
        renderMeshRendezvous();
        renderMeshControls(status);
        updateMeshSidebar(nodes);

        // Start polling
        PollManager.start('mesh', () => {
            const panel = document.querySelector('.nav-item.active');
            if (panel && panel.dataset.panel === 'nodes') loadMeshStatus();
            else PollManager.stop('mesh');
        }, APP_CONFIG.poll.mesh);
    } catch (e) {
        container.innerHTML = '<p>Error loading mesh status</p>';
    }
}

function renderMeshTopology(status, nodes, peers, trust) {
    const container = document.getElementById('mesh-topology');
    const svg = document.getElementById('mesh-topo-svg');
    if (!svg) return;

    const cx = 300, cy = 200;
    const trustRecords = trust.records || [];
    const trustMap = {};
    trustRecords.forEach(r => { trustMap[r.node_id] = r; });

    // Merge nodes + peers into unified peer list, dedup by id
    const seen = new Set();
    const allPeers = [];
    (nodes.nodes || []).forEach(n => {
        if (seen.has(n.id)) return;
        seen.add(n.id);
        const tr = trustMap[n.id] || {};
        allPeers.push({
            id: n.id, name: n.name || n.id.substring(0, 8),
            type: n.type || 'node', trust_level: tr.trust_level || 'unknown',
            transport: tr.transport || 'lan',
            tool_count: n.tools?.length || 0,
            stale: false, is_node: true
        });
    });
    (peers.peers || []).forEach(p => {
        const pid = p.node_id || p.id;
        if (seen.has(pid)) return;
        seen.add(pid);
        const tr = trustMap[pid] || {};
        const lastSeen = p.last_seen ? (Date.now() - new Date(p.last_seen).getTime() > 300000) : false;
        allPeers.push({
            id: pid, name: p.node_name || pid.substring(0, 8),
            type: 'peer', trust_level: tr.trust_level || p.trust_level || 'unknown',
            transport: tr.transport || p.discovery_source || 'lan',
            tool_count: 0, stale: lastSeen, is_node: false
        });
    });

    // Center node info
    const centerName = document.getElementById('creature-name')?.textContent || status.node_name || 'Me';
    const centerSub = status.host ? `${status.host}:${status.port}` : 'gateway';

    // Build SVG content
    let svgHTML = `<defs>
        <filter id="topo-glow" x="-50%" y="-50%" width="200%" height="200%">
            <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur"/>
            <feComposite in="SourceGraphic" in2="blur" operator="over"/>
        </filter>
    </defs>`;

    if (allPeers.length === 0) {
        // Empty state: center node with ripple rings
        svgHTML += `<circle cx="${cx}" cy="${cy}" r="60" fill="none" stroke="var(--accent)" stroke-width="1" opacity="0.2">
            <animate attributeName="r" values="45;120" dur="3s" repeatCount="indefinite"/>
            <animate attributeName="opacity" values="0.3;0" dur="3s" repeatCount="indefinite"/>
        </circle>
        <circle cx="${cx}" cy="${cy}" r="60" fill="none" stroke="var(--accent)" stroke-width="1" opacity="0.2">
            <animate attributeName="r" values="45;120" dur="3s" begin="1s" repeatCount="indefinite"/>
            <animate attributeName="opacity" values="0.3;0" dur="3s" begin="1s" repeatCount="indefinite"/>
        </circle>
        <circle cx="${cx}" cy="${cy}" r="60" fill="none" stroke="var(--accent)" stroke-width="1" opacity="0.2">
            <animate attributeName="r" values="45;120" dur="3s" begin="2s" repeatCount="indefinite"/>
            <animate attributeName="opacity" values="0.3;0" dur="3s" begin="2s" repeatCount="indefinite"/>
        </circle>`;
        svgHTML += `<circle cx="${cx}" cy="${cy}" r="35" class="topo-center-circle"/>`;
        svgHTML += `<text x="${cx}" y="${cy + 4}" class="topo-center-label">${escapeHtml(centerName)}</text>`;
        svgHTML += `<text x="${cx}" y="${cy + 52}" class="topo-empty-text">No peers discovered yet</text>`;
    } else {
        // Calculate peer positions
        const radius1 = 130, radius2 = 105;
        const useTwo = allPeers.length > 8;
        const ring1 = useTwo ? allPeers.slice(0, 8) : allPeers;
        const ring2 = useTwo ? allPeers.slice(8) : [];
        const positions = [];

        ring1.forEach((p, i) => {
            const angle = (2 * Math.PI * i / ring1.length) - Math.PI / 2;
            positions.push({ ...p, x: cx + radius1 * Math.cos(angle), y: cy + radius1 * Math.sin(angle) });
        });
        ring2.forEach((p, i) => {
            const angle = (2 * Math.PI * i / ring2.length) - Math.PI / 2 + Math.PI / ring2.length;
            positions.push({ ...p, x: cx + radius2 * Math.cos(angle), y: cy + radius2 * Math.sin(angle) });
        });

        // Draw connection lines first (behind nodes)
        positions.forEach(p => {
            const staleClass = p.stale ? ' stale' : '';
            const animStyle = p.trust_level === 'trusted' && !p.stale
                ? ' stroke-dasharray="8 4" style="animation: topo-dash 1s linear infinite"' : '';
            svgHTML += `<line x1="${cx}" y1="${cy}" x2="${p.x}" y2="${p.y}" class="topo-link trust-${p.trust_level}${staleClass}"${animStyle}/>`;
        });

        // Transport icons at midpoints
        positions.forEach(p => {
            const mx = (cx + p.x) / 2, my = (cy + p.y) / 2;
            let icon = '\u{1F4E1}'; // satellite for LAN
            if (p.transport === 'tor') icon = '\u{1F9C5}'; // onion
            else if (p.transport === 'mdns') icon = '\u{1F4E1}';
            svgHTML += `<text x="${mx}" y="${my}" class="topo-transport-icon">${icon}</text>`;
        });

        // Center node (on top of lines)
        svgHTML += `<circle cx="${cx}" cy="${cy}" r="35" class="topo-center-circle"/>`;
        svgHTML += `<text x="${cx}" y="${cy + 1}" class="topo-center-label">${escapeHtml(centerName)}</text>`;
        svgHTML += `<text x="${cx}" y="${cy + 48}" class="topo-center-sub">${escapeHtml(centerSub)}</text>`;

        // Peer nodes
        positions.forEach(p => {
            const r = Math.min(28, 18 + Math.floor(p.tool_count / 10) * 2);
            const staleClass = p.stale ? ' stale' : '';
            const nameStr = p.name.length > 12 ? p.name.substring(0, 11) + '\u2026' : p.name;
            svgHTML += `<circle cx="${p.x}" cy="${p.y}" r="${r}" class="topo-peer-circle trust-${p.trust_level}${staleClass}" data-peer-id="${escapeHtml(p.id)}" onclick="topoClickPeer('${escapeHtml(p.id)}')"/>`;
            svgHTML += `<text x="${p.x}" y="${p.y + r + 14}" class="topo-label">${escapeHtml(nameStr)}</text>`;
            svgHTML += `<text x="${p.x}" y="${p.y + r + 25}" class="topo-meta">${escapeHtml(p.type)} \u2022 ${p.tool_count} tools</text>`;
        });
    }

    svg.innerHTML = svgHTML;
    container.style.display = '';
}

function topoClickPeer(peerId) {
    // Find matching trust record card and scroll to it
    const trustEl = document.getElementById('mesh-trust');
    if (!trustEl) return;
    const cards = trustEl.querySelectorAll('.card');
    for (const card of cards) {
        card.classList.remove('topo-highlight');
        const strong = card.querySelector('strong');
        if (strong && (card.innerHTML.includes(peerId) || card.innerHTML.includes(peerId.substring(0, 8)))) {
            card.classList.add('topo-highlight');
            card.scrollIntoView({ behavior: 'smooth', block: 'center' });
            setTimeout(() => card.classList.remove('topo-highlight'), 3000);
        }
    }
}

function renderTransports(status) {
    const el = document.getElementById('mesh-transports');
    if (!status.transports) { el.style.display = 'none'; return; }
    const t = status.transports;
    const ws = t.websocket || {};
    const mdns = t.mdns || {};
    const tor = t.tor || {};
    const torAvail = typeof tor.available === 'object' ? tor.available : { available: tor.available };

    let mdnsColor = mdns.running ? 'green' : 'red';
    let mdnsDetail = mdns.running ? `${mdns.peer_count || 0} peers` : 'Disabled';

    let torColor = tor.running ? 'green' : (torAvail.available ? 'yellow' : 'red');
    let torDetail = tor.running ? '' : (torAvail.available ? 'Available' : 'Not installed');

    el.style.display = '';
    el.innerHTML = `<h3>Transports</h3>
        <div class="mesh-transport-row">
            <div class="mesh-transport-dot ${ws.running ? 'green' : 'red'}"></div>
            <span class="label">WebSocket (LAN)</span>
            <span class="detail">${ws.address || ''}</span>
        </div>
        <div class="mesh-transport-row">
            <div class="mesh-transport-dot ${mdnsColor}"></div>
            <span class="label">mDNS Discovery</span>
            <span class="detail">${mdnsDetail}</span>
            <button class="btn btn-secondary mesh-btn-sm" onclick="toggleDiscovery(${mdns.running})">${mdns.running ? 'Disable' : 'Enable'}</button>
        </div>
        <div class="mesh-transport-row">
            <div class="mesh-transport-dot ${torColor}"></div>
            <span class="label">Tor Hidden Service</span>
            <span class="detail">${tor.running ? '' : torDetail}</span>
            ${tor.running ? `<span class="mesh-onion-addr" title="Click to copy" onclick="navigator.clipboard.writeText('${tor.onion_address}')">${tor.onion_address}</span>` : ''}
            ${torAvail.available || tor.running ? `<button class="btn btn-secondary mesh-btn-sm" onclick="toggleTor(${tor.running})">${tor.running ? 'Disable' : 'Enable'}</button>` : ''}
        </div>
        ${!torAvail.available && !tor.running && torAvail.missing ? `<div class="mesh-info-pad">Missing: ${torAvail.missing.join(', ')}</div>` : ''}
        <div class="mesh-transport-row" id="rdv-transport-row" style="display:none">
            <div class="mesh-transport-dot" id="rdv-dot"></div>
            <span class="label"><i class="fas fa-satellite-dish"></i> Rendezvous</span>
            <span class="detail" id="rdv-detail"></span>
            <button class="btn btn-secondary mesh-btn-sm" id="rdv-toggle-btn" onclick="toggleRendezvous()"></button>
        </div>`;

    // Fetch rendezvous status asynchronously
    apiFetch(API + '/api/mesh/rendezvous/status').then(r => r.json()).then(rdv => {
        const row = document.getElementById('rdv-transport-row');
        if (!row) return;
        row.style.display = '';
        const dot = document.getElementById('rdv-dot');
        const detail = document.getElementById('rdv-detail');
        const btn = document.getElementById('rdv-toggle-btn');
        dot.className = 'mesh-transport-dot ' + (rdv.running ? 'green' : rdv.enabled ? 'yellow' : 'red');
        detail.textContent = rdv.running ? `${(rdv.servers||[]).length} server(s)` : (rdv.enabled ? 'Enabled (no servers)' : 'Disabled');
        btn.textContent = rdv.enabled ? 'Disable' : 'Enable';
        btn.onclick = () => toggleRendezvous(rdv.enabled);
    }).catch(() => {});
}

function renderMeshNodes(nodes, trust) {
    const el = document.getElementById('mesh-nodes');
    const list = nodes.nodes || [];
    el.style.display = '';

    if (list.length === 0) {
        el.innerHTML = `<h3>Connected Nodes</h3>
            <div class="card">
                <p>No nodes connected</p>
                <p class="mesh-desc" style="margin-top:10px">Connect a node with:<br>
                <code style="display:block;margin-top:8px;padding:12px;background:var(--bg-input);border-radius:6px;font-size:0.85rem">familiar-mesh node --gateway ws://YOUR_IP:18789 --key YOUR_KEY</code></p>
            </div>`;
        return;
    }

    const trustRecords = trust.records || [];
    const trustMap = {};
    trustRecords.forEach(r => { trustMap[r.node_id] = r; });

    el.innerHTML = `<h3>Connected Nodes (${list.length})</h3>` + list.map(n => {
        const tr = trustMap[n.id] || {};
        const tl = tr.trust_level || 'unknown';
        const transport = (tr.transport === 'tor') ? '<i class="fas fa-user-secret" title="Tor"></i>' : '<i class="fas fa-wifi" title="LAN"></i>';
        return `<div class="mesh-node-card">
            <div class="icon">${transport}</div>
            <div class="info">
                <div class="name">${n.name}</div>
                <div class="meta">${n.type} &bull; ${n.tools?.length || 0} tools</div>
            </div>
            <span class="trust-badge ${tl}">${tl}</span>
            <div class="node-dot"></div>
        </div>`;
    }).join('');
}

function renderMeshPeers(peers) {
    const el = document.getElementById('mesh-peers');
    const list = peers.peers || [];
    if (list.length === 0) { el.style.display = 'none'; return; }

    el.style.display = '';
    el.innerHTML = `<h3>Discovered Peers (${list.length})</h3>` + list.map(p => {
        const sourceIcon = p.discovery_source === 'rendezvous' ? '<i class="fas fa-satellite-dish" title="Rendezvous"></i>' : p.discovery_source === 'mdns' ? '<i class="fas fa-broadcast-tower" title="mDNS"></i>' : '<i class="fas fa-hand-pointer" title="Manual"></i>';
        const tl = p.trust_level || 'unknown';
        const stale = p.last_seen && (Date.now() - new Date(p.last_seen).getTime() > 300000);
        return `<div class="mesh-node-card ${stale ? 'stale' : ''}">
            <div class="icon">${sourceIcon}</div>
            <div class="info">
                <div class="name">${p.node_name || p.node_id}</div>
                <div class="meta">${p.host}:${p.port}${stale ? ' &bull; <span class="mesh-stale">stale</span>' : ''}</div>
            </div>
            <span class="trust-badge ${tl}">${tl}</span>
        </div>`;
    }).join('');
}

function renderMeshTrust(trust) {
    const el = document.getElementById('mesh-trust');
    el.style.display = '';

    const cryptoStatus = trust.has_crypto
        ? '<span class="activity-status-ok"><i class="fas fa-lock"></i> Signal Protocol active</span>'
        : '<span class="mesh-desc"><i class="fas fa-lock-open"></i> Encryption unavailable (install pynacl)</span>';

    const fp = trust.our_fingerprint || 'N/A';
    const records = trust.records || [];

    let recordsHtml = '';
    if (records.length === 0) {
        recordsHtml = '<p class="mesh-desc">No peer trust records yet</p>';
    } else {
        recordsHtml = records.map(r => {
            const permKeys = ['list_skills','invoke_skills','request_memory','delegate_tasks','share_memory_to','share_markdown_memories','share_models','send_messages'];
            const perms = r.permissions || {};
            const permToggles = permKeys.map(k =>
                `<div class="mesh-perm-row">
                    <span>${k.replace(/_/g, ' ')}</span>
                    <label class="mesh-toggle"><input type="checkbox" ${perms[k] ? 'checked' : ''} onchange="toggleMeshPerm('${r.node_id}','${k}',this.checked)"><span class="slider"></span></label>
                </div>`
            ).join('');

            const actions = r.trust_level === 'pending'
                ? `<button class="btn btn-secondary mesh-btn-sm" onclick="approvePeer('${r.node_id}')">Approve</button>
                   <button class="btn btn-secondary mesh-btn-sm" style="margin-left:4px" onclick="blockPeer('${r.node_id}')">Block</button>`
                : r.trust_level === 'trusted'
                ? `<button class="btn btn-secondary mesh-btn-sm" onclick="blockPeer('${r.node_id}')">Block</button>`
                : r.trust_level === 'blocked'
                ? `<button class="btn btn-secondary mesh-btn-sm" onclick="approvePeer('${r.node_id}')">Unblock</button>`
                : '';

            return `<div class="card" style="margin-bottom:10px">
                <div class="mesh-card-header">
                    <strong>${r.node_name || r.node_id.substring(0,8)}</strong>
                    <span class="trust-badge ${r.trust_level}">${r.trust_level}</span>
                    ${r.has_session ? '<i class="fas fa-lock activity-status-ok" style="font-size:0.8rem" title="Encrypted session"></i>' : ''}
                    <span class="mesh-spacer"></span>
                    ${actions}
                </div>
                ${r.verification_code ? `<div class="mesh-verification-code">${r.verification_code}</div><div class="mesh-info" style="text-align:center;margin-bottom:8px">Verification code &mdash; compare with peer</div>` : ''}
                ${permToggles}
            </div>`;
        }).join('');
    }

    el.innerHTML = `<h3>Trust &amp; Security</h3>
        <div class="mesh-section-gap">${cryptoStatus}</div>
        <div class="mesh-fingerprint">Our fingerprint: <code>${fp}</code></div>
        ${recordsHtml}`;
}

function renderMeshMemory(memory) {
    const el = document.getElementById('mesh-memory');
    el.style.display = '';
    if (!memory.initialized) {
        el.innerHTML = '<h3>Shared Memory</h3><p class="mesh-desc">Memory bridge not initialized</p>';
        return;
    }
    const s = memory;
    const topics = s.markdown_topics || [];
    const shared = s.shared_topics || [];
    const received = s.received_topics || [];
    const totalWords = s.total_words || 0;
    const totalTopics = s.total_topics || 0;

    // Summary stats
    let html = `<h3>Shared Memory</h3>
        <div class="mesh-dross-grid">
            <div class="mesh-dross-stat"><div class="value">${totalTopics}</div><div class="label">Knowledge Topics</div></div>
            <div class="mesh-dross-stat"><div class="value">${totalWords.toLocaleString()}</div><div class="label">Total Words</div></div>
            <div class="mesh-dross-stat"><div class="value">${shared.length}</div><div class="label">Shared with Peers</div></div>
            <div class="mesh-dross-stat"><div class="value">${received.length}</div><div class="label">Received from Peers</div></div>
        </div>`;

    // HIPAA warning
    if (s.hipaa_mode) {
        html += '<div class="mesh-hipaa-warning"><i class="fas fa-exclamation-triangle"></i> HIPAA mode &mdash; PHI sharing restricted</div>';
    }

    // Your Knowledge — local topics
    if (topics.length > 0) {
        html += `<div class="mesh-knowledge-header" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'':'none'">
            <h4><i class="fas fa-brain"></i> Your Knowledge</h4>
            <span class="count">${topics.length} topics &bull; ${totalWords.toLocaleString()} words <i class="fas fa-chevron-down mesh-info"></i></span>
        </div>
        <div>`;
        for (const t of topics) {
            const tags = (t.tags || []).filter(tg => tg !== 'mesh-received').map(tg =>
                `<span class="topic-tag">${tg}</span>`
            ).join('');
            const updated = t.updated ? formatDate(t.updated, 'date') : '';
            html += `<div class="mesh-topic-item">
                <span class="topic-name">${t.topic}${tags}</span>
                <span class="topic-rev" title="Revision ${t.revision}">v${t.revision}</span>
                <span class="topic-words">${(t.word_count || 0).toLocaleString()} words</span>
                <span class="topic-meta">${updated}</span>
            </div>`;
        }
        html += '</div>';
    }

    // Shared with Peers
    if (shared.length > 0) {
        html += `<div class="mesh-knowledge-header" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'':'none'">
            <h4><i class="fas fa-share-alt"></i> Shared with Peers</h4>
            <span class="count">${shared.length} topics <i class="fas fa-chevron-down" style="font-size:0.7rem"></i></span>
        </div>
        <div>`;
        for (const t of shared) {
            const sharedAt = t.shared_at ? formatDate(t.shared_at, 'date') : '';
            html += `<div class="mesh-topic-item">
                <span class="topic-name">${t.key}</span>
                <span class="topic-meta">scope: ${t.scope}</span>
                <span class="topic-meta">${sharedAt}</span>
            </div>`;
        }
        html += '</div>';
    }

    // Received from Peers
    if (received.length > 0) {
        html += `<div class="mesh-knowledge-header" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'':'none'">
            <h4><i class="fas fa-download"></i> Received from Peers</h4>
            <span class="count">${received.length} topics <i class="fas fa-chevron-down" style="font-size:0.7rem"></i></span>
        </div>
        <div>`;
        for (const t of received) {
            const recAt = t.received_at ? formatDate(t.received_at, 'date') : '';
            const genesis = t.origin_genesis ? `<span class="topic-tag">genesis</span>` : '';
            html += `<div class="mesh-topic-item">
                <span class="topic-name">${t.key}${genesis}</span>
                <span class="mesh-origin-badge">${t.origin_name || t.origin_id?.substring(0,8) || 'peer'}</span>
                <span class="topic-meta">${recAt}</span>
            </div>`;
        }
        html += '</div>';
    }

    // Empty state
    if (topics.length === 0 && shared.length === 0 && received.length === 0) {
        html += '<p style="color:var(--text-dim);margin-top:12px">No knowledge topics yet. Memories will appear here as your familiar learns.</p>';
    }

    el.innerHTML = html;
}

function renderMeshModels(models) {
    const el = document.getElementById('mesh-models');
    el.style.display = '';
    if (!models.initialized) {
        el.innerHTML = '<h3>Model Sharing</h3><p style="color:var(--text-dim)">Model bridge not initialized</p>';
        return;
    }
    const s = models;
    el.innerHTML = `<h3>Model Sharing</h3>
        <div class="mesh-dross-grid">
            <div class="mesh-dross-stat"><div class="value">${s.local_models || 0}</div><div class="label">Local Models</div></div>
            <div class="mesh-dross-stat"><div class="value">${s.shareable_models || 0}</div><div class="label">Shareable</div></div>
            <div class="mesh-dross-stat"><div class="value">${s.peer_catalogs || 0}</div><div class="label">Peer Catalogs</div></div>
            <div class="mesh-dross-stat"><div class="value">${s.active_downloads || 0}</div><div class="label">Active Downloads</div></div>
        </div>`;
}

function renderMeshDross(usage) {
    const el = document.getElementById('mesh-dross');
    el.style.display = '';
    if (!usage.initialized) {
        el.innerHTML = '<h3>Dross Economy</h3><p class="mesh-desc">Usage tracker not initialized</p>';
        return;
    }
    const s = usage;
    const byType = s.by_type || {};
    let typeRows = Object.entries(byType).map(([k, v]) =>
        `<tr><td>${k.replace(/_/g, ' ')}</td><td class="right">${v.count || 0}</td><td class="right">${(v.dross || 0).toFixed(2)}</td></tr>`
    ).join('');

    el.innerHTML = `<h3>Dross Economy</h3>
        <div class="mesh-dross-grid">
            <div class="mesh-dross-stat mesh-dross-earned"><div class="value">${(s.earned || 0).toFixed(2)}</div><div class="label">Earned (provided)</div></div>
            <div class="mesh-dross-stat mesh-dross-spent"><div class="value">${(s.spent || 0).toFixed(2)}</div><div class="label">Spent (consumed)</div></div>
            <div class="mesh-dross-stat"><div class="value">${s.resources_provided || 0}</div><div class="label">Resources Provided</div></div>
            <div class="mesh-dross-stat"><div class="value">${s.resources_consumed || 0}</div><div class="label">Resources Consumed</div></div>
        </div>
        ${typeRows ? `<table class="mesh-dross-table"><thead><tr><th>Type</th><th class="right">Count</th><th class="right">Dross</th></tr></thead><tbody>${typeRows}</tbody></table>` : ''}`;
}

async function renderMeshRendezvous() {
    const el = document.getElementById('mesh-rendezvous');
    el.style.display = '';
    const COMMUNITY_URL = 'https://rdv.familiar.community';

    // Always fetch rendezvous status, even without gateway
    let rdv = {enabled: false, running: false, servers: [], token_set: false};
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/status');
        rdv = await res.json();
    } catch(e) {}

    // Check local server status
    let localRunning = false;
    try {
        const lr = await apiFetch(API + '/api/mesh/rendezvous/local/status');
        const ld = await lr.json();
        localRunning = ld.running;
    } catch(e) {}

    const servers = rdv.servers || [];
    const hasCommunity = servers.includes(COMMUNITY_URL);
    const statusDot = rdv.running ? 'green' : rdv.enabled ? 'yellow' : 'red';
    const statusText = rdv.running
        ? `Active — announcing to ${servers.length} server(s)`
        : rdv.enabled ? 'Enabled (waiting for gateway)' : 'Disabled';

    let serverListHtml = servers.length === 0
        ? '<div class="mesh-desc" style="padding:6px 0">No servers configured yet. Use one of the quick-start options below.</div>'
        : servers.map(s => `<div class="rdv-server-item">
            <span class="rdv-server-url"><i class="fas fa-satellite-dish"></i> ${_esc(s)}${s === COMMUNITY_URL ? ' <span class="mesh-community-badge">community</span>' : ''}</span>
            <button class="btn btn-secondary mesh-btn-xs" onclick="removeRdvServer('${_esc(s)}')"><i class="fas fa-times"></i></button>
        </div>`).join('');

    const localStatusDot = localRunning ? 'green' : 'red';
    const localStatusLabel = localRunning ? 'Running on port 18790' : 'Not running';

    el.innerHTML = `<h3><i class="fas fa-satellite-dish"></i> Rendezvous Discovery</h3>
        <div class="mesh-desc">
            Internet-wide peer discovery. The rendezvous server is just a matchmaker — all connections are direct encrypted P2P. Built-in anti-spam: rate limiting, per-IP caps, and give-to-get peer listing.
        </div>
        <div class="mesh-transport-row mesh-section-gap">
            <div class="mesh-transport-dot ${statusDot}"></div>
            <span class="label">${statusText}</span>
            <button class="btn btn-secondary mesh-btn-sm" onclick="toggleRendezvous(${rdv.enabled})">${rdv.enabled ? 'Disable' : 'Enable'}</button>
        </div>
        <div class="mesh-quickstart">
            <button class="btn ${hasCommunity ? 'btn-secondary' : 'btn-primary'} mesh-btn-lg" onclick="joinCommunityRdv()" ${hasCommunity ? 'disabled' : ''}>
                <i class="fas fa-globe"></i> ${hasCommunity ? 'Community Server Added' : 'Join Community Server'}
            </button>
            <button class="btn btn-secondary mesh-btn-lg" onclick="startLocalRdv()">
                <i class="fas fa-server"></i> ${localRunning ? 'Local Server Running' : 'Start My Own'}
            </button>
        </div>
        <div style="margin-bottom:8px">
            <div class="mesh-label">Servers</div>
            ${serverListHtml}
        </div>
        <div class="mesh-input-group">
            <input type="text" id="rdv-server-input" placeholder="http://rdv.example.com:18790" class="mesh-input">
            <button class="btn btn-secondary mesh-btn-md" onclick="addRdvServer()"><i class="fas fa-plus"></i> Add</button>
        </div>
        <div class="mesh-input-group">
            <input type="password" id="rdv-token-input" placeholder="Auth token (optional)" value="${rdv.token_set ? '••••••••' : ''}" class="mesh-input">
            <span class="mesh-info">Token</span>
        </div>
        ${localRunning ? `<div class="mesh-status-box">
            <div class="mesh-transport-row">
                <div class="mesh-transport-dot ${localStatusDot}"></div>
                <span class="label">Local server: ${localStatusLabel}</span>
            </div>
            <div class="mesh-info" style="margin-top:4px">
                Others can reach your server at <code>http://&lt;your-ip&gt;:18790</code>
            </div>
        </div>` : ''}`;
}

async function addRdvServer() {
    const inp = document.getElementById('rdv-server-input');
    const url = (inp?.value || '').trim();
    if (!url) return;
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/servers', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({url})
        });
        const data = await res.json();
        if (data.error) { alert(data.error); return; }
        inp.value = '';
        renderMeshRendezvous();
    } catch(e) { alert('Error: ' + e.message); }
}

async function removeRdvServer(url) {
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/servers', {
            method: 'DELETE',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({url})
        });
        const data = await res.json();
        if (data.error) { alert(data.error); return; }
        renderMeshRendezvous();
    } catch(e) { alert('Error: ' + e.message); }
}

async function joinCommunityRdv() {
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/servers', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({url: 'https://rdv.familiar.community'})
        });
        const data = await res.json();
        if (data.error) { alert(data.error); return; }
        // Auto-enable rendezvous when joining community
        await apiFetch(API + '/api/mesh/rendezvous/enable', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({enabled: true})
        });
        renderMeshRendezvous();
    } catch(e) { alert('Error: ' + e.message); }
}

async function startLocalRdv() {
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/local/start', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({})
        });
        const data = await res.json();
        if (data.error) { alert(data.error); return; }
        // Auto-add local server to the server list
        const port = data.port || 18790;
        await apiFetch(API + '/api/mesh/rendezvous/servers', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({url: `http://localhost:${port}`})
        });
        await apiFetch(API + '/api/mesh/rendezvous/enable', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({enabled: true})
        });
        renderMeshRendezvous();
    } catch(e) { alert('Error: ' + e.message); }
}

function renderMeshControls(status) {
    const el = document.getElementById('mesh-controls');
    el.style.display = '';
    const tor = status.transports?.tor || {};
    const nodeName = status.config?.node_name || status.node_name || 'familiar';

    el.innerHTML = `<h3>Gateway Controls</h3>
        <div class="msg-name-edit">
            <span class="mesh-name-label">Display Name:</span>
            <input type="text" id="mesh-node-name" value="${_esc(nodeName)}" maxlength="32" placeholder="Your display name">
            <button onclick="saveMeshNodeName()"><i class="fas fa-check"></i></button>
        </div>
        <div class="mesh-btn-group">
            <button class="btn btn-secondary" onclick="showSecretKey()"><i class="fas fa-key"></i> Show Secret Key</button>
            <button class="btn btn-secondary" onclick="showConnectionString()"><i class="fas fa-link"></i> Connection String (WS)</button>
            ${tor.running ? `<button class="btn btn-secondary" onclick="showTorConnectionString('${tor.onion_address}')"><i class="fas fa-user-secret"></i> Connection String (Tor)</button>` : ''}
        </div>`;
}

async function saveMeshNodeName() {
    const inp = document.getElementById('mesh-node-name');
    const name = (inp?.value || '').trim();
    if (name.length < 2 || name.length > 32) {
        alert('Name must be 2-32 characters');
        return;
    }
    try {
        const res = await apiFetch('/api/mesh/config/name', {
            method: 'POST',
            headers: {'Content-Type':'application/json'},
            body: JSON.stringify({name})
        });
        const data = await res.json();
        if (data.success) {
            inp.style.borderColor = 'var(--success, #4caf50)';
            setTimeout(() => { inp.style.borderColor = ''; }, 1500);
            loadPanel('mesh');
        } else {
            alert(data.error || 'Failed to save name');
        }
    } catch(e) {
        alert('Failed to save name');
    }
}

function updateMeshSidebar(nodes) {
    const sidebar = document.getElementById('nodes-sidebar');
    if (!sidebar) return;
    const list = nodes.nodes || [];
    const gatewayHtml = `<div class="node-item"><div class="node-dot"></div><div><div style="font-weight:500">Gateway</div><div style="font-size:0.75rem;color:var(--text-dim)">This device</div></div></div>`;
    sidebar.innerHTML = gatewayHtml + list.map(n =>
        `<div class="node-item"><div class="node-dot"></div><div><div style="font-weight:500">${n.name}</div><div style="font-size:0.75rem;color:var(--text-dim)">${n.type}</div></div></div>`
    ).join('');
}

async function toggleTor(running) {
    try {
        const res = await apiFetch(API + '/api/mesh/tor/' + (running ? 'disable' : 'enable'), {method:'POST'});
        const data = await res.json();
        if (data.error) alert(data.error);
        loadMeshStatus();
    } catch (e) { alert('Error toggling Tor: ' + e.message); }
}

async function toggleDiscovery(running) {
    try {
        const res = await apiFetch(API + '/api/mesh/discovery/' + (running ? 'disable' : 'enable'), {method:'POST'});
        const data = await res.json();
        if (data.error) alert(data.error);
        loadMeshStatus();
    } catch (e) { alert('Error toggling discovery: ' + e.message); }
}

async function toggleRendezvous(enabled) {
    try {
        const res = await apiFetch(API + '/api/mesh/rendezvous/enable', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({enable: !enabled})
        });
        const data = await res.json();
        if (data.error) alert(data.error);
        loadMeshStatus();
    } catch (e) { alert('Error toggling rendezvous: ' + e.message); }
}

async function approvePeer(nodeId) {
    try {
        await apiFetch(API + '/api/mesh/trust/' + nodeId + '/approve', {method:'POST'});
        loadMeshStatus();
    } catch (e) { alert('Error: ' + e.message); }
}

async function blockPeer(nodeId) {
    if (!confirm('Block this peer?')) return;
    try {
        await apiFetch(API + '/api/mesh/trust/' + nodeId + '/block', {method:'POST'});
        loadMeshStatus();
    } catch (e) { alert('Error: ' + e.message); }
}

async function toggleMeshPerm(nodeId, perm, granted) {
    try {
        await apiFetch(API + '/api/mesh/trust/' + nodeId + '/permissions', {
            method:'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({permission: perm, granted: granted})
        });
    } catch (e) { alert('Error: ' + e.message); }
}

async function showSecretKey() {
    try {
        const res = await apiFetch(API + '/api/mesh/key');
        const data = await res.json();
        if (data.key) {
            const ok = confirm('Secret Key (click OK to copy):\n\n' + data.key);
            if (ok) navigator.clipboard.writeText(data.key);
        } else {
            alert(data.error || 'Could not retrieve key');
        }
    } catch (e) { alert('Error: ' + e.message); }
}

async function showConnectionString() {
    try {
        const res = await apiFetch(API + '/api/mesh/status');
        const data = await res.json();
        const keyRes = await apiFetch(API + '/api/mesh/key');
        const keyData = await keyRes.json();
        const addr = data.transports?.websocket?.address || 'ws://YOUR_IP:18789';
        const cmd = `familiar-mesh node --gateway ${addr} --key ${keyData.key || 'YOUR_KEY'}`;
        const ok = confirm('Connection command (click OK to copy):\n\n' + cmd);
        if (ok) navigator.clipboard.writeText(cmd);
    } catch (e) { alert('Error: ' + e.message); }
}

function showTorConnectionString(onion) {
    const cmd = `familiar-mesh node --gateway ws://${onion}:18789 --key YOUR_KEY --transport tor`;
    const ok = confirm('Tor connection command (click OK to copy):\n\n' + cmd);
    if (ok) navigator.clipboard.writeText(cmd);
}

// Modals
// showModal — defined in core.js

// hideModal — defined in core.js

function showAddTaskModal() {
    document.getElementById('task-id').value = '';
    document.getElementById('task-title').value = '';
    document.getElementById('task-due').value = '';
    document.getElementById('task-modal-title').textContent = 'Add Task';
    document.getElementById('task-files-section').style.display = 'none';
    document.getElementById('task-files-list').innerHTML = '';
    showModal('task-modal');
}
function showAddMemoryModal() { showModal('memory-modal'); }

async function addTask() {
    const data = {
        title: document.getElementById('task-title').value,
        priority: document.getElementById('task-priority').value,
        due_date: document.getElementById('task-due').value,
        category: document.getElementById('task-category').value
    };

    if (!data.title) {
        alert('Please enter a task title');
        return;
    }

    const resp = await apiFetch(API + '/api/tasks', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
    });
    const result = await resp.json();
    const taskId = result.task?.id || '';
    if (taskId) {
        document.getElementById('task-id').value = taskId;
        document.getElementById('task-files-section').style.display = 'block';
        document.getElementById('task-modal-title').textContent = 'Task Created';
        flLoadFiles('task-files-list', 'tasks', taskId, 'helper');
        showToast('Task added! You can now attach files.', 'success');
        loadTasks();
        return;
    }

    hideModal('task-modal');
    document.getElementById('task-title').value = '';
    document.getElementById('task-due').value = '';
    loadTasks();
}

async function addMemory() {
    const data = {
        key: document.getElementById('memory-key').value,
        value: document.getElementById('memory-value').value,
        category: document.getElementById('memory-category').value
    };
    
    if (!data.key || !data.value) {
        alert('Please enter key and value');
        return;
    }
    
    await apiFetch(API + '/api/memory', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
    });
    
    hideModal('memory-modal');
    document.getElementById('memory-key').value = '';
    document.getElementById('memory-value').value = '';
    loadMemory();
}

async function changeProvider(provider) {
    // Show loading state on provider card
    const wrap = document.getElementById('provider-select-wrap');
    const banner = document.getElementById('provider-change-banner');
    if (banner) banner.remove();
    const loading = document.createElement('div');
    loading.id = 'provider-change-banner';
    loading.style.cssText = 'margin-top:8px;padding:8px 12px;border-radius:8px;font-size:0.82rem;background:var(--bg-input);color:var(--text-dim);display:flex;align-items:center;gap:8px;';
    loading.innerHTML = '<div class="wizard-loading-dot" style="width:6px;height:6px;"></div> Switching provider...';
    if (wrap) wrap.parentElement.appendChild(loading);

    await apiFetch(API + '/api/config', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({provider: provider})
    });

    // Refresh everything
    await loadStatus();
    await loadRoutingConfig();

    // Show success banner
    if (loading) {
        loading.style.background = 'var(--success)';
        loading.style.color = '#000';
        loading.innerHTML = '✓ Switched to <b>' + provider + '</b>';
        setTimeout(() => loading.remove(), 3000);
    }
}

