/**
 * main.js — Bootstrap, router wiring, state loaders, tab refresh.
 * Entry point for the ES module app tree.
 */

import { S, bus }                from './state.js';
import { api, bustCache, clearCache } from './api.js';
import { connect as connectSSE,
         setOnEvent }             from './sse.js';
import { register as navRegister,
         start as routerStart }   from './router.js';
import { init as initDrawer,
         closeDrawer }            from './widgets/drawer.js';
import { init as initCmdBar }     from './widgets/command-bar.js';
import * as Board                 from './views/board.js';
import * as Workforce             from './views/workforce.js';
import * as Memory                from './views/memory.js';
import * as ContextLog            from './views/context-log.js';
import * as Knowledge             from './views/knowledge.js';
import * as Repo                  from './views/repo.js';
import * as Governance            from './views/governance.js';
import * as Trust                 from './views/trust.js';
import * as License               from './views/license.js';
import * as Federation            from './views/federation.js';
import * as Runtime               from './views/runtime.js';
import { mountErrorBanner }       from './widgets/error-banner.js';
import { mount as mountRuntimeBadges } from './widgets/runtime-badge.js';

const $ = id => document.getElementById(id);
const esc = s => s==null?'':String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
let memoryReloadTimer = null;

function _memoryApiUrl() {
    const params = new URLSearchParams({ includeInactive: 'true', limit: '100' });
    if (S.workspace?.repoName) params.set('repo', S.workspace.repoName);
    return '/api/memory?' + params.toString();
}

function _refreshMemorySoon() {
    bustCache(_memoryApiUrl());
    bustCache('/api/memory');
    bustCache('/api/memory/health');
    bustCache('/api/dashboard/surface/memory');
    if (S.tab !== 'memory') return;
    clearTimeout(memoryReloadTimer);
    memoryReloadTimer = setTimeout(() => {
        memoryReloadTimer = null;
        Memory.load();
    }, 150);
}

/* ─────────────────── Router ─────────────────── */
navRegister('board',      Board.load);
navRegister('runtime',    Runtime.load);
navRegister('workforce',  Workforce.load);
navRegister('memory',     Memory.load);
navRegister('context-log', ContextLog.load);
navRegister('repo',       Repo.load);
navRegister('knowledge',  Knowledge.load);
navRegister('governance', Governance.load);
navRegister('trust',      Trust.load);
navRegister('license',    License.load);
navRegister('federation', Federation.load);

/* ─────────────────── SSE event router ─────────────────── */
setOnEvent(evt => {
    // Runtime tab ingests ALL events regardless of active tab (for live history)
    Runtime.ingestEvent(evt);

    // Neural Stream HUD ring buffer ingests ALL events too (board card auto-renders)
    Board.pushNeuralEvent?.(evt);

    const tab = S.tab;
    if (tab === 'board') {
        Board.render();
        _renderTicker();
    }
    // Refresh tool health tile on tool lifecycle events.
    if (['tool.invocation', 'mcp.handler.complete', 'mcp.handler.failed'].includes(String(evt.type||''))) {
        bustCache('/api/runtime/tool-health');
        bustCache('/api/dashboard/surface/operate');
        if (tab === 'board') {
            Board.loadToolHealth();
        }
    }
    if (tab === 'workforce' && ['synapse','operative','mission'].includes(evt.category)) {
        Workforce.render();
    }
    if (tab === 'memory' && evt.category === 'memory') {
        _refreshMemorySoon();
    }
    if (tab === 'governance' && evt.category === 'darwin') {
        Governance.load();
    }
    if (tab === 'governance' && evt.category === 'byzantine') {
        Governance.handleByzantineEvent(evt);
    }
    if (String(evt.type||'').startsWith('graph.cr.build')) {
        bustCache('/api/knowledge-topology');
        Repo.handleBuildEvent?.(evt);
        if (tab === 'knowledge') {
            Knowledge.load();
        }
        if (tab === 'memory' && evt.type === 'graph.cr.build.complete') {
            Memory.load();
        }
    }
    // Route push-mode dispatch events regardless of active tab
    if (String(evt.type||'').startsWith('dispatch.')) {
        Workforce.handleDispatchEvent(evt);
    }

    // Orchestration pipeline events — keep board fresh as decomposition / completion fires.
    // The board's own SSE listener (board.js:loadOrchestrationPipeline) renders the detail card;
    // here we just refresh the surface payload so kanban + KPIs reflect the new run.
    if (String(evt.type||'').startsWith('orchestration.')) {
        bustCache('/api/dashboard/surface/operate');
        bustCache('/api/runs');
        if (tab === 'board') {
            Board.handleOrchestrationEvent?.(evt);
            Board.render();
        }
        if (tab === 'context-log') {
            ContextLog.load();
        }
    }
    // Workspace root promoted (e.g. from bootstrap hint) — refresh header immediately.
    if (String(evt.type||'') === 'workspace.changed') {
        bustCache('/api/workspace');
        bustCache('/api/repo-tree');
        loadWorkspace();
    }
});

