const SECTIONS = {
  overview: {
    title: "Overview",
    desc: "Governance posture across autonomous agents",
    panelTitle: "Live activity stream",
    panelSubtitle: "Recent policy enforcement events across all agents",
  },
  activity: {
    title: "Live Activity",
    desc: "Real-time enforcement and interception events",
    panelTitle: "Agent activity",
    panelSubtitle: "SIEM-style stream of governed execution attempts",
  },
  decisions: {
    title: "Governance Decisions",
    desc: "Allow, deny, and escalation analytics",
    panelTitle: "Decision analytics",
    panelSubtitle: "Distribution and agents driving policy outcomes",
  },
  agents: {
    title: "Agent Registry",
    desc: "Registered agents (#2) — identity catalog for policy enforcement",
    panelTitle: "Agent registry",
    panelSubtitle: "GET /api/v1/agents · Postgres-backed when DATABASE_URL is set",
  },
  policies: {
    title: "Policies",
    desc: "Active governance policy bundles",
    panelTitle: "Policy catalog",
    panelSubtitle: "Rego policies governing execution surfaces",
  },
  approvals: {
    title: "Approvals",
    desc: "Human-in-the-loop escalation queue",
    panelTitle: "Approval queue",
    panelSubtitle: "Click a row to review · Approve or deny in the inspector panel",
  },
  traces: {
    title: "Traces",
    desc: "Governance trace investigation",
    panelTitle: "Trace log",
    panelSubtitle: "Correlate enforcement decisions by trace identifier",
  },
  replay: {
    title: "Replay",
    desc: "Deterministic governance forensics",
    panelTitle: "Replay engine",
    panelSubtitle: "Reconstruct execution chains for audit and compliance",
  },
  settings: {
    title: "Settings",
    desc: "Control plane connection and preferences",
    panelTitle: "Configuration",
    panelSubtitle: "Interceptor API endpoint and refresh behavior",
  },
};

/** Which KPI tiles appear per tab (no duplicate 6-tile row everywhere). */
const SECTION_KPIS = {
  overview: ["agents", "allow", "deny", "escalate", "high_risk", "total"],
  activity: ["total", "agents", "allow"],
  decisions: ["allow", "deny", "escalate", "total"],
  agents: ["agents", "total"],
  policies: [],
  approvals: [],
  traces: ["total", "agents"],
  replay: [],
  settings: [],
};

const KPI_META = {
  agents: {
    label: "Active agents",
    accent: "",
    hint: "Click for agent list",
    value: (m) => m.active_agents ?? "—",
  },
  allow: {
    label: "Allow rate",
    accent: "allow",
    hint: "Click for allowed events",
    value: (m) => (m.allow_pct != null ? `${m.allow_pct.toFixed(1)}%` : "—"),
  },
  deny: {
    label: "Deny rate",
    accent: "deny",
    hint: "Click for denied events",
    value: (m) => (m.deny_pct != null ? `${m.deny_pct.toFixed(1)}%` : "—"),
  },
  escalate: {
    label: "Escalations",
    accent: "escalate",
    hint: "Click for escalated events",
    value: (m) => m.escalate_count ?? "—",
  },
  high_risk: {
    label: "High-risk actions",
    accent: "",
    hint: "Risk score > 0.8",
    value: (m) => m.high_risk_count ?? "—",
  },
  pending: {
    label: "Pending approvals",
    accent: "escalate",
    hint: "Awaiting human approve/deny",
    value: () => getPendingApprovals().length,
  },
  total: {
    label: "Governed events",
    accent: "",
    hint: "Click for event summary",
    value: (m) => m.total_events ?? "—",
  },
};

const DRILL_TITLES = {
  agents: "Active agents — detail",
  allow: "Allowed actions",
  deny: "Denied actions",
  escalate: "Escalated actions",
  high_risk: "High-risk actions",
  total: "Governed events — summary",
};

const apiBaseEl = document.getElementById("apiBase");
const btnRefresh = document.getElementById("btnRefresh");
const connStatus = document.getElementById("connStatus");
const metricsBar = document.getElementById("metricsBar");
const sectionContent = document.getElementById("sectionContent");
const traceDetail = document.getElementById("traceDetail");
const traceSummary = document.getElementById("traceSummary");
const pageTitle = document.getElementById("pageTitle");
const pageDesc = document.getElementById("pageDesc");
const breadcrumbSection = document.getElementById("breadcrumbSection");
const panelTitle = document.getElementById("panelTitle");
const panelSubtitle = document.getElementById("panelSubtitle");
const lastUpdated = document.getElementById("lastUpdated");

let events = [];
let metrics = {};
let approvals = [];
let approvalsApiOk = true;
let registryAgents = [];
let registryApiOk = true;
let selectedApprovalTraceId = null;
let selectedIdx = null;
let currentSection = "overview";
let kpiDrill = null;
let pollTimer = null;
let policyCatalog = [];
let policyCatalogWarning = "";
let selectedPolicyKey = null;
let selectedPolicyRuleId = null;

function apiBase() {
  const v = apiBaseEl.value.replace(/\/$/, "").trim();
  if (v) return v;
  return window.location.origin;
}

function defaultApiBase() {
  const origin = window.location.origin;
  const p = window.location.pathname;
  // Gateway (:3090) or dashboard with API proxy (:3001) — same origin
  if (p.startsWith("/dashboard/") || p === "/dashboard") {
    return origin;
  }
  if (window.location.port === "3001" || window.location.port === "3090") {
    return origin;
  }
  return "http://localhost:3090";
}

