/**
 * governance.js — Governance view: Darwin auto-propose review gate.
 *
 * Renders pending Darwin proposals with diff preview and approve/reject/defer
 * buttons. Wired to POST /api/governance/darwin/:id/review.
 *
 * Cut 21 (Phase H): Darwin auto-propose UI.
 * Cut 22 (Phase H): Byzantine consensus bar (see byzantineSection below).
 */

import { api, post } from '../api.js';
import { openDrawer }  from '../widgets/drawer.js';
import { S }           from '../state.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;');

function fmtTs(ts) {
    if (!ts) return '—';
    const d = new Date(ts);
    return d.toLocaleString([], { dateStyle: 'short', timeStyle: 'short' });
}

function outcomeChip(outcome) {
    const cls = { pending: 'chip-warn', applied: 'chip-ok', rejected: 'chip-bad', deferred: 'chip-muted' };
    return `<span class="chip ${cls[outcome] || 'chip-muted'}">${esc(outcome)}</span>`;
}

/** Render a compact unified diff with line highlighting. */
function renderDiff(diff) {
    if (!diff) return `<div class="diff-empty">No diff recorded</div>`;
    const lines = String(diff).split('\n').slice(0, 120);
    const html = lines.map(line => {
        const cls = line.startsWith('+') ? 'diff-add'
                  : line.startsWith('-') ? 'diff-del'
                  : line.startsWith('@@') ? 'diff-hunk'
                  : 'diff-ctx';
        return `<div class="${cls}">${esc(line)}</div>`;
    }).join('');
    const more = String(diff).split('\n').length > 120
        ? `<div class="diff-ctx" style="opacity:.45">… ${String(diff).split('\n').length - 120} more lines</div>`
        : '';
    return `<pre class="diff-block">${html}${more}</pre>`;
}

/* ── Load + render ─────────────────────────────────────────────────────────── */

export async function load() {
    const container = $('governance-container');
    if (!container) return;
    container.innerHTML = `<div class="empty"><div class="empty-title">Loading proposals…</div></div>`;

    const [data, health] = await Promise.all([
        api('/api/governance/darwin', 10_000),
        api('/api/health', 15_000),
    ]);
    const cycles = Array.isArray(data?.cycles) ? data.cycles : [];

    render(cycles, health);
}

export function render(cycles, health = null) {
    const container = $('governance-container');
    if (!container) return;

    if (!cycles.length) {
        container.innerHTML = `
            <div class="trust-grid">
              <div class="card trust-card">
                <div class="trust-card-hd"><span class="trust-card-title">Darwin auto-propose</span><span class="chip chip-muted">idle</span></div>
                <div class="trust-posture-body">
                  <div class="trust-posture-row"><span>Proposals</span><strong>0</strong></div>
                  <div class="trust-posture-row"><span>Trigger</span><strong>nexus_orchestrate</strong></div>
                  <div class="trust-posture-row"><span>Self-improve</span><strong>${esc(health?.runtime?.selfImprove ? 'on' : 'off')}</strong></div>
                </div>
              </div>
              <div class="card trust-card">
                <div class="trust-card-hd"><span class="trust-card-title">Consensus guard</span><span class="chip chip-ok">ready</span></div>
                <div class="empty-sub">Live Byzantine votes and review decisions appear here when proposals are created.</div>
              </div>
            </div>`;
        _renderByzantineSection();
        return;
    }

    const pending  = cycles.filter(c => c.outcome === 'pending');
    const resolved = cycles.filter(c => c.outcome !== 'pending');

    let html = '';

    if (pending.length) {
        html += `<div class="shd">Pending proposals (${pending.length})</div>`;
        html += pending.map(c => cycleCard(c, true)).join('');
    }

    if (resolved.length) {
        html += `<div class="shd" style="margin-top:24px">Resolved proposals (${resolved.length})</div>`;
        html += resolved.map(c => cycleCard(c, false)).join('');
    }

    container.innerHTML = html;

    // Attach button handlers.
    container.querySelectorAll('[data-darwin-review]').forEach(btn => {
        btn.addEventListener('click', () => {
            const { darwinReview: cycleId, darwinDecision: decision } = btn.dataset;
            _doReview(cycleId, decision);
        });
    });

    // Attach diff-preview handlers.
    container.querySelectorAll('[data-darwin-diff]').forEach(btn => {
        btn.addEventListener('click', () => {
            const cycleId = btn.dataset.darwinDiff;
            const cycle   = cycles.find(c => c.id === cycleId);
            if (!cycle) return;
            openDrawer({
                title: `Diff — ${cycle.targetFile}`,
                body: `
                    <div style="margin-bottom:10px">
                      <strong>${esc(cycle.hypothesis)}</strong>
                      <div style="font-size:var(--text-sm);color:var(--text-muted);margin-top:4px">${esc(cycle.approach)}</div>
                    </div>
                    ${renderDiff(cycle.diffGenerated)}
                `,
            });
        });
    });
    _renderByzantineSection();
}

