<?php
/**
 * SCLS Audit Tab — Dashboard view
 * Renders within the existing dashboard layout (included by index.php).
 *
 * Features:
 *   - Stats cards (24h totals, events/hour, decryptions, success rate)
 *   - Filter bar (node, tool, severity, status, date range)
 *   - Data table with pagination
 *   - Decrypt modal with reason + rate limit warning
 *   - Real-time SSE stream for new events
 *   - Dark theme consistent with existing dashboard
 */
if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit; }
RBAC::enforce('view.security');

/* ── Node name map ─────────────────────────────────────────────────────── */
$nodeNames = [
    '199335' => 'DNS',
    '244939' => 'Proxy',
    '333941' => 'Filter',
    '414342' => 'Shield',
    '558201' => 'Origin',
    '669427' => 'Engine',
    '714820' => 'Payment',
    '869377' => 'Vault',
];
$nodeColors = [
    '199335' => '#3b82f6',
    '244939' => '#8b5cf6',
    '333941' => '#e6a817',
    '414342' => '#ef4444',
    '558201' => '#22c55e',
    '669427' => '#f97316',
    '714820' => '#06b6d4',
    '869377' => '#ec4899',
];
?>

<style nonce="<?= $cspNonce ?? '' ?>">
/* ══════════════════════════════════════════════════════════════════════════
   SCLS Audit Tab — Scoped Styles
   ══════════════════════════════════════════════════════════════════════ */

.audit-wrap { display:flex; flex-direction:column; gap:20px; }