function fmtTime(iso) {
  if (!iso) return "—";
  const d = new Date(iso);
  return d.toLocaleString(undefined, {
    month: "short",
    day: "numeric",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
}

function fmtRisk(v) {
  if (v == null) return "—";
  const n = Number(v);
  const cls = n > 0.8 ? "cell-mono risk-high" : "cell-mono";
  return `<span class="${cls}">${n.toFixed(2)}</span>`;
}

function setConnectionState(state, label) {
  connStatus.dataset.state = state;
  connStatus.querySelector(".status-label").textContent = label;
}

async function fetchJSON(path) {
  const r = await fetch(`${apiBase()}${path}`);
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

async function postJSON(path, body) {
  const r = await fetch(`${apiBase()}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body || {}),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

function deriveAgentsFromEvents(evts) {
  const map = new Map();
  for (const e of evts) {
    if (!e.agent_id) continue;
    let a = map.get(e.agent_id);
    if (!a) {
      a = {
        id: e.agent_id,
        events: 0,
        allow: 0,
        deny: 0,
        escalate: 0,
        tools: new Set(),
        actions: new Set(),
        lastSeen: e.timestamp,
        highRisk: 0,
        lastDecision: e.decision,
      };
      map.set(e.agent_id, a);
    }
    a.events++;
    if (e.decision === "allow") a.allow++;
    else if (e.decision === "deny") a.deny++;
    else if (e.decision === "escalate") a.escalate++;
    if (e.tool) a.tools.add(e.tool);
    if (e.action) a.actions.add(e.action);
    if (e.risk_score != null && Number(e.risk_score) > 0.8) a.highRisk++;
    const ts = new Date(e.timestamp);
    if (!a.lastSeen || ts > new Date(a.lastSeen)) {
      a.lastSeen = e.timestamp;
      a.lastDecision = e.decision;
    }
  }
  return Array.from(map.values()).sort((x, y) => y.events - x.events);
}

function filterEventsByDrill(drill) {
  switch (drill) {
    case "allow":
      return events.filter((e) => e.decision === "allow");
    case "deny":
      return events.filter((e) => e.decision === "deny");
    case "escalate":
      return events.filter((e) => e.decision === "escalate");
    case "high_risk":
      return events.filter((e) => e.risk_score != null && Number(e.risk_score) > 0.8);
    default:
      return events;
  }
}

async function fetchPolicyCatalog() {
  try {
    const r = await fetch(`${apiBase()}/api/v1/policies/catalog`);
    if (r.status === 404) {
      return {
        policies: [],
        warning:
          "Catalog API not found (404). Rebuild the interceptor: docker compose -f interceptor/deployments/docker/docker-compose.yml up --build -d interceptor",
      };
    }
    if (!r.ok) throw new Error(String(r.status));
    return await r.json();
  } catch {
    return {
      policies: [],
      warning: "Could not load policy catalog. Check interceptor is running and POLICIES_DIR is set.",
    };
  }
}

function policyCardKey(p) {
  return p.policy_id || `${p.entry_kind}:${p.domain}`;
}

function decisionBadgeClass(decision) {
  if (decision === "allow") return "badge-allow";
  if (decision === "deny") return "badge-deny";
  if (decision === "escalate") return "badge-escalate";
  return "";
}

function formatConditions(conds) {
  if (!conds || !conds.length) return "—";
  return conds.join("; ");
}

function eventsMatchingPolicy(p) {
  if (!p) return [];
  if (p.entry_kind === "router") {
    return events.slice(0, 80);
  }
  const pid = p.policy_id || "";
  if (!pid) return [];
  return events.filter((e) => {
    const pol = e.policy || "";
    return pol === pid || pol.startsWith(`${pid}:`);
  });
}

function policiesInspectorPlaceholder() {
  traceSummary.className = "trace-summary empty-inspector";
  traceSummary.innerHTML =
    "<p>Select a policy bundle, then a rule row, to see conditions and matching enforcement events.</p>";
  traceDetail.textContent = "—";
}

function eventsMatchingRule(p, rule) {
  const base = eventsMatchingPolicy(p);
  if (!rule || !rule.reason) return base;
  return base.filter((e) => e.reason === rule.reason);
}

function showPolicyInspector(p, rule) {
  const matches = rule ? eventsMatchingRule(p, rule) : eventsMatchingPolicy(p);
  const counts = { allow: 0, deny: 0, escalate: 0 };
  matches.forEach((e) => {
    if (e.decision === "allow") counts.allow++;
    else if (e.decision === "deny") counts.deny++;
    else if (e.decision === "escalate") counts.escalate++;
  });
  traceSummary.className = "trace-summary";
  if (rule) {
    const condHtml =
      rule.conditions && rule.conditions.length
        ? `<dt>Conditions</dt><dd><ul class="rule-cond-list">${rule.conditions
            .map((c) => `<li><code>${escapeHtml(c)}</code></li>`)
            .join("")}</ul></dd>`
        : rule.is_default
          ? "<dt>Conditions</dt><dd>Default rule (no extra guards)</dd>"
          : "";
    traceSummary.innerHTML = `
      <dl>
        <dt>Bundle</dt><dd><span class="cell-mono">${escapeHtml(p.policy_id || p.domain)}</span></dd>
        <dt>Outcome</dt><dd><span class="badge ${decisionBadgeClass(rule.decision)}">${escapeHtml(rule.decision)}</span></dd>
        <dt>Reason</dt><dd><span class="cell-mono">${escapeHtml(rule.reason)}</span></dd>
        ${condHtml}
        <dt>Events (reason match)</dt><dd>${matches.length} in buffer</dd>
        <dt>Decisions</dt><dd>${counts.allow} allow · ${counts.deny} deny · ${counts.escalate} escalate</dd>
      </dl>`;
    traceDetail.textContent =
      matches.length > 0
        ? JSON.stringify(matches.slice(0, 20), null, 2)
        : "No events in the current buffer matched this rule's reason code.";
    return;
  }
  const pidLine =
    p.entry_kind === "router"
      ? "<dt>Role</dt><dd>Routes <code>input.tool</code> to domain policy bundles.</dd>"
      : `<dt>Policy id</dt><dd><span class="cell-mono">${escapeHtml(p.policy_id)}</span></dd>`;
  traceSummary.innerHTML = `
    <dl>
      <dt>Package</dt><dd><span class="cell-mono">${escapeHtml(p.package)}</span></dd>
      ${pidLine}
      <dt>Rules</dt><dd>${p.rule_count ?? (p.rules || p.routes || []).length}</dd>
      <dt>Events in buffer</dt><dd>${matches.length}</dd>
      <dt>Decisions</dt><dd>${counts.allow} allow · ${counts.deny} deny · ${counts.escalate} escalate</dd>
    </dl>
    <p class="form-hint" style="margin-top:0.75rem">Click a rule row for conditions and reason-matched events.</p>`;
  traceDetail.textContent = "—";
}

function renderPolicyRulesTable(p) {
  const rules = p.rules || [];
  if (!rules.length) {
    return `<p class="form-hint">No decision rules parsed from this bundle.</p>`;
  }
  const rows = rules
    .map((r) => {
      const sel =
        selectedPolicyRuleId === r.id ? " selected" : "";
      const cond = formatConditions(r.conditions);
      const summary = r.summary || r.reason.replace(/_/g, " ");
      return `<tr class="policy-rule-row${sel}" data-rule-id="${escapeHtml(r.id)}" tabindex="0">
        <td><span class="badge ${decisionBadgeClass(r.decision)}">${escapeHtml(r.decision)}</span></td>
        <td class="cell-mono">${escapeHtml(r.reason)}</td>
        <td>${escapeHtml(summary)}</td>
        <td class="cell-muted cell-cond">${escapeHtml(cond)}</td>
        <td>${r.priority}</td>
      </tr>`;
    })
    .join("");
  return `<div class="table-wrap">
    <table class="data" id="policyRulesTable">
      <thead><tr>
        <th>Outcome</th><th>Reason</th><th>Summary</th><th>Conditions</th><th>Order</th>
      </tr></thead>
      <tbody>${rows}</tbody>
    </table>
  </div>`;
}

function renderPolicyRoutesTable(p) {
  const routes = p.routes || [];
  if (!routes.length) {
    return `<p class="form-hint">No tool routes parsed from router bundle.</p>`;
  }
  const rows = routes
    .map(
      (r) => `<tr>
        <td class="cell-mono">${escapeHtml(r.tool)}</td>
        <td class="cell-mono">${escapeHtml(r.policy_id)}</td>
        <td class="cell-muted">${escapeHtml(r.target || "—")}</td>
      </tr>`
    )
    .join("");
  return `<div class="table-wrap">
    <table class="data">
      <thead><tr><th>input.tool</th><th>Policy bundle</th><th>Delegates to</th></tr></thead>
      <tbody>${rows}</tbody>
    </table>
  </div>`;
}

function renderPolicyDetail(p) {
  const title = p.title || p.domain;
  const isRouter = p.entry_kind === "router";
  const table = isRouter ? renderPolicyRoutesTable(p) : renderPolicyRulesTable(p);
  const heading = isRouter ? "Tool routing" : "Decision rules";
  const hint = isRouter
    ? "Each row is how the top-level <code>acp</code> package delegates <code>input.tool</code> to a domain bundle."
    : "Parsed from live Rego on the interceptor — same source OPA evaluates. Rules are ordered by appearance in the file.";
  return `<div class="section-block">
    <div class="drill-toolbar">
      <button type="button" class="btn btn-ghost btn-back" id="btnPolicyBack">← All policies</button>
      <h3 class="drill-title">${escapeHtml(title)}</h3>
    </div>
    <p class="form-hint policy-detail-meta">package <span class="cell-mono">${escapeHtml(p.package)}</span> · <span class="cell-mono">${escapeHtml(p.policy_id)}</span></p>
    <h4 class="section-heading">${heading}</h4>
    <p class="form-hint" style="margin-bottom:1rem">${hint}</p>
    ${table}
  </div>`;
}

async function loadData() {
  setConnectionState("unknown", "Connecting");
  try {
    const [ev, met, cat, appr, reg] = await Promise.all([
      fetchJSON("/api/v1/events?limit=100"),
      fetchJSON("/api/v1/metrics/summary"),
      fetchPolicyCatalog(),
      fetchJSON("/api/v1/approvals?limit=100")
        .then((data) => {
          approvalsApiOk = true;
          return data;
        })
        .catch(() => {
          approvalsApiOk = false;
          return { approvals: [] };
        }),
      fetchJSON("/api/v1/agents?limit=100")
        .then((data) => {
          registryApiOk = true;
          return data;
        })
        .catch(() => {
          registryApiOk = false;
          return { agents: [] };
        }),
    ]);
    events = ev.events || [];
    metrics = met;
    approvals = appr.approvals || [];
    registryAgents = reg.agents || [];
    if (!registryApiOk && registryAgents.length === 0) {
      console.warn(
        "Agent registry API failed — open dashboard via http://localhost:3090/dashboard/ or rebuild interceptor."
      );
    }
    policyCatalog = Array.isArray(cat.policies) ? cat.policies : [];
    policyCatalogWarning = typeof cat.warning === "string" ? cat.warning : "";
    setConnectionState("live", "Live");
    lastUpdated.textContent = `Updated ${new Date().toLocaleTimeString()}`;
  } catch {
    setConnectionState("offline", "Offline");
    lastUpdated.textContent = "Unable to reach control plane";
    if (events.length === 0) events = [];
  }
  renderMetricsBar();
  renderSection();
}

function renderMetricsBar() {
  const keys = SECTION_KPIS[currentSection] || [];
  if (!keys.length) {
    metricsBar.hidden = true;
    metricsBar.innerHTML = "";
    return;
  }
  metricsBar.hidden = false;
  metricsBar.style.gridTemplateColumns = `repeat(${Math.min(keys.length, 6)}, 1fr)`;

  metricsBar.innerHTML = keys
    .map((key) => {
      const meta = KPI_META[key];
      const accent = meta.accent ? ` kpi-accent-${meta.accent}` : "";
      const active = kpiDrill === key ? " kpi-active" : "";
      return `<article class="kpi-card kpi-clickable${accent}${active}" data-kpi="${key}" role="button" tabindex="0" aria-label="${escapeHtml(meta.label)} — ${escapeHtml(meta.hint)}">
        <span class="kpi-label">${escapeHtml(meta.label)}</span>
        <span class="kpi-value">${escapeHtml(String(meta.value(metrics)))}</span>
        <span class="kpi-hint">${escapeHtml(meta.hint)}</span>
      </article>`;
    })
    .join("");

  metricsBar.querySelectorAll("[data-kpi]").forEach((card) => {
    const open = () => openKpiDrill(card.dataset.kpi);
    card.addEventListener("click", open);
    card.addEventListener("keydown", (ev) => {
      if (ev.key === "Enter" || ev.key === " ") {
        ev.preventDefault();
        open();
      }
    });
  });
}

function openKpiDrill(key) {
  kpiDrill = key;
  selectedIdx = null;
  renderMetricsBar();
  renderSection();
  showKpiDrillInspector(key);
}

function clearKpiDrill() {
  kpiDrill = null;
  renderMetricsBar();
  renderSection();
  showTrace(null);
}

function showKpiDrillInspector(drill) {
  const agents = deriveAgentsFromEvents(events);
  const filtered = filterEventsByDrill(drill);

  traceSummary.className = "trace-summary";
  let html = "";

  switch (drill) {
    case "agents":
      html = `<p><strong>${agents.length}</strong> agent(s) in the current window.</p><ul class="risk-list">`;
      agents.forEach((a) => {
        html += `<li><strong>${escapeHtml(a.id)}</strong> — ${a.events} events (${a.allow} allow, ${a.deny} deny, ${a.escalate} escalate)</li>`;
      });
      html += "</ul>";
      if (!agents.length) html = "<p>No agents observed yet. Run governed tool calls from your agents.</p>";
      break;
    case "allow":
    case "deny":
    case "escalate":
      html = `<p><strong>${filtered.length}</strong> ${drill} event(s) of ${events.length} total.</p>`;
      if (filtered[0]) {
        html += `<dl>
          <dt>Latest agent</dt><dd>${escapeHtml(filtered[0].agent_id)}</dd>
          <dt>Latest tool</dt><dd>${escapeHtml(filtered[0].tool)} / ${escapeHtml(filtered[0].action)}</dd>
          <dt>Policy</dt><dd>${escapeHtml(filtered[0].policy || "—")}</dd>
        </dl>`;
      }
      break;
    case "high_risk":
      html = `<p><strong>${filtered.length}</strong> action(s) with risk score &gt; 0.8.</p>`;
      filtered.slice(0, 5).forEach((e) => {
        html += `<p class="form-hint">${escapeHtml(e.agent_id)} · ${escapeHtml(e.tool)} · risk ${Number(e.risk_score).toFixed(2)}</p>`;
      });
      break;
    case "total":
      html = `<dl>
        <dt>Total events</dt><dd>${metrics.total_events ?? 0}</dd>
        <dt>Allow</dt><dd>${metrics.allow_count ?? 0} (${metrics.allow_pct != null ? metrics.allow_pct.toFixed(1) : "0"}%)</dd>
        <dt>Deny</dt><dd>${metrics.deny_count ?? 0}</dd>
        <dt>Escalate</dt><dd>${metrics.escalate_count ?? 0}</dd>
        <dt>Active agents</dt><dd>${metrics.active_agents ?? 0}</dd>
      </dl>`;
      break;
    default:
      html = "<p>Select a row in the table for full trace JSON.</p>";
  }

  traceSummary.innerHTML = html;
  traceDetail.textContent =
    filtered.length > 0
      ? JSON.stringify(drill === "agents" ? agents : filtered.slice(0, 10), null, 2)
      : "—";
}

function decisionBadge(d) {
  const key = (d || "").toLowerCase();
  const safe = ["allow", "deny", "escalate"].includes(key) ? key : "escalate";
  const label = (d || "unknown").toUpperCase();
  return `<span class="badge badge-${safe}">${label}</span>`;
}

function emptyState(title, body, icon = "◎") {
  return `<div class="empty-state">
    <div class="empty-state-icon">${icon}</div>
    <h3>${escapeHtml(title)}</h3>
    <p>${body}</p>
  </div>`;
}

function drillToolbar() {
  return `<div class="drill-toolbar">
    <button type="button" class="btn btn-ghost btn-back" id="btnDrillBack">← Back</button>
    <h3 class="drill-title">${escapeHtml(DRILL_TITLES[kpiDrill] || "Detail")}</h3>
  </div>`;
}

function eventIndex(e) {
  let i = events.indexOf(e);
  if (i >= 0) return i;
  if (e.trace_id) return events.findIndex((x) => x.trace_id === e.trace_id);
  return -1;
}

function activityTable(list) {
  const rows = list || events;
  if (!rows.length) {
    return emptyState(
      "No matching events",
      "Try another filter or generate traffic from your agents.",
      "⧉"
    );
  }

  const body = rows
    .map((e) => {
      const idx = eventIndex(e);
      const sel = selectedIdx === idx ? "selected" : "";
      return `<tr class="${sel}" data-idx="${idx}">
        <td class="cell-muted">${fmtTime(e.timestamp)}</td>
        <td class="cell-agent">${escapeHtml(e.agent_id)}</td>
        <td><span class="cell-mono">${escapeHtml(e.tool)}</span></td>
        <td><span class="cell-mono">${escapeHtml(e.action)}</span></td>
        <td>${decisionBadge(e.decision)}</td>
        <td>${fmtRisk(e.risk_score)}</td>
        <td class="cell-muted">${escapeHtml(e.policy || "—")}</td>
        <td class="cell-mono">${escapeHtml((e.trace_id || "").slice(0, 12))}…</td>
      </tr>`;
    })
    .join("");

  return `<div class="table-wrap">
    <table class="data" id="activityTable">
      <thead><tr>
        <th>Timestamp</th><th>Agent</th><th>Tool</th><th>Action</th>
        <th>Decision</th><th>Risk</th><th>Policy</th><th>Trace</th>
      </tr></thead>
      <tbody>${body}</tbody>
    </table>
  </div>`;
}

function renderKpiDrill() {
  const toolbar = drillToolbar();

  if (kpiDrill === "agents") {
    const agents = deriveAgentsFromEvents(events);
    if (!agents.length) {
      return (
        toolbar +
        emptyState(
          "No agents observed",
          "Agents appear when they call governed tools through the interceptor. Register agents via the API or your application.",
          "⬡"
        )
      );
    }
    const cards = agents
      .map(
        (a) => `<article class="agent-card" data-agent="${escapeHtml(a.id)}">
        <h3>${escapeHtml(a.id)}</h3>
        <dl>
          <dt>Total calls</dt><dd>${a.events}</dd>
          <dt>Decisions</dt><dd>${a.allow} allow · ${a.deny} deny · ${a.escalate} escalate</dd>
          <dt>Tools used</dt><dd>${escapeHtml([...a.tools].join(", ") || "—")}</dd>
          <dt>Actions</dt><dd>${escapeHtml([...a.actions].join(", ") || "—")}</dd>
          <dt>High-risk calls</dt><dd>${a.highRisk}</dd>
          <dt>Last activity</dt><dd>${fmtTime(a.lastSeen)} (${escapeHtml(a.lastDecision || "")})</dd>
        </dl>
      </article>`
      )
      .join("");
    return `${toolbar}<div class="agent-card-grid">${cards}</div>`;
  }

  if (kpiDrill === "total") {
    const agents = deriveAgentsFromEvents(events);
    const agentList = agents
      .map((a) => `<li><strong>${escapeHtml(a.id)}</strong> — ${a.events} governed calls</li>`)
      .join("");
    return `${toolbar}
      <div class="section-block">
        <h3 class="section-heading">Event window</h3>
        <p class="form-hint">${events.length} events in buffer · ${agents.length} unique agent(s)</p>
        <ul class="risk-list">${agentList || "<li>No agents yet</li>"}</ul>
      </div>
      ${activityTable(events.slice(0, 25))}`;
  }

  const filtered = filterEventsByDrill(kpiDrill);
  return `${toolbar}${activityTable(filtered)}`;
}

function renderDecisions() {
  const total = metrics.total_events || 0;
  const bars = [
    { label: "Allow", count: metrics.allow_count || 0, cls: "allow" },
    { label: "Deny", count: metrics.deny_count || 0, cls: "deny" },
    { label: "Escalate", count: metrics.escalate_count || 0, cls: "escalate" },
  ];
  const barHtml = bars
    .map((b) => {
      const pct = total ? (b.count / total) * 100 : 0;
      return `<div class="bar-row">
        <span>${b.label}</span>
        <div class="bar-track"><div class="bar-fill ${b.cls}" style="width:${Math.max(pct, 2)}%"></div></div>
        <span>${b.count}</span>
      </div>`;
    })
    .join("");

  const byAgent = {};
  events.forEach((e) => {
    if (e.decision === "escalate" || e.decision === "deny") {
      byAgent[e.agent_id] = (byAgent[e.agent_id] || 0) + 1;
    }
  });
  const risky = Object.entries(byAgent)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 8)
    .map(([a, c]) => `<li><strong>${escapeHtml(a)}</strong> — ${c} denied or escalated</li>`)
    .join("");

  return `<div class="section-block">
      <h3 class="section-heading">Decision distribution</h3>
      <p class="form-hint">Click Allow, Deny, or Escalations tiles above to see matching events.</p>
      <div class="chart-bars">${barHtml}</div>
    </div>
    <div class="section-block">
      <h3 class="section-heading">Agents with policy friction</h3>
      <ul class="risk-list">${risky || "<li>No denials or escalations in current window</li>"}</ul>
    </div>`;
}

function mergeRegistryWithActivity(registryList, activity) {
  const activityById = new Map(activity.map((a) => [a.id, a]));
  const merged = [];
  const seen = new Set();
  for (const r of registryList) {
    const id = r.agent_id;
    seen.add(id);
    const act = activityById.get(id);
    merged.push({
      id,
      displayName: r.display_name || id,
      tenant: r.tenant || "default",
      status: r.status || "active",
      roles: Array.isArray(r.roles) ? r.roles : [],
      events: act?.events ?? 0,
      allow: act?.allow ?? 0,
      deny: act?.deny ?? 0,
      escalate: act?.escalate ?? 0,
      tools: act?.tools ?? new Set(),
      lastSeen: act?.lastSeen,
      registered: true,
    });
  }
  for (const a of activity) {
    if (seen.has(a.id)) continue;
    merged.push({
      id: a.id,
      displayName: a.id,
      tenant: "—",
      status: "unregistered",
      roles: [],
      events: a.events,
      allow: a.allow,
      deny: a.deny,
      escalate: a.escalate,
      tools: a.tools,
      lastSeen: a.lastSeen,
      registered: false,
    });
  }
  return merged.sort((x, y) => y.events - x.events || x.id.localeCompare(y.id));
}

function renderAgents() {
  const activity = deriveAgentsFromEvents(events);
  const agents = mergeRegistryWithActivity(registryAgents, activity);
  if (!agents.length) {
    const hint = registryApiOk
      ? "No agents in the registry yet. POST to <code>/api/v1/agents</code> to register agents."
      : `Registry API failed (base: <code>${escapeHtml(apiBase())}</code>). Use <a href="http://localhost:3090/dashboard/#agents">http://localhost:3090/dashboard/</a> or set Settings → API to <code>http://localhost:8080</code>, then Save.`;
    return emptyState("No agents registered", hint, "⬡");
  }

  const registryBanner = !registryApiOk
    ? `<p class="form-hint" style="margin-bottom:1rem;color:#fbbf24">⚠ Registry API unreachable — showing activity-only agents. Fix API base in Settings.</p>`
    : "";

  const rows = agents
    .map((a) => {
      const statusBadge =
        a.status === "active"
          ? '<span class="badge badge-allow">active</span>'
          : `<span class="badge badge-deny">${escapeHtml(a.status)}</span>`;
      const regBadge = a.registered
        ? '<span class="badge badge-allow">registered</span>'
        : '<span class="badge badge-escalate">observed only</span>';
      return `<tr data-agent="${escapeHtml(a.id)}">
      <td class="cell-agent">${escapeHtml(a.id)}<br><small class="cell-muted">${escapeHtml(a.displayName)}</small></td>
      <td>${statusBadge} ${regBadge}</td>
      <td class="cell-muted">${escapeHtml(a.tenant)}</td>
      <td class="cell-muted">${escapeHtml(a.roles.join(", ") || "—")}</td>
      <td>${a.events}</td>
      <td><span class="badge badge-allow">${a.allow}</span></td>
      <td><span class="badge badge-deny">${a.deny}</span></td>
      <td><span class="badge badge-escalate">${a.escalate}</span></td>
      <td class="cell-muted">${escapeHtml([...(a.tools || [])].join(", ") || "—")}</td>
      <td class="cell-muted">${a.lastSeen ? fmtTime(a.lastSeen) : "—"}</td>
    </tr>`;
    })
    .join("");

  return `<div class="section-block">
    ${registryBanner}
    <h3 class="section-heading">Agent registry <span class="badge-sample">Live</span></h3>
    <p class="form-hint" style="margin-bottom:1rem">${agents.length} agent(s) — <strong>${registryAgents.length}</strong> from registry · call stats from audit events.</p>
    <div class="table-wrap">
      <table class="data" id="agentsTable">
        <thead><tr>
          <th>Agent</th><th>Status</th><th>Tenant</th><th>Roles</th><th>Calls</th><th>Allow</th><th>Deny</th><th>Escalate</th><th>Tools</th><th>Last seen</th>
        </tr></thead>
        <tbody>${rows}</tbody>
      </table>
    </div>
  </div>`;
}

function renderPolicies() {
  const warn = policyCatalogWarning
    ? `<p class="form-hint" style="margin-bottom:0.75rem">${escapeHtml(policyCatalogWarning)}</p>`
    : "";
  const selected = policyCatalog.find((x) => policyCardKey(x) === selectedPolicyKey);
  if (selected) {
    return warn + renderPolicyDetail(selected);
  }
  if (!policyCatalog.length) {
    return `<div class="section-block">${warn}${emptyState(
      "No Rego catalog",
      "The interceptor exposes Rego files from POLICIES_DIR at GET /api/v1/policies/catalog. In Docker Compose, policies are mounted at /policies on both OPA and the interceptor.",
      "⎔"
    )}</div>`;
  }

  const cards = policyCatalog
    .map((p) => {
      const key = policyCardKey(p);
      const sel = selectedPolicyKey === key ? " policy-card-selected" : "";
      const label = p.title || p.domain;
      const count = p.rule_count ?? 0;
      const countLabel = p.entry_kind === "router" ? `${count} routes` : `${count} rules`;
      return `<article class="policy-card${sel}" data-policy-key="${escapeHtml(key)}" tabindex="0" role="button" aria-label="Policy ${escapeHtml(label)}">
      <h3>${escapeHtml(label)} <small>${escapeHtml(p.policy_id)}</small></h3>
      <div class="policy-meta">
        <span class="badge badge-allow">${escapeHtml(p.entry_kind)}</span>
        <span class="cell-muted">${escapeHtml(countLabel)}</span>
      </div>
      <p class="policy-path">package <span class="cell-mono">${escapeHtml(p.package)}</span></p>
      <p class="policy-card-hint">Click to view rules →</p>
    </article>`;
    })
    .join("");

  return `<div class="section-block">
    ${warn}
    <p class="form-hint" style="margin-bottom:1rem">Rules are parsed from live Rego on the interceptor — single source of truth for OPA and this UI.</p>
    <div class="card-grid">${cards}</div>
  </div>`;
}

function bindPolicyCards() {
  document.getElementById("btnPolicyBack")?.addEventListener("click", () => {
    selectedPolicyKey = null;
    selectedPolicyRuleId = null;
    renderSection();
  });
  sectionContent.querySelectorAll(".policy-card[data-policy-key]").forEach((card) => {
    const open = () => {
      selectedPolicyKey = card.dataset.policyKey;
      selectedPolicyRuleId = null;
      renderSection();
    };
    card.addEventListener("click", open);
    card.addEventListener("keydown", (ev) => {
      if (ev.key === "Enter" || ev.key === " ") {
        ev.preventDefault();
        open();
      }
    });
  });
}

function bindPolicyRules() {
  const p = policyCatalog.find((x) => policyCardKey(x) === selectedPolicyKey);
  if (!p) return;
  sectionContent.querySelectorAll(".policy-rule-row[data-rule-id]").forEach((row) => {
    const open = () => {
      selectedPolicyRuleId = row.dataset.ruleId;
      const rule = (p.rules || []).find((r) => r.id === selectedPolicyRuleId);
      renderSection();
      if (rule) showPolicyInspector(p, rule);
    };
    row.addEventListener("click", open);
    row.addEventListener("keydown", (ev) => {
      if (ev.key === "Enter" || ev.key === " ") {
        ev.preventDefault();
        open();
      }
    });
  });
}

function getPendingApprovals() {
  return approvals.filter((a) => (a.status || "pending") === "pending");
}

function getApprovedApprovals() {
  return approvals.filter((a) => (a.status || "").toLowerCase() === "approved");
}

function getDeniedApprovals() {
  return approvals.filter((a) => (a.status || "").toLowerCase() === "denied");
}

function findApproval(traceId) {
  return approvals.find((a) => a.trace_id === traceId);
}

function approvalsInspectorPlaceholder() {
  traceSummary.className = "trace-summary empty-inspector";
  traceSummary.innerHTML =
    "<p><strong>Review queue</strong></p><p>Select a pending approval row to inspect the escalation and use <strong>Approve</strong> or <strong>Deny</strong>.</p>";
  traceDetail.textContent = "—";
}

function showApprovalInspector(a) {
  if (!a) {
    approvalsInspectorPlaceholder();
    return;
  }
  const status = (a.status || "pending").toLowerCase();
  if (status === "approved" || status === "denied") {
    showApprovalDecidedInspector(a);
    return;
  }

  selectedApprovalTraceId = a.trace_id;
  const trace = a.trace_id || "—";
  const amt =
    a.amount != null ? `$${Number(a.amount).toLocaleString()}` : "—";
  traceSummary.className = "trace-summary";
  traceSummary.innerHTML = `
    <p class="approval-review-label pending">Pending human review</p>
    <dl>
      <dt>Status</dt><dd><span class="badge badge-escalate">PENDING</span></dd>
      <dt>Trace ID</dt><dd class="cell-mono approval-trace-full">${escapeHtml(trace)}</dd>
      <dt>Agent</dt><dd>${escapeHtml(a.agent_id || "—")}</dd>
      <dt>Tool / action</dt><dd><span class="cell-mono">${escapeHtml(a.tool || "—")}</span> · <span class="cell-mono">${escapeHtml(a.action || "—")}</span></dd>
      <dt>Amount</dt><dd>${escapeHtml(amt)}</dd>
      <dt>Escalation reason</dt><dd>${escapeHtml(a.escalate_reason || a.reason || "—")}</dd>
      <dt>Policy</dt><dd>${escapeHtml(a.policy || "—")}</dd>
      <dt>Created</dt><dd>${escapeHtml(fmtTime(a.created_at))}</dd>
    </dl>
    <div class="approval-inspector-actions">
      <button type="button" id="btnInspectorApprove" class="btn btn-approve btn-review">Approve</button>
      <button type="button" id="btnInspectorDeny" class="btn btn-deny btn-review">Deny</button>
    </div>
    <p class="form-hint approval-inspector-hint">Stored on the interceptor by <code>trace_id</code>. After approve, the client retries <code>POST /tool-call</code> with the same <code>X-Trace-ID</code>.</p>
  `;
  traceDetail.textContent = JSON.stringify(a, null, 2);

  document.getElementById("btnInspectorApprove")?.addEventListener("click", (ev) => {
    ev.stopPropagation();
    submitApprovalDecision(trace, "approve");
  });
  document.getElementById("btnInspectorDeny")?.addEventListener("click", (ev) => {
    ev.stopPropagation();
    submitApprovalDecision(trace, "deny");
  });
}

function showApprovalDecidedInspector(a) {
  selectedApprovalTraceId = a.trace_id;
  const status = (a.status || "").toLowerCase();
  const isApproved = status === "approved";
  const trace = a.trace_id || "—";
  const amt =
    a.amount != null ? `$${Number(a.amount).toLocaleString()}` : "—";
  const badge = isApproved
    ? '<span class="badge badge-allow">APPROVED</span>'
    : '<span class="badge badge-deny">DENIED</span>';

  traceSummary.className = "trace-summary";
  traceSummary.innerHTML = `
    <p class="approval-review-label ${escapeHtml(status)}">${isApproved ? "Approved" : "Denied"}</p>
    <p class="approval-decided-msg">${isApproved ? "Human approval recorded. Ticket remains in the Approved queue below." : "Request denied. Listed in the Denied queue below."}</p>
    <dl>
      <dt>Status</dt><dd>${badge}</dd>
      <dt>Trace ID</dt><dd class="cell-mono approval-trace-full">${escapeHtml(trace)}</dd>
      <dt>Agent</dt><dd>${escapeHtml(a.agent_id || "—")}</dd>
      <dt>Tool / action</dt><dd><span class="cell-mono">${escapeHtml(a.tool || "—")}</span> · <span class="cell-mono">${escapeHtml(a.action || "—")}</span></dd>
      <dt>Amount</dt><dd>${escapeHtml(amt)}</dd>
      <dt>Decided by</dt><dd>${escapeHtml(a.decided_by || "dashboard")}</dd>
      <dt>Decided at</dt><dd>${escapeHtml(fmtTime(a.decided_at))}</dd>
      <dt>Escalation reason</dt><dd>${escapeHtml(a.escalate_reason || "—")}</dd>
    </dl>
    <p class="form-hint approval-inspector-hint">Persisted in interceptor memory (<code>approval store</code>) keyed by trace_id until restart.</p>
  `;
  traceDetail.textContent = JSON.stringify(a, null, 2);
}

function mergeApprovalRecord(updated) {
  if (!updated?.trace_id) return;
  const i = approvals.findIndex((a) => a.trace_id === updated.trace_id);
  if (i >= 0) approvals[i] = updated;
  else approvals.unshift(updated);
}

async function submitApprovalDecision(traceId, action) {
  if (!traceId) return;
  const approveBtn = document.getElementById("btnInspectorApprove");
  const denyBtn = document.getElementById("btnInspectorDeny");
  if (approveBtn) approveBtn.disabled = true;
  if (denyBtn) denyBtn.disabled = true;
  try {
    const updated = await postJSON(
      `/api/v1/approvals/${encodeURIComponent(traceId)}/${action}`,
      { decided_by: "dashboard" }
    );
    mergeApprovalRecord(updated);
    selectedApprovalTraceId = traceId;
    renderSection();
    showApprovalDecidedInspector(updated);
  } catch (err) {
    console.error(err);
    traceSummary.className = "trace-summary";
    traceSummary.innerHTML = `<p class="approval-error"><strong>Action failed.</strong> ${escapeHtml(String(err.message || err))}</p><p class="form-hint">Ensure the ticket exists (run escalate scenario first) and the interceptor is up to date.</p>`;
    if (approveBtn) approveBtn.disabled = false;
    if (denyBtn) denyBtn.disabled = false;
  }
}

function approvalTableRows(list, statusBadgeHtml) {
  if (!list.length) {
    return `<tr><td colspan="9" class="cell-muted approval-empty-row">No items</td></tr>`;
  }
  return list
    .map((a) => {
      const trace = a.trace_id || "";
      const sel =
        selectedApprovalTraceId && selectedApprovalTraceId === trace
          ? " selected"
          : "";
      const amt = a.amount != null ? `$${Number(a.amount).toLocaleString()}` : "—";
      const decided = a.decided_at ? fmtTime(a.decided_at) : "—";
      return `<tr class="approval-row${sel}" data-trace-id="${escapeHtml(trace)}" tabindex="0" role="button">
        <td class="cell-muted">${fmtTime(a.created_at)}</td>
        <td class="cell-agent">${escapeHtml(a.agent_id)}</td>
        <td><span class="cell-mono">${escapeHtml(a.tool)}</span></td>
        <td><span class="cell-mono">${escapeHtml(a.action)}</span></td>
        <td>${amt}</td>
        <td class="cell-muted">${escapeHtml(a.escalate_reason || "—")}</td>
        <td class="cell-mono approval-trace-cell" title="${escapeHtml(trace)}">${escapeHtml(trace.slice(0, 16))}…</td>
        <td>${statusBadgeHtml(a)}</td>
        <td class="cell-muted">${escapeHtml(decided)}</td>
      </tr>`;
    })
    .join("");
}

function renderApprovalQueueSection(title, count, hint, tableId, rowsHtml) {
  return `<div class="section-block approval-queue-section">
    <h3 class="section-heading">${escapeHtml(title)} <span class="approval-count">(${count})</span></h3>
    <p class="form-hint approvals-queue-hint">${hint}</p>
    <div class="table-wrap">
      <table class="data approvals-queue-table" id="${tableId}">
        <thead><tr>
          <th>Created</th><th>Agent</th><th>Tool</th><th>Action</th>
          <th>Amount</th><th>Reason</th><th>Trace ID</th><th>Status</th><th>Decided</th>
        </tr></thead>
        <tbody>${rowsHtml}</tbody>
      </table>
    </div>
  </div>`;
}

function renderApprovals() {
  const pending = getPendingApprovals();
  const approved = getApprovedApprovals();
  const denied = getDeniedApprovals();

  if (!approvalsApiOk && !pending.length && !approved.length && !denied.length) {
    return emptyState(
      "Approvals API unavailable",
      "Could not reach GET /api/v1/approvals — rebuild and restart the interceptor.",
      "!"
    );
  }

  const pendingRows = approvalTableRows(
    pending,
    () => '<span class="badge badge-escalate">PENDING</span>'
  );
  const approvedRows = approvalTableRows(
    approved,
    () => '<span class="badge badge-allow">APPROVED</span>'
  );
  const deniedRows = approvalTableRows(
    denied,
    () => '<span class="badge badge-deny">DENIED</span>'
  );

  let html = `<p class="form-hint approvals-page-intro">Click any row to inspect in the Trace inspector. Pending items can be approved or denied there.</p>`;

  html += renderApprovalQueueSection(
    "Pending queue",
    pending.length,
    pending.length
      ? "Awaiting human review — Approve or Deny in the inspector."
      : "No escalations awaiting review.",
    "approvalsTablePending",
    pendingRows
  );

  html += renderApprovalQueueSection(
    "Approved queue",
    approved.length,
    approved.length
      ? "Approved tickets stay here until interceptor restart. Client retries with the same trace_id."
      : "Approved decisions will appear here.",
    "approvalsTableApproved",
    approvedRows
  );

  html += renderApprovalQueueSection(
    "Denied queue",
    denied.length,
    denied.length
      ? "Denied tickets remain for audit; client retry receives approval_denied."
      : "No denied decisions yet.",
    "approvalsTableDenied",
    deniedRows
  );

  return html;
}


function renderReplay() {
  return emptyState(
    "Replay engine not configured",
    "Deterministic replay of agent → policy → enforcement → execution chains will enable compliance forensics and incident investigation.",
    "↺"
  );
}

function renderSettings() {
  return `<div class="section-block">
    <div class="form-field">
      <label for="settingsApi">Control plane API URL</label>
      <input type="url" id="settingsApi" value="${escapeHtml(apiBase())}" />
      <p class="form-hint">Interceptor base URL (PEP). Default: http://localhost:8080</p>
    </div>
    <div class="form-field">
      <label>Refresh interval</label>
      <input type="text" value="5 seconds" disabled />
      <p class="form-hint">Overview and Activity refresh automatically every 5 seconds.</p>
    </div>
    <button type="button" class="btn btn-primary" id="saveSettings">Save configuration</button>
  </div>`;
}

function bindApprovalsTable() {
  const root = document.getElementById("sectionContent");
  if (!root) return;

  const selectRow = (traceId) => {
    const rec = findApproval(traceId);
    if (!rec) return;
    root.querySelectorAll("tr.approval-row").forEach((r) => {
      r.classList.toggle("selected", r.dataset.traceId === traceId);
    });
    showApprovalInspector(rec);
  };

  root.querySelectorAll("tr.approval-row[data-trace-id]").forEach((row) => {
    const open = () => selectRow(row.dataset.traceId);
    row.addEventListener("click", open);
    row.addEventListener("keydown", (ev) => {
      if (ev.key === "Enter" || ev.key === " ") {
        ev.preventDefault();
        open();
      }
    });
  });

  if (selectedApprovalTraceId) {
    const still = findApproval(selectedApprovalTraceId);
    if (still) selectRow(selectedApprovalTraceId);
  }
}

function bindActivityTable() {
  const table = document.getElementById("activityTable");
  if (!table) return;
  table.querySelectorAll("tr[data-idx]").forEach((row) => {
    row.addEventListener("click", () => {
      selectedIdx = Number(row.dataset.idx);
      if (selectedIdx >= 0 && events[selectedIdx]) {
        showTrace(events[selectedIdx]);
        if (kpiDrill) showKpiDrillInspector(kpiDrill);
      }
      const sel = row.classList.contains("selected");
      table.querySelectorAll("tr.selected").forEach((r) => r.classList.remove("selected"));
      if (!sel) row.classList.add("selected");
    });
  });
}

function bindAgentsTable() {
  const table = document.getElementById("agentsTable");
  if (!table) return;
  table.querySelectorAll("tr[data-agent]").forEach((row) => {
    row.addEventListener("click", () => {
      const agentId = row.dataset.agent;
      const agentEvents = events.filter((e) => e.agent_id === agentId);
      traceSummary.className = "trace-summary";
      traceSummary.innerHTML = `<p><strong>${escapeHtml(agentId)}</strong> — ${agentEvents.length} event(s) in window.</p>`;
      traceDetail.textContent = JSON.stringify(agentEvents.slice(0, 15), null, 2);
      sectionContent.innerHTML = `<div class="drill-toolbar">
        <button type="button" class="btn btn-ghost btn-back" id="btnAgentBack">← All agents</button>
        <h3 class="drill-title">${escapeHtml(agentId)}</h3>
      </div>${activityTable(agentEvents)}`;
      document.getElementById("btnAgentBack")?.addEventListener("click", () => renderSection());
      bindActivityTable();
    });
  });
}

function renderSection() {
  const sec = SECTIONS[currentSection];
  pageTitle.textContent = sec.title;
  pageDesc.textContent = sec.desc;
  breadcrumbSection.textContent = sec.title;
  panelTitle.textContent = kpiDrill ? DRILL_TITLES[kpiDrill] : sec.panelTitle;
  panelSubtitle.textContent = kpiDrill
    ? "Drill-down from metrics · click Back to return"
    : sec.panelSubtitle;

  let html = "";
  if (kpiDrill && (currentSection === "overview" || currentSection === "activity" || currentSection === "decisions")) {
    html = renderKpiDrill();
  } else {
    switch (currentSection) {
      case "overview":
        html = activityTable();
        break;
      case "activity":
      case "traces":
        html = activityTable();
        break;
      case "decisions":
        html = renderDecisions();
        break;
      case "agents":
        html = renderAgents();
        break;
      case "policies":
        html = renderPolicies();
        break;
      case "approvals":
        html = renderApprovals();
        break;
      case "replay":
        html = renderReplay();
        break;
      case "settings":
        html = renderSettings();
        break;
      default:
        html = activityTable();
    }
  }

  sectionContent.innerHTML = html;

  document.getElementById("btnDrillBack")?.addEventListener("click", clearKpiDrill);
  bindActivityTable();
  bindAgentsTable();
  bindApprovalsTable();
  bindPolicyCards();
  bindPolicyRules();

  if (currentSection === "approvals") {
    const tracePanel = document.getElementById("tracePanel");
    if (tracePanel) {
      tracePanel.querySelector(".panel-subtitle").textContent =
        "Review selection · Approve or deny";
    }
    const sel = selectedApprovalTraceId
      ? findApproval(selectedApprovalTraceId)
      : null;
    if (sel) showApprovalInspector(sel);
    else approvalsInspectorPlaceholder();
  } else {
    const tracePanel = document.getElementById("tracePanel");
    if (tracePanel) {
      tracePanel.querySelector(".panel-subtitle").textContent =
        "Governance context for selected event";
    }
  }

  if (currentSection === "policies") {
    const p = policyCatalog.find((x) => policyCardKey(x) === selectedPolicyKey);
    if (p) {
      const rule = selectedPolicyRuleId
        ? (p.rules || []).find((r) => r.id === selectedPolicyRuleId)
        : null;
      showPolicyInspector(p, rule);
    } else policiesInspectorPlaceholder();
  }

  const saveBtn = document.getElementById("saveSettings");
  if (saveBtn) {
    saveBtn.addEventListener("click", () => {
      apiBaseEl.value = document.getElementById("settingsApi").value;
      sessionStorage.setItem("acp_dashboard_api", apiBaseEl.value);
      loadData();
    });
  }
}

function showTrace(e) {
  if (!e) {
    traceSummary.className = "trace-summary empty-inspector";
    traceSummary.textContent =
      "Select a row to inspect trace ID, policy, and enforcement payload.";
    traceDetail.textContent = "—";
    return;
  }
  traceSummary.className = "trace-summary";
  traceSummary.innerHTML = `
    <dl>
      <dt>Trace ID</dt><dd>${escapeHtml(e.trace_id || "—")}</dd>
      <dt>Agent</dt><dd>${escapeHtml(e.agent_id)}</dd>
      <dt>Decision</dt><dd>${escapeHtml((e.decision || "").toUpperCase())}</dd>
      <dt>Policy</dt><dd>${escapeHtml(e.policy || "—")}</dd>
      <dt>Risk score</dt><dd>${e.risk_score != null ? Number(e.risk_score).toFixed(2) : "—"}</dd>
      <dt>Reason</dt><dd>${escapeHtml(e.reason || "—")}</dd>
    </dl>`;
  traceDetail.textContent = JSON.stringify(e, null, 2);
}

function escapeHtml(s) {
  return String(s)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
}

function navigate(section) {
  currentSection = section;
  kpiDrill = null;
  selectedIdx = null;
  selectedApprovalTraceId = null;
  selectedPolicyKey = null;
  selectedPolicyRuleId = null;
  document.querySelectorAll(".nav-item").forEach((a) => {
    a.classList.toggle("active", a.dataset.section === section);
  });
  if (
    section === "activity" ||
    section === "overview" ||
    section === "policies" ||
    section === "approvals"
  ) {
    if (!pollTimer) pollTimer = setInterval(loadData, 5000);
  } else if (pollTimer) {
    clearInterval(pollTimer);
    pollTimer = null;
  }
  renderMetricsBar();
  renderSection();
  if (section === "approvals") {
    approvalsInspectorPlaceholder();
  } else if (section !== "policies") {
    showTrace(null);
  }
}

document.querySelectorAll(".nav-item").forEach((a) => {
  a.addEventListener("click", (ev) => {
    ev.preventDefault();
    navigate(a.dataset.section);
  });
});

btnRefresh.addEventListener("click", loadData);

const savedApi = sessionStorage.getItem("acp_dashboard_api");
if (savedApi) apiBaseEl.value = savedApi;
else apiBaseEl.value = defaultApiBase();

loadData();
pollTimer = setInterval(loadData, 5000);