function cycleCard(c, isPending) {
    const metrics = _fmtMetrics(c.metricsBefore, c.metricsAfter);
    return `
        <div class="card darwin-card" data-cycle-id="${esc(c.id)}">
          <div class="darwin-card-hd">
            <div class="darwin-hypothesis">${esc(c.hypothesis)}</div>
            <div class="darwin-meta">
              ${outcomeChip(c.outcome)}
              <span class="chip chip-muted">${esc(c.targetFile.split('/').pop())}</span>
              <span style="font-size:var(--text-sm);color:var(--text-muted)">${fmtTs(c.timestamp)}</span>
            </div>
          </div>
          <div class="darwin-approach">${esc(c.approach)}</div>
          ${metrics ? `<div class="darwin-metrics">${metrics}</div>` : ''}
          <div class="darwin-validation">
            <span class="${c.buildPassed ? 'ok-dot' : 'err-dot'}">Build ${c.buildPassed ? 'PASS' : 'FAIL'}</span>
            <span class="${c.testsPassed ? 'ok-dot' : 'err-dot'}">Tests ${c.testsPassed ? 'PASS' : 'FAIL'}</span>
            ${c.guardrailResult ? `<span class="chip chip-muted">Guardrail ${c.guardrailResult.passed ? '✓' : '✗'} (${c.guardrailResult.score})</span>` : ''}
          </div>
          <div class="darwin-actions">
            <button class="btn btn-sm" data-darwin-diff="${esc(c.id)}">View diff</button>
            ${isPending ? `
              <button class="btn btn-sm btn-ok"  data-darwin-review="${esc(c.id)}" data-darwin-decision="approve">Approve</button>
              <button class="btn btn-sm btn-bad" data-darwin-review="${esc(c.id)}" data-darwin-decision="reject">Reject</button>
              <button class="btn btn-sm"         data-darwin-review="${esc(c.id)}" data-darwin-decision="defer">Defer</button>
            ` : ''}
          </div>
          ${c.learnings?.length ? `<div class="darwin-learnings">Learnings: ${esc(c.learnings.join('; '))}</div>` : ''}
        </div>`;
}

function _fmtMetrics(before, after) {
    if (!before || !after) return '';
    const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])];
    if (!keys.length) return '';
    return keys.map(k => {
        const b = Number(before[k] ?? 0), a = Number(after[k] ?? 0);
        const delta = a - b;
        const cls = delta > 0 ? 'metric-up' : delta < 0 ? 'metric-down' : 'metric-flat';
        return `<span class="darwin-metric ${cls}">${esc(k)}: ${b.toFixed(2)} → ${a.toFixed(2)} (${delta >= 0 ? '+' : ''}${delta.toFixed(2)})</span>`;
    }).join('');
}

async function _doReview(cycleId, decision) {
    const btns = document.querySelectorAll(`[data-darwin-review="${CSS.escape(cycleId)}"]`);
    btns.forEach(b => { b.disabled = true; });

    const result = await post(`/api/governance/darwin/${encodeURIComponent(cycleId)}/review`, { decision }).catch(() => null);
    if (result?.ok) {
        // Reload after review to reflect the new state.
        load();
    } else {
        const msg = result?.error ?? 'Review failed';
        btns.forEach(b => { b.disabled = false; });
        const errEl = document.createElement('div');
        errEl.className = 'toast toast-bad';
        errEl.textContent = msg;
        document.getElementById('toast-stack')?.appendChild(errEl);
        setTimeout(() => errEl.remove(), 4000);
    }
}

/* ── Byzantine consensus section ──────────────────────────────────────────── */

/**
 * Handle a live `byzantine.vote.*` SSE event and patch the consensus section.
 * Called by main.js when tab === 'governance' and evt.category === 'byzantine'.
 */
export function handleByzantineEvent(evt) {
    const payload = evt.payload;
    if (!payload?.proposalId) return;
    const pid = payload.proposalId;

    if (evt.type === 'byzantine.vote.started') {
        S.byzantineVotes.set(pid, {
            proposalId: pid,
            agentCount: payload.agentCount ?? 0,
            quorum:     payload.quorum ?? 0,
            votesFor:   0,
            votesAgainst: 0,
            votesPending: payload.agentCount ?? 0,
            accepted:   null,
            agentVotes: [],
            startedAt:  Date.now(),
        });
    } else if (evt.type === 'byzantine.vote.result') {
        S.byzantineVotes.set(pid, {
            proposalId:   pid,
            agentCount:   payload.total ?? 0,
            quorum:       payload.quorum ?? 0,
            votesFor:     payload.votesFor ?? 0,
            votesAgainst: payload.votesAgainst ?? 0,
            votesPending: payload.votesPending ?? 0,
            accepted:     payload.accepted ?? null,
            agentVotes:   payload.agentVotes ?? [],
            startedAt:    S.byzantineVotes.get(pid)?.startedAt ?? Date.now(),
        });
        // Evict old sessions (keep last 20)
        if (S.byzantineVotes.size > 20) {
            const oldest = [...S.byzantineVotes.keys()][0];
            S.byzantineVotes.delete(oldest);
        }
    }

    _renderByzantineSection();
}