/* ── Stats row ────────────────────────────────────────────────────────── */
.audit-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px; }
.audit-stat-card {
    background:rgba(16,20,34,.92);
    border:1px solid rgba(255,255,255,.06);
    border-radius:12px;
    padding:18px 20px;
    display:flex; flex-direction:column; gap:4px;
}
.audit-stat-label { font-size:.78rem; color:rgba(255,255,255,.45); text-transform:uppercase; letter-spacing:.05em; font-weight:600; }
.audit-stat-value { font-size:1.6rem; font-weight:800; color:#fff; font-variant-numeric:tabular-nums; }
.audit-stat-sub   { font-size:.72rem; color:rgba(255,255,255,.35); }

/* ── Filter bar ───────────────────────────────────────────────────────── */
.audit-filters {
    background:rgba(16,20,34,.92);
    border:1px solid rgba(255,255,255,.06);
    border-radius:12px;
    padding:16px 20px;
    display:flex; flex-wrap:wrap; gap:10px; align-items:flex-end;
}
.audit-filter-group { display:flex; flex-direction:column; gap:4px; }
.audit-filter-group label { font-size:.72rem; color:rgba(255,255,255,.45); text-transform:uppercase; letter-spacing:.04em; font-weight:600; }
.audit-filter-group select,
.audit-filter-group input {
    background:rgba(255,255,255,.05);
    border:1px solid rgba(255,255,255,.1);
    border-radius:6px;
    padding:6px 10px;
    color:#fff;
    font-size:.82rem;
    min-width:120px;
    outline:none;
    transition:border-color .15s;
}
.audit-filter-group select:focus,
.audit-filter-group input:focus { border-color:rgba(230,168,23,.5); }
.audit-filter-group select option { background:#0f1322; color:#fff; }
.audit-filter-group input[type="date"] { min-width:140px; }
.audit-filter-group input[type="date"]::-webkit-calendar-picker-indicator { filter:invert(.7); }

.audit-btn {
    padding:7px 18px;
    border:none;
    border-radius:8px;
    font-size:.82rem;
    font-weight:700;
    cursor:pointer;
    transition:all .15s;
    display:inline-flex; align-items:center; gap:6px;
}
.audit-btn-primary { background:rgba(230,168,23,.2); color:#e6a817; }
.audit-btn-primary:hover { background:rgba(230,168,23,.35); }
.audit-btn-secondary { background:rgba(255,255,255,.06); color:rgba(255,255,255,.6); }
.audit-btn-secondary:hover { background:rgba(255,255,255,.12); }
.audit-btn-danger { background:rgba(239,68,68,.15); color:#ef4444; }
.audit-btn-danger:hover { background:rgba(239,68,68,.3); }

/* ── SSE connection indicator ─────────────────────────────────────────── */
.audit-stream-status {
    display:inline-flex; align-items:center; gap:6px;
    font-size:.75rem; font-weight:600; padding:5px 12px;
    border-radius:6px; background:rgba(255,255,255,.04);
    margin-left:auto;
}
.audit-stream-dot { width:8px; height:8px; border-radius:50%; }
.audit-stream-dot.connected { background:#22c55e; box-shadow:0 0 6px #22c55e; animation:audit-pulse 2s infinite; }
.audit-stream-dot.disconnected { background:#ef4444; }
@keyframes audit-pulse { 0%,100%{opacity:1} 50%{opacity:.4} }

/* ── Data table ───────────────────────────────────────────────────────── */
.audit-table-wrap {
    background:rgba(16,20,34,.92);
    border:1px solid rgba(255,255,255,.06);
    border-radius:12px;
    overflow:hidden;
}
.audit-table { width:100%; border-collapse:collapse; }
.audit-table thead th {
    padding:12px 14px;
    text-align:left;
    font-size:.72rem;
    text-transform:uppercase;
    letter-spacing:.05em;
    color:rgba(255,255,255,.4);
    font-weight:600;
    border-bottom:1px solid rgba(255,255,255,.06);
    background:rgba(0,0,0,.2);
    white-space:nowrap;
    user-select:none;
}
.audit-table tbody tr {
    border-bottom:1px solid rgba(255,255,255,.03);
    transition:background .1s;
}
.audit-table tbody tr:hover { background:rgba(255,255,255,.03); }
.audit-table tbody td {
    padding:10px 14px;
    font-size:.82rem;
    color:rgba(255,255,255,.75);
    white-space:nowrap;
}
.audit-table tbody tr.audit-row-new {
    animation:audit-flash .8s ease-out;
}
@keyframes audit-flash {
    0%   { background:rgba(230,168,23,.15); }
    100% { background:transparent; }
}

/* ── Badges ───────────────────────────────────────────────────────────── */
.audit-badge {
    display:inline-flex; align-items:center; gap:4px;
    padding:2px 10px; border-radius:5px;
    font-size:.72rem; font-weight:700;
    white-space:nowrap;
}
.audit-sev-info     { background:rgba(59,130,246,.15);  color:#3b82f6; }
.audit-sev-low      { background:rgba(34,197,94,.15);   color:#22c55e; }
.audit-sev-medium   { background:rgba(230,168,23,.15);  color:#e6a817; }
.audit-sev-high     { background:rgba(249,115,22,.15);  color:#f97316; }
.audit-sev-critical { background:rgba(239,68,68,.18);   color:#ef4444; }

.audit-status-success { color:#22c55e; }
.audit-status-failed  { color:#ef4444; }
.audit-status-pending { color:#e6a817; }
.audit-status-timeout { color:#f97316; }
.audit-status-killed  { color:#ef4444; font-style:italic; }

.audit-node-badge {
    display:inline-flex; align-items:center; gap:5px;
    padding:2px 10px; border-radius:5px;
    font-size:.72rem; font-weight:700;
}
.audit-node-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; }
.audit-tool-cat { font-size:.68rem; color:rgba(255,255,255,.35); font-style:italic; }

/* ── Decrypt button ───────────────────────────────────────────────────── */
.audit-decrypt-btn {
    background:rgba(147,51,234,.15);
    color:#a78bfa;
    border:none; border-radius:5px;
    padding:4px 10px;
    font-size:.72rem; font-weight:700;
    cursor:pointer;
    transition:all .15s;
    display:inline-flex; align-items:center; gap:4px;
}
.audit-decrypt-btn:hover { background:rgba(147,51,234,.3); }
.audit-decrypt-btn:disabled { opacity:.4; cursor:not-allowed; }

/* ── Pagination ───────────────────────────────────────────────────────── */
.audit-pagination {
    display:flex; justify-content:space-between; align-items:center;
    padding:14px 20px;
    border-top:1px solid rgba(255,255,255,.06);
    font-size:.78rem; color:rgba(255,255,255,.45);
}
.audit-page-btns { display:flex; gap:6px; }
.audit-page-btn {
    padding:5px 12px; border:1px solid rgba(255,255,255,.1);
    border-radius:6px; background:transparent; color:rgba(255,255,255,.6);
    font-size:.78rem; cursor:pointer; transition:all .15s;
}
.audit-page-btn:hover { background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.2); }
.audit-page-btn:disabled { opacity:.3; cursor:not-allowed; }
.audit-page-btn.active { background:rgba(230,168,23,.2); border-color:rgba(230,168,23,.4); color:#e6a817; }

/* ── Modal ────────────────────────────────────────────────────────────── */
.audit-modal-overlay {
    display:none; position:fixed; inset:0; z-index:10000;
    background:rgba(0,0,0,.7); backdrop-filter:blur(4px);
    justify-content:center; align-items:center;
}
.audit-modal-overlay.active { display:flex; }
.audit-modal {
    background:#101422; border:1px solid rgba(255,255,255,.1);
    border-radius:14px; width:640px; max-width:95vw; max-height:85vh;
    display:flex; flex-direction:column; overflow:hidden;
    box-shadow:0 25px 60px rgba(0,0,0,.5);
}
.audit-modal-header {
    display:flex; justify-content:space-between; align-items:center;
    padding:18px 22px; border-bottom:1px solid rgba(255,255,255,.06);
}
.audit-modal-header h3 { margin:0; font-size:1.05rem; font-weight:700; color:#fff; }
.audit-modal-close {
    background:none; border:none; color:rgba(255,255,255,.4);
    font-size:1.4rem; cursor:pointer; padding:4px 8px; line-height:1;
}
.audit-modal-close:hover { color:#fff; }
.audit-modal-body { padding:22px; overflow-y:auto; flex:1; }
.audit-modal-body textarea {
    width:100%; min-height:80px; resize:vertical;
    background:rgba(255,255,255,.04); border:1px solid rgba(255,255,255,.1);
    border-radius:8px; padding:12px; color:#fff; font-size:.85rem;
    font-family:inherit; outline:none; transition:border-color .15s;
}
.audit-modal-body textarea:focus { border-color:rgba(230,168,23,.5); }
.audit-modal-body .audit-field-label {
    font-size:.75rem; color:rgba(255,255,255,.45); text-transform:uppercase;
    letter-spacing:.04em; font-weight:600; margin-bottom:6px; display:block;
}
.audit-modal-footer {
    padding:14px 22px; border-top:1px solid rgba(255,255,255,.06);
    display:flex; justify-content:flex-end; gap:8px;
}
.audit-rate-warn {
    font-size:.72rem; color:#f97316; padding:6px 12px;
    background:rgba(249,115,22,.1); border-radius:6px;
    margin-top:10px;
}

/* ── Decrypted content viewer ─────────────────────────────────────────── */
.audit-decrypted-content {
    background:rgba(0,0,0,.3); border:1px solid rgba(255,255,255,.06);
    border-radius:8px; padding:16px; overflow-x:auto; margin-top:12px;
    font-family:'SF Mono','Fira Code',monospace; font-size:.78rem;
    line-height:1.6; color:#e2e8f0; max-height:400px; overflow-y:auto;
    white-space:pre-wrap; word-break:break-all;
}
.audit-json-key   { color:#7dd3fc; }
.audit-json-str   { color:#86efac; }
.audit-json-num   { color:#fbbf24; }
.audit-json-bool  { color:#c084fc; }
.audit-json-null  { color:#94a3b8; font-style:italic; }

/* ── Responsive ───────────────────────────────────────────────────────── */
@media (max-width: 900px) {
    .audit-stats { grid-template-columns:repeat(2,1fr); }
    .audit-table-wrap { overflow-x:auto; }
    .audit-filters { flex-direction:column; }
}
</style>

<div class="workspace audit-wrap" id="ws-audit">

<!-- ── Stats Cards ──────────────────────────────────────────────────────── -->
<div class="audit-stats">
    <div class="audit-stat-card">
        <span class="audit-stat-label">Total Events (24h)</span>
        <span class="audit-stat-value" id="audit-stat-total">—</span>
        <span class="audit-stat-sub" id="audit-stat-total-sub">Loading…</span>
    </div>
    <div class="audit-stat-card">
        <span class="audit-stat-label">Events / Hour</span>
        <span class="audit-stat-value" id="audit-stat-rate">—</span>
        <span class="audit-stat-sub">Average over 24h</span>
    </div>
    <div class="audit-stat-card">
        <span class="audit-stat-label">Decryptions Today</span>
        <span class="audit-stat-value" id="audit-stat-decryptions">—</span>
        <span class="audit-stat-sub" id="audit-stat-decrypt-sub">Rate limit: 10/min</span>
    </div>
    <div class="audit-stat-card">
        <span class="audit-stat-label">Success Rate</span>
        <span class="audit-stat-value" id="audit-stat-success">—</span>
        <span class="audit-stat-sub" id="audit-stat-nodes-sub">— nodes active</span>
    </div>
</div>

<!-- ── Filter Bar ──────────────────────────────────────────────────────── -->
<div class="audit-filters">
    <div class="audit-filter-group">
        <label>Node</label>
        <select id="audit-f-node">
            <option value="">All Nodes</option>
            <?php foreach ($nodeNames as $nid => $name): ?>
            <option value="<?= $nid ?>"><?= htmlspecialchars($name) ?> (<?= $nid ?>)</option>
            <?php endforeach; ?>
        </select>
    </div>
    <div class="audit-filter-group">
        <label>Tool</label>
        <select id="audit-f-tool">
            <option value="">All Tools</option>
        </select>
    </div>
    <div class="audit-filter-group">
        <label>Severity</label>
        <select id="audit-f-severity">
            <option value="">All</option>
            <option value="info">Info</option>
            <option value="low">Low</option>
            <option value="medium">Medium</option>
            <option value="high">High</option>
            <option value="critical">Critical</option>
        </select>
    </div>
    <div class="audit-filter-group">
        <label>Status</label>
        <select id="audit-f-status">
            <option value="">All</option>
            <option value="success">Success</option>
            <option value="failed">Failed</option>
            <option value="pending">Pending</option>
            <option value="timeout">Timeout</option>
            <option value="killed">Killed</option>
        </select>
    </div>
    <div class="audit-filter-group">
        <label>From</label>
        <input type="date" id="audit-f-from">
    </div>
    <div class="audit-filter-group">
        <label>To</label>
        <input type="date" id="audit-f-to">
    </div>

    <button class="audit-btn audit-btn-primary" onclick="AuditTab.applyFilters()">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
        Apply
    </button>
    <button class="audit-btn audit-btn-secondary" onclick="AuditTab.clearFilters()">Clear</button>

    <div class="audit-stream-status">
        <span class="audit-stream-dot disconnected" id="audit-stream-dot"></span>
        <span id="audit-stream-label">Connecting…</span>
    </div>
</div>

<!-- ── Data Table ──────────────────────────────────────────────────────── -->
<div class="audit-table-wrap">
    <table class="audit-table">
        <thead>
            <tr>
                <th>Time</th>
                <th>Node</th>
                <th>Tool</th>
                <th>Category</th>
                <th>Severity</th>
                <th>Status</th>
                <th>Duration</th>
                <th>Trust</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody id="audit-tbody">
            <tr><td colspan="9" style="text-align:center;padding:40px;color:rgba(255,255,255,.3)">Loading audit entries…</td></tr>
        </tbody>
    </table>

    <!-- Pagination -->
    <div class="audit-pagination">
        <span id="audit-page-info">Page 1 of 1 · 0 entries</span>
        <div class="audit-page-btns">
            <button class="audit-page-btn" id="audit-prev" onclick="AuditTab.prevPage()" disabled>← Prev</button>
            <span id="audit-page-nums"></span>
            <button class="audit-page-btn" id="audit-next" onclick="AuditTab.nextPage()" disabled>Next →</button>
        </div>
    </div>
</div>

</div><!-- /.audit-wrap -->

<!-- ── Decrypt Modal ──────────────────────────────────────────────────── -->
<div class="audit-modal-overlay" id="audit-decrypt-modal">
    <div class="audit-modal">
        <div class="audit-modal-header">
            <h3 id="audit-modal-title">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
                Decrypt Audit Entry
            </h3>
            <button class="audit-modal-close" onclick="AuditTab.closeModal()">×</button>
        </div>
        <div class="audit-modal-body">
            <!-- Pre-decrypt form -->
            <div id="audit-decrypt-form">
                <div style="margin-bottom:14px">
                    <span class="audit-field-label">Entry ID</span>
                    <span id="audit-decrypt-id" style="color:#fff;font-weight:600;font-family:monospace"></span>
                </div>
                <div>
                    <span class="audit-field-label">Reason for Decryption</span>
                    <textarea id="audit-decrypt-reason" placeholder="Provide a justification (min 10 characters)…" maxlength="500"></textarea>
                </div>
                <div class="audit-rate-warn" id="audit-rate-warn" style="display:none">
                    ⚠ You are approaching the rate limit. <span id="audit-rate-remaining"></span> decryptions remaining this minute.
                </div>
            </div>
            <!-- Decrypted content viewer -->
            <div id="audit-decrypted-view" style="display:none">
                <span class="audit-field-label">Decrypted Statement</span>
                <div class="audit-decrypted-content" id="audit-decrypted-content"></div>
                <div style="margin-top:12px;font-size:.72rem;color:rgba(255,255,255,.35)">
                    Decrypted at <span id="audit-decrypt-ts"></span> · This action has been logged.
                </div>
            </div>
        </div>
        <div class="audit-modal-footer" id="audit-modal-footer">
            <button class="audit-btn audit-btn-secondary" onclick="AuditTab.closeModal()">Cancel</button>
            <button class="audit-btn audit-btn-primary" id="audit-decrypt-submit" onclick="AuditTab.submitDecrypt()">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
                Decrypt
            </button>
        </div>
    </div>
</div>

<script nonce="<?= $cspNonce ?? '' ?>">
/* ══════════════════════════════════════════════════════════════════════════
   SCLS Audit Tab — Client-Side Logic
   ══════════════════════════════════════════════════════════════════════ */
const AuditTab = (() => {
    'use strict';

    /* ── Node metadata ────────────────────────────────────────────────── */
    const NODE_NAMES  = <?= json_encode($nodeNames) ?>;
    const NODE_COLORS = <?= json_encode($nodeColors) ?>;

    /* ── State ────────────────────────────────────────────────────────── */
    let state = {
        page: 1,
        perPage: 50,
        totalPages: 1,
        total: 0,
        filters: {},
        entries: [],
        sse: null,
        sseRetries: 0,
    };

    /* ── Severity CSS class ───────────────────────────────────────────── */
    function sevClass(s) {
        return 'audit-sev-' + (s || 'info');
    }

    /* ── Status CSS class ─────────────────────────────────────────────── */
    function statusClass(s) {
        return 'audit-status-' + (s || 'pending');
    }

    /* ── Format duration ──────────────────────────────────────────────── */
    function fmtDuration(ms) {
        if (ms == null) return '—';
        if (ms < 1000) return ms + 'ms';
        if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
        return (ms / 60000).toFixed(1) + 'm';
    }

    /* ── Format time ──────────────────────────────────────────────────── */
    function fmtTime(ts) {
        if (!ts) return '—';
        const d = new Date(ts);
        const hh = String(d.getHours()).padStart(2, '0');
        const mm = String(d.getMinutes()).padStart(2, '0');
        const ss = String(d.getSeconds()).padStart(2, '0');
        const mo = String(d.getMonth() + 1).padStart(2, '0');
        const dd = String(d.getDate()).padStart(2, '0');
        return `${mo}-${dd} ${hh}:${mm}:${ss}`;
    }

    /* ── Escape HTML ──────────────────────────────────────────────────── */
    function esc(s) {
        if (s == null) return '';
        const d = document.createElement('div');
        d.textContent = String(s);
        return d.innerHTML;
    }

    /* ── Syntax highlight JSON ────────────────────────────────────────── */
    function highlightJson(obj) {
        const raw = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
        return raw.replace(
            /("(?:\\.|[^"\\])*")\s*:/g,
            '<span class="audit-json-key">$1</span>:'
        ).replace(
            /:\s*("(?:\\.|[^"\\])*")/g,
            ': <span class="audit-json-str">$1</span>'
        ).replace(
            /:\s*(\d+\.?\d*)/g,
            ': <span class="audit-json-num">$1</span>'
        ).replace(
            /:\s*(true|false)/g,
            ': <span class="audit-json-bool">$1</span>'
        ).replace(
            /:\s*(null)/g,
            ': <span class="audit-json-null">$1</span>'
        );
    }

    /* ── Build node badge ─────────────────────────────────────────────── */
    function nodeBadge(nid) {
        const name  = NODE_NAMES[nid] || nid;
        const color = NODE_COLORS[nid] || '#64748b';
        return `<span class="audit-node-badge" style="background:${color}15;color:${color}">` +
               `<span class="audit-node-dot" style="background:${color}"></span>${esc(name)}</span>`;
    }

    /* ── Build table row HTML ─────────────────────────────────────────── */
    function buildRow(e, isNew) {
        const cls = isNew ? ' class="audit-row-new"' : '';
        return `<tr${cls} data-id="${e.id}">
            <td>${fmtTime(e.started_at)}</td>
            <td>${nodeBadge(e.node_id)}</td>
            <td><code>${esc(e.tool_name)}</code></td>
            <td><span class="audit-tool-cat">${esc(e.tool_category || 'general')}</span></td>
            <td><span class="audit-badge ${sevClass(e.severity)}">${esc(e.severity)}</span></td>
            <td><span class="${statusClass(e.status)}">${esc(e.status)}</span></td>
            <td>${fmtDuration(e.duration_ms)}</td>
            <td>${e.trust_score != null ? parseFloat(e.trust_score).toFixed(2) : '—'}</td>
            <td><button class="audit-decrypt-btn" onclick="AuditTab.openDecrypt(${e.id})">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
                Decrypt
            </button></td>
        </tr>`;
    }

    /* ── Render table body ────────────────────────────────────────────── */
    function renderTable(entries) {
        const tbody = document.getElementById('audit-tbody');
        if (!entries.length) {
            tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;padding:40px;color:rgba(255,255,255,.3)">No audit entries match your filters</td></tr>';
            return;
        }
        tbody.innerHTML = entries.map(e => buildRow(e, false)).join('');
    }

    /* ── Render pagination ────────────────────────────────────────────── */
    function renderPagination(p) {
        document.getElementById('audit-page-info').textContent =
            `Page ${p.page} of ${p.total_pages} · ${p.total.toLocaleString()} entries`;

        document.getElementById('audit-prev').disabled = !p.has_prev;
        document.getElementById('audit-next').disabled = !p.has_next;

        // Page number buttons
        let nums = '';
        const start = Math.max(1, p.page - 2);
        const end   = Math.min(p.total_pages, p.page + 2);
        for (let i = start; i <= end; i++) {
            const cls = i === p.page ? ' active' : '';
            nums += `<button class="audit-page-btn${cls}" onclick="AuditTab.goPage(${i})">${i}</button>`;
        }
        document.getElementById('audit-page-nums').innerHTML = nums;
    }

    /* ── Render stats ─────────────────────────────────────────────────── */
    function renderStats(summary, total) {
        document.getElementById('audit-stat-total').textContent = total.toLocaleString();
        document.getElementById('audit-stat-total-sub').textContent =
            `${summary.tools_24h} distinct tools`;
        document.getElementById('audit-stat-rate').textContent =
            total > 0 ? (total / 24).toFixed(1) : '0';
        document.getElementById('audit-stat-success').textContent =
            summary.success_rate + '%';
        document.getElementById('audit-stat-nodes-sub').textContent =
            `${summary.nodes_active} nodes active`;
    }

    /* ── Fetch data ───────────────────────────────────────────────────── */
    async function fetchData() {
        const params = new URLSearchParams();
        params.set('page', state.page);
        params.set('per_page', state.perPage);

        // Apply filters
        const f = state.filters;
        if (f.node_id)  params.set('node_id',  f.node_id);
        if (f.tool_id)  params.set('tool_id',  f.tool_id);
        if (f.severity) params.set('severity', f.severity);
        if (f.status)   params.set('status',   f.status);
        if (f.from)     params.set('from',     f.from);
        if (f.to)       params.set('to',       f.to);

        try {
            const resp = await fetch('/api/audit/list.php?' + params.toString());
            const json = await resp.json();
            if (json.status !== 'ok') throw new Error(json.error || 'Unknown error');

            state.entries    = json.data.entries;
            state.page       = json.data.pagination.page;
            state.totalPages = json.data.pagination.total_pages;
            state.total      = json.data.pagination.total;

            renderTable(state.entries);
            renderPagination(json.data.pagination);
            renderStats(json.data.summary, json.data.pagination.total);
        } catch (err) {
            console.error('Audit fetch error:', err);
            document.getElementById('audit-tbody').innerHTML =
                `<tr><td colspan="9" style="text-align:center;padding:40px;color:#ef4444">
                    Error loading data: ${esc(err.message)}</td></tr>`;
        }
    }

    /* ── Filter controls ──────────────────────────────────────────────── */
    function applyFilters() {
        state.filters = {
            node_id:  document.getElementById('audit-f-node').value,
            tool_id:  document.getElementById('audit-f-tool').value,
            severity: document.getElementById('audit-f-severity').value,
            status:   document.getElementById('audit-f-status').value,
            from:     document.getElementById('audit-f-from').value,
            to:       document.getElementById('audit-f-to').value,
        };
        state.page = 1;
        fetchData();
    }

    function clearFilters() {
        document.getElementById('audit-f-node').value = '';
        document.getElementById('audit-f-tool').value = '';
        document.getElementById('audit-f-severity').value = '';
        document.getElementById('audit-f-status').value = '';
        document.getElementById('audit-f-from').value = '';
        document.getElementById('audit-f-to').value = '';
        state.filters = {};
        state.page = 1;
        fetchData();
    }

    /* ── Pagination ───────────────────────────────────────────────────── */
    function goPage(p) { state.page = p; fetchData(); }
    function prevPage() { if (state.page > 1) goPage(state.page - 1); }
    function nextPage() { if (state.page < state.totalPages) goPage(state.page + 1); }

    /* ── SSE Stream ───────────────────────────────────────────────────── */
    function connectSSE() {
        if (state.sse) {
            state.sse.close();
            state.sse = null;
        }

        const dot   = document.getElementById('audit-stream-dot');
        const label = document.getElementById('audit-stream-label');

        // Get the latest ID from current table
        let lastId = 0;
        if (state.entries.length > 0) {
            lastId = Math.max(...state.entries.map(e => parseInt(e.id, 10)));
        }

        const url = '/api/audit/stream.php?last_id=' + lastId;
        const es = new EventSource(url);

        es.addEventListener('connected', function(e) {
            dot.className = 'audit-stream-dot connected';
            label.textContent = 'Live';
            state.sseRetries = 0;
        });

        es.addEventListener('audit', function(e) {
            try {
                const entry = JSON.parse(e.data);
                // Only add if we're on page 1 with no filters
                if (state.page === 1 && Object.keys(state.filters).filter(k => state.filters[k]).length === 0) {
                    const tbody = document.getElementById('audit-tbody');
                    // Check for "no entries" placeholder
                    if (tbody.querySelector('td[colspan]')) {
                        tbody.innerHTML = '';
                    }
                    // Prepend new row
                    tbody.insertAdjacentHTML('afterbegin', buildRow(entry, true));
                    // Trim to perPage
                    while (tbody.children.length > state.perPage) {
                        tbody.removeChild(tbody.lastChild);
                    }
                    // Update stats counters
                    const totalEl = document.getElementById('audit-stat-total');
                    const cur = parseInt(totalEl.textContent.replace(/,/g, ''), 10) || 0;
                    totalEl.textContent = (cur + 1).toLocaleString();
                }
            } catch (err) {
                console.warn('SSE parse error:', err);
            }
        });

        es.addEventListener('timeout', function() {
            es.close();
            dot.className = 'audit-stream-dot disconnected';
            label.textContent = 'Reconnecting…';
            setTimeout(connectSSE, 2000);
        });

        es.addEventListener('error', function() {
            dot.className = 'audit-stream-dot disconnected';
            label.textContent = 'Disconnected';
            es.close();
            state.sseRetries++;
            const delay = Math.min(30000, 1000 * Math.pow(2, state.sseRetries));
            setTimeout(connectSSE, delay);
        });

        state.sse = es;
    }

    /* ── Decrypt modal ────────────────────────────────────────────────── */
    let decryptTargetId = null;

    function openDecrypt(id) {
        decryptTargetId = id;
        document.getElementById('audit-decrypt-id').textContent = '#' + id;
        document.getElementById('audit-decrypt-reason').value = '';
        document.getElementById('audit-decrypt-form').style.display = '';
        document.getElementById('audit-decrypted-view').style.display = 'none';
        document.getElementById('audit-decrypt-submit').disabled = false;
        document.getElementById('audit-decrypt-submit').style.display = '';
        document.getElementById('audit-rate-warn').style.display = 'none';
        document.getElementById('audit-modal-title').innerHTML =
            '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg> Decrypt Audit Entry';
        document.getElementById('audit-decrypt-modal').classList.add('active');
    }

    function closeModal() {
        document.getElementById('audit-decrypt-modal').classList.remove('active');
        decryptTargetId = null;
    }

    async function submitDecrypt() {
        if (!decryptTargetId) return;

        const reason = document.getElementById('audit-decrypt-reason').value.trim();
        if (reason.length < 10) {
            alert('Reason must be at least 10 characters.');
            return;
        }

        const btn = document.getElementById('audit-decrypt-submit');
        btn.disabled = true;
        btn.textContent = 'Decrypting…';

        try {
            const resp = await fetch('/api/audit/decrypt.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ audit_id: decryptTargetId, reason }),
            });
            const json = await resp.json();

            if (resp.status === 429) {
                document.getElementById('audit-rate-warn').style.display = '';
                document.getElementById('audit-rate-remaining').textContent =
                    (json.remaining || 0) + '';
                btn.disabled = false;
                btn.textContent = 'Decrypt';
                return;
            }

            if (json.status !== 'ok') throw new Error(json.error || 'Decryption failed');

            // Show decrypted content
            document.getElementById('audit-decrypt-form').style.display = 'none';
            document.getElementById('audit-decrypted-view').style.display = '';
            btn.style.display = 'none';

            const content = json.data.decrypted.statement;
            document.getElementById('audit-decrypted-content').innerHTML =
                highlightJson(content);
            document.getElementById('audit-decrypt-ts').textContent =
                json.data.decrypted.decrypted_at || new Date().toISOString();

            document.getElementById('audit-modal-title').innerHTML =
                '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 018 0"/></svg> Decrypted Content';

            // Update decryptions counter
            const decEl = document.getElementById('audit-stat-decryptions');
            const cur = parseInt(decEl.textContent, 10) || 0;
            decEl.textContent = (cur + 1).toString();

            // Show remaining rate limit
            if (json.data.rate_limit) {
                document.getElementById('audit-stat-decrypt-sub').textContent =
                    `${json.data.rate_limit.remaining} remaining · ${json.data.rate_limit.total} total`;
            }
        } catch (err) {
            alert('Decryption error: ' + err.message);
            btn.disabled = false;
            btn.textContent = 'Decrypt';
        }
    }

    /* ── Load tool registry for filter dropdown ───────────────────────── */
    async function loadToolRegistry() {
        // We'll populate the tool dropdown from the first fetch's distinct tools
        // For now, use a static known set matching the SQL seed data
        const tools = [
            { id: 'a1f0', name: 'postfix-qmgr-aux' },
            { id: 'b2e0', name: 'oomd-report.sh' },
            { id: 'c3d0', name: 'common.sh' },
            { id: 'd4c0', name: 'svcbeat' },
            { id: 'd4c2', name: 'runtime-health.sh' },
            { id: 'e5b0', name: 'svcfile' },
            { id: 'e5b1', name: 'svcfw' },
            { id: 'f6a0', name: 'svcpass' },
            { id: 'f6a1', name: 'security-audit.sh' },
            { id: 'f6a9', name: 'log_to_db.sh' },
            { id: '1a00', name: 'network-control.sh' },
            { id: '2b00', name: 'data-backup.sh' },
            { id: '4d00', name: 'phoenix-core.sh' },
            { id: '7a00', name: 'tvault' },
            { id: '9d00', name: 'security-analyzer.sh' },
            { id: '3c00', name: 'fleet_audit.sh' },
        ];
        const sel = document.getElementById('audit-f-tool');
        tools.forEach(t => {
            const opt = document.createElement('option');
            opt.value = t.id;
            opt.textContent = `${t.name} (${t.id})`;
            sel.appendChild(opt);
        });
    }

    /* ── Init ─────────────────────────────────────────────────────────── */
    function init() {
        loadToolRegistry();
        fetchData();
        connectSSE();

        // Decryptions stat — fetch a rough count
        document.getElementById('audit-stat-decryptions').textContent = '0';
    }

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

    /* ── Public API ───────────────────────────────────────────────────── */
    return {
        applyFilters,
        clearFilters,
        goPage,
        prevPage,
        nextPage,
        openDecrypt,
        closeModal,
        submitDecrypt,
    };
})();
</script>