/* ─────────────────── Bus wiring ─────────────────── */
bus.on('connection', ok => _renderHeader(ok));
bus.on('banner',     b  => b ? _showBanner(b.msg, b.kind) : _hideBanner());
bus.on('ticker',     _  => _renderTicker());
bus.on('toast',      ({ msg, kind }) => _toast(msg, kind));
bus.on('goal:queued', data => {
    _toast(`Goal queued (run ${(data?.runId||'').slice(-6)||'…'})`, 'good');
    bustCache('/api/dashboard/surface/operate');
    setTimeout(Board.load, 1200);
});

/* ─────────────────── Workspace + header ─────────────────── */
async function loadWorkspace() {
    const ws = await api('/api/workspace', 30000); if (!ws) return;
    S.workspace = ws;
    const repoEl = $('workspace-repo'), branchEl = $('workspace-branch');
    if (repoEl)   repoEl.textContent   = ws.repoName || '—';
    if (branchEl) branchEl.textContent = ws.branch   || 'detached';
    document.title = `${ws.repoName || 'Nexus Prime'} · Nexus Prime`;
    const badge = $('workspace-badge');
    if (badge) badge.title = `${ws.repoName||''} @ ${ws.repoRoot||''}`;
}

async function loadRepoTree() {
    const tree = await api('/api/repo-tree', 45000);
    const el = $('repo-tree'); if (!el) return;
    if (!tree?.children?.length) { el.innerHTML = `<div class="empty"><div class="empty-title">No tree available</div></div>`; return; }
    el.innerHTML = tree.children.slice(0,80).map(node => {
        const isDir = node.type === 'directory';
        const icon = isDir ? '▸' : '·';
        const meta = !isDir && node.size ? _fmtBytes(node.size) : '';
        return `<div class="repo-tree-entry ${isDir?'dir':'file'}">
            <span class="repo-tree-icon">${icon}</span>
            <span class="repo-tree-name">${esc(node.name)}</span>
            ${meta?`<span class="repo-tree-meta">${esc(meta)}</span>`:''}
        </div>`;
    }).join('');
}

function _fmtBytes(n) {
    if (n==null) return '';
    if (n<1024) return n+'B'; if (n<1024*1024) return (n/1024).toFixed(1)+'K';
    return (n/(1024*1024)).toFixed(1)+'M';
}

async function loadProjects() {
    const [projectsD, runtimesD] = await Promise.all([
        api('/api/memory/projects', 30000),
        api('/api/runtimes', 10000),
    ]);
    S.projects = Array.isArray(projectsD) ? projectsD : (projectsD?.projects||[]);
    S.runtimes = Array.isArray(runtimesD) ? runtimesD : (runtimesD?.runtimes||[]);
    _selectDefaultRuntime();
    _renderProjectSelectors();
}

async function loadCompanies() {
    const d = await api('/api/workforce/companies', 30000);
    const companies = Array.isArray(d?.companies) ? d.companies : [];
    S.companies = companies;
}