function _renderByzantineSection() {
    const container = document.getElementById('governance-container');
    if (!container) return;

    // Upsert the Byzantine sub-section at the top without wiping Darwin cards.
    let section = container.querySelector('.byzantine-section');
    if (!section) {
        section = document.createElement('div');
        section.className = 'byzantine-section';
        container.insertBefore(section, container.firstChild);
    }

    const sessions = [...S.byzantineVotes.values()].slice(-10).reverse();
    if (!sessions.length) {
        section.innerHTML = '';
        return;
    }

    section.innerHTML = `
        <div class="shd" style="margin-bottom:10px">Byzantine consensus (live)</div>
        ${sessions.map(s => _byzantineBar(s)).join('')}
    `;

    // Attach drawer handlers for per-voter detail.
    section.querySelectorAll('[data-byz-detail]').forEach(btn => {
        btn.addEventListener('click', () => {
            const pid = btn.dataset.byzDetail;
            const s = S.byzantineVotes.get(pid);
            if (!s) return;
            openDrawer({
                title: `Consensus — ${pid.slice(-8)}`,
                body: `
                    <div style="margin-bottom:10px;font-size:var(--text-sm);color:var(--text-muted)">
                        Quorum needed: ${s.quorum} / ${s.agentCount} agents
                    </div>
                    <div class="consensus-bar">${_consensusTrack(s)}</div>
                    ${s.agentVotes.length ? `
                    <table style="width:100%;border-collapse:collapse;font-size:var(--text-sm);margin-top:10px">
                      <thead><tr style="color:var(--text-muted)"><th style="text-align:left;padding:4px 0">Agent</th><th>Vote</th><th>Reliability</th></tr></thead>
                      <tbody>
                        ${s.agentVotes.map(v => `
                          <tr>
                            <td style="padding:3px 0;font-family:var(--font-mono);font-size:var(--caption)">${esc(v.agentId.slice(-12))}</td>
                            <td style="text-align:center">${v.approved ? '<span style="color:var(--ok)">✓ Approve</span>' : '<span style="color:var(--bad)">✗ Reject</span>'}</td>
                            <td style="text-align:center;font-family:var(--font-mono)">${(v.reliability * 100).toFixed(0)}%</td>
                          </tr>
                        `).join('')}
                      </tbody>
                    </table>` : '<div style="color:var(--text-muted);font-size:var(--text-sm);margin-top:8px">Votes not yet recorded.</div>'}
                `,
            });
        });
    });
}

function _byzantineBar(s) {
    const pct = s.agentCount > 0
        ? `${s.votesFor}/${s.agentCount} approve`
        : '— / — approve';
    const outcome = s.accepted === true  ? '<span class="chip chip-ok">accepted</span>'
                  : s.accepted === false ? '<span class="chip chip-bad">rejected</span>'
                  : '<span class="chip chip-warn">voting…</span>';
    return `
        <div class="card" style="margin-bottom:10px;padding:12px 16px">
          <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;gap:8px">
            <span style="font-family:var(--font-mono);font-size:var(--caption);color:var(--text-muted)">
              proposal·${esc(s.proposalId.slice(-8))}
            </span>
            <div style="display:flex;align-items:center;gap:6px">
              ${outcome}
              <button class="btn btn-sm" data-byz-detail="${esc(s.proposalId)}" style="font-size:var(--caption)">Detail</button>
            </div>
          </div>
          <div class="consensus-bar">
            ${_consensusTrack(s)}
            <div class="consensus-label">
              <span>${s.votesFor} approve · ${s.votesAgainst} reject · ${s.votesPending} pending</span>
              <span style="color:var(--text-muted)">${pct} (quorum ${s.quorum})</span>
            </div>
          </div>
        </div>`;
}

function _consensusTrack(s) {
    const total = s.agentCount || 1;
    const approveW  = ((s.votesFor     / total) * 100).toFixed(1);
    const rejectW   = ((s.votesAgainst / total) * 100).toFixed(1);
    const pendingW  = ((s.votesPending / total) * 100).toFixed(1);
    return `<div class="consensus-track">
        <div class="consensus-seg-approve" style="width:${approveW}%"></div>
        <div class="consensus-seg-reject"  style="width:${rejectW}%"></div>
        <div class="consensus-seg-pending" style="width:${pendingW}%"></div>
    </div>`;
}