function _renderProjectSelectors() {
    const sel = $('ctx-project'); if (!sel) return;
    const runtimeOptions = (S.runtimes||[]).map(r => {
        const id = r.runtimeId || '';
        const repo = r.repoIdentity || {};
        const label = repo.repoName || repo.repoRoot || id || 'runtime';
        const suffix = r.lastHeartbeatAt ? ' · runtime' : '';
        return `<option value="runtime:${esc(id)}">${esc(label + suffix)}</option>`;
    }).join('');
    const projectOptions = S.projects.map(p=>`<option value="project:${esc(p.id||p.projectId)}">${esc(p.name||p.id||'Project')}</option>`).join('');
    sel.innerHTML = '<option value="">All projects</option>' + runtimeOptions + projectOptions;
    if (S.scope.runtimeId) sel.value = `runtime:${S.scope.runtimeId}`;
    else if (S.scope.projectId) sel.value = `project:${S.scope.projectId}`;
}

function _selectDefaultRuntime() {
    if (S.scope.runtimeId || !(S.runtimes||[]).length) return;
    const current = S.workspace?.repoRoot || S.workspace?.workspaceRoot || '';
    const candidates = (S.runtimes||[]).filter(r => r?.runtimeId);
    const exact = current ? candidates.find(r => r.repoIdentity?.repoRoot === current) : null;
    const currentLooksLikeHome = exact
        && !exact.repoIdentity?.remoteUrl
        && String(exact.repoIdentity?.repoRoot || '').split('/').filter(Boolean).length <= 2
        && candidates.length > 1;
    const remoteRepo = candidates.find(r => r.repoIdentity?.remoteUrl && r.repoIdentity?.repoName !== 'brew');
    const deepestRepo = [...candidates].sort((a, b) => {
        const depthA = String(a.repoIdentity?.repoRoot || '').split('/').filter(Boolean).length;
        const depthB = String(b.repoIdentity?.repoRoot || '').split('/').filter(Boolean).length;
        return depthB - depthA;
    }).find(r => {
        const root = r.repoIdentity?.repoRoot || '';
        return root && root !== '/';
    });
    const selected = (exact && !currentLooksLikeHome ? exact : null) || remoteRepo || deepestRepo || candidates[0];
    S.scope.runtimeId = selected?.runtimeId || null;
}

/* ─────────────────── Header / ticker ─────────────────── */
function _renderHeader(connected) {
    const pill = $('sse-status'), lbl = $('sse-label'); if (!pill) return;
    lbl.textContent = connected ? 'connected' : 'offline';
    pill.className  = 'sse-pill ' + (connected ? 'ok' : 'err');
}

function _renderTicker() {
    const track = $('ticker-track'); if (!track || !S.ticker.length) return;
    const items = [...S.ticker, ...S.ticker].map(e =>
        `<span class="ticker-item cat-${esc(e.category)}"><span class="t-dot"></span><span>${esc(e.type)}</span>${e.summary?`<span style="opacity:.5">${esc(e.summary.substring(0,40))}</span>`:''}<span style="opacity:.35">${_fmtTs(e.time)}</span></span>`
    ).join('');
    track.innerHTML = items;
    track.style.animationDuration = Math.max(25, S.ticker.length * 2.5) + 's';
}

function _fmtTs(ts) { if (!ts) return ''; return new Date(ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'}); }

/* ─────────────────── Banner / toast ─────────────────── */
function _showBanner(msg, type='info') { const el=$('banner'); if(el){ el.textContent=msg; el.className=type; el.style.display='block'; } }
function _hideBanner() { const el=$('banner'); if(el) el.style.display='none'; }

function _toast(msg, kind='info') {
    const stack = $('toast-stack'); if (!stack) return;
    const t = document.createElement('div');
    t.className = 'toast toast-' + kind; t.textContent = msg;
    stack.appendChild(t);
    setTimeout(() => { t.style.transition='opacity 0.2s'; t.style.opacity='0'; setTimeout(()=>t.remove(),220); }, 4000);
}

/* ─────────────────── Event handlers ─────────────────── */
function _attachHandlers() {
    // Drawer
    initDrawer();

    // Command bar
    initCmdBar();

    // Memory view init
    Memory.init();

    // Context selectors
    $('ctx-project')?.addEventListener('change', async e => {
        const value = e.target.value || '';
        if (value.startsWith('runtime:')) {
            S.scope.runtimeId = value.slice('runtime:'.length) || null;
            S.scope.projectId = null;
            clearCache();
            await loadWorkspace();
            await loadRepoTree();
        } else {
            S.scope.projectId = value.startsWith('project:') ? value.slice('project:'.length) : (value || null);
        }
        const mp = $('mem-project-filter'); if (mp) mp.value = S.scope.projectId || '';
        _reloadTab(S.tab);
    });
    $('ctx-agent')?.addEventListener('change', e => {
        S.scope.agentId = e.target.value || null;
        _reloadTab(S.tab);
    });

    // Chat float
    document.addEventListener('keydown', e => {
        if ((e.ctrlKey||e.metaKey) && e.key === '`') { e.preventDefault(); _toggleChat(); }
    });
    $('chat-toggle-btn')?.addEventListener('click', _toggleChat);
    $('chat-send-btn')?.addEventListener('click', _sendChat);
    $('chat-input')?.addEventListener('keydown', e => { if (e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); _sendChat(); } });
    $('chat-close-btn')?.addEventListener('click', _toggleChat);
}

function _reloadTab(tab) {
    if (tab === 'board')     Board.load();
    if (tab === 'workforce') Workforce.load();
    if (tab === 'memory')    Memory.load();
    if (tab === 'context-log') ContextLog.load();
    if (tab === 'knowledge') Knowledge.load();
}

/* ─────────────────── Chat panel ─────────────────── */
function _toggleChat() {
    const panel = $('chat-float'); if (!panel) return;
    panel.classList.toggle('hidden');
    if (!panel.classList.contains('hidden')) $('chat-input')?.focus();
}

async function _sendChat() {
    const input = $('chat-input'), msgs = $('chat-messages'); if (!input||!msgs) return;
    const text = input.value.trim(); if (!text) return;
    input.value = '';
    const uEl=document.createElement('div'); uEl.className='chat-msg user'; uEl.textContent=text; msgs.appendChild(uEl); msgs.scrollTop=msgs.scrollHeight;
    const sendBtn = $('chat-send-btn'); if (sendBtn) sendBtn.disabled=true;
    try {
        const qs = S.scope?.runtimeId ? `?runtimeId=${encodeURIComponent(S.scope.runtimeId)}` : '';
        const res = await fetch('/api/runtime/execute'+qs,{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ action:'chat', goal:text, message:text, projectId:S.scope?.projectId, runtimeId:S.scope?.runtimeId }) }).then(r=>r.json()).catch(()=>null);
        const aEl=document.createElement('div'); aEl.className='chat-msg agent'; aEl.textContent=res?.result||res?.message||res?.output||'Command queued.'; msgs.appendChild(aEl);
    } catch { const aEl=document.createElement('div'); aEl.className='chat-msg agent'; aEl.textContent='Could not reach agent — is the server running?'; msgs.appendChild(aEl); }
    if (sendBtn) sendBtn.disabled=false;
    msgs.scrollTop=msgs.scrollHeight;
}

/* ─────────────────── Bootstrap ─────────────────── */
async function bootstrap() {
    // Persistent error banner — mount first, before any data loads.
    mountErrorBanner();

    // Runtime availability badges (push-mode: amber = not in PATH, green = available).
    void mountRuntimeBadges();

    _attachHandlers();
    _renderHeader(false);

    await loadWorkspace();
    await loadProjects();

    // Fast first paint: board + companies + repo-tree + license
    const [, , , lic] = await Promise.all([
        Board.load(),
        loadCompanies(),
        loadRepoTree(),
        api('/api/license', 60000),
    ]);
    S.license = lic;

    // Populate agent selector
    const agentSel = $('ctx-agent');
    if (agentSel && S.synapseHealth.length) {
        agentSel.innerHTML = '<option value="">All agents</option>' +
            S.synapseHealth.map(o=>`<option value="${esc(o.id||o.operativeId)}">${esc(o.role||o.name||o.id||'agent')}</option>`).join('');
    }

    // Wire router after first paint
    routerStart();

    // Connect SSE stream — SSE drives all live updates; no polling needed.
    connectSSE();
}

// Start when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', bootstrap);
} else {
    bootstrap();
}
