/**
 * ZeqTrail — consent-based, encrypted, hash-linked browser audit trail.
 *
 * Privacy model (founder-chosen): NOTHING is captured unless the user opts a
 * specific host in. Captured entries hold only what the tabs API already
 * exposes (host, title, url, time) and are stored as ONE AES-256-GCM blob in
 * chrome.storage.local, encrypted under a key derived from the signed-in
 * identity's session token. Entries are hash-linked (each carries the prior
 * entry's hash) so the local trail is tamper-evident — the same discipline as
 * the machine's entangled state. The trail head can be ANCHORED to the user's
 * machine (POST /api/chain/:slug/event) to bind browser activity to the
 * on-machine entangled state.
 *
 * Works in the service worker (importScripts) and the panel (script tag) — it
 * attaches to `self`. WebCrypto only; no plaintext at rest.
 *
 * @version 1.287.6
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */
(function (global) {
  "use strict";

  const LOG_KEY = "zeq_trail_log";       // { iv, data } AES-GCM blob
  const CONSENT_KEY = "zeq_trail_consent"; // { enabled, hosts: { host: true } }
  const SESSION_KEY = "zeq_auth_session";
  const MAX_ENTRIES = 500;
  const TAU = 0.777000777;

  function _u8FromB64(b64) { const s = atob(b64); const u = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i); return u; }
  function _b64FromU8(u) { let s = ""; for (let i = 0; i < u.length; i++) s += String.fromCharCode(u[i]); return btoa(s); }
  function _enc(str) { return new TextEncoder().encode(str); }
  function _dec(buf) { return new TextDecoder().decode(buf); }

  async function _sha256Hex(str) {
    const d = new Uint8Array(await crypto.subtle.digest("SHA-256", _enc(str)));
    let h = ""; for (let i = 0; i < d.length; i++) h += d[i].toString(16).padStart(2, "0");
    return h;
  }

  function _storageGet(keys) { return new Promise((res) => chrome.storage.local.get(keys, (r) => res(r || {}))); }
  function _storageSet(obj) { return new Promise((res) => chrome.storage.local.set(obj, () => res())); }

  async function _token() {
    const r = await _storageGet(SESSION_KEY);
    return r[SESSION_KEY] && r[SESSION_KEY].token ? r[SESSION_KEY].token : null;
  }
  async function _zid() {
    const r = await _storageGet(SESSION_KEY);
    return r[SESSION_KEY] && r[SESSION_KEY].user ? r[SESSION_KEY].user.zid : null;
  }

  // AES-GCM key derived from the session token (per-identity). No token → no key.
  async function _key() {
    const tok = await _token();
    if (!tok) return null;
    const material = new Uint8Array(await crypto.subtle.digest("SHA-256", _enc("zeq-trail-v1|" + tok)));
    return crypto.subtle.importKey("raw", material, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
  }

  async function _readEntries() {
    const key = await _key();
    if (!key) return [];
    const r = await _storageGet(LOG_KEY);
    const blob = r[LOG_KEY];
    if (!blob || !blob.iv || !blob.data) return [];
    try {
      const iv = _u8FromB64(blob.iv);
      const data = _u8FromB64(blob.data);
      const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data);
      const arr = JSON.parse(_dec(pt));
      return Array.isArray(arr) ? arr : [];
    } catch (_) { return []; }
  }

  async function _writeEntries(entries) {
    const key = await _key();
    if (!key) return false;
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, _enc(JSON.stringify(entries)));
    await _storageSet({ [LOG_KEY]: { iv: _b64FromU8(iv), data: _b64FromU8(new Uint8Array(ct)) } });
    return true;
  }

  // ── Consent ────────────────────────────────────────────────────────────
  async function getConsent() {
    const r = await _storageGet(CONSENT_KEY);
    const c = r[CONSENT_KEY] || {};
    return { enabled: !!c.enabled, mode: c.mode === 'all' ? 'all' : 'optin', hosts: c.hosts || {} };
  }
  async function setEnabled(on) { const c = await getConsent(); c.enabled = !!on; await _storageSet({ [CONSENT_KEY]: c }); return c; }
  async function setMode(mode) { const c = await getConsent(); c.mode = mode === 'all' ? 'all' : 'optin'; await _storageSet({ [CONSENT_KEY]: c }); return c; }
  async function allowHost(host) { const c = await getConsent(); c.hosts[host] = true; await _storageSet({ [CONSENT_KEY]: c }); return c; }
  async function removeHost(host) { const c = await getConsent(); delete c.hosts[host]; await _storageSet({ [CONSENT_KEY]: c }); return c; }
  // Track-all (consent-gated): enabled + mode 'all' → any host; else opt-in list.
  async function isAllowed(host) { const c = await getConsent(); if (!c.enabled || !host) return false; return c.mode === 'all' || !!c.hosts[host]; }

  // ── Capture (consent-gated, hash-linked) ─────────────────────────────────
  async function capture(ev) {
    const host = (ev && ev.host) || "";
    if (!host || !(await isAllowed(host))) return false;
    const key = await _key();
    if (!key) return false; // not signed in → nothing captured
    const entries = await _readEntries();
    const prevHash = entries.length ? entries[entries.length - 1].hash : "GENESIS";
    const now = Date.now() / 1000;
    const base = {
      host: host,
      kind: ev.kind || "navigation",
      title: String(ev.title || "").slice(0, 200),
      url: String(ev.url || "").slice(0, 500),
      zeqond: Math.floor(now / TAU),
      phase: Number(((now % TAU) / TAU).toFixed(6)),
      ts: Date.now(),
      prevHash: prevHash,
    };
    base.hash = await _sha256Hex(JSON.stringify(base));
    entries.push(base);
    while (entries.length > MAX_ENTRIES) entries.shift();
    await _writeEntries(entries);
    return true;
  }

  async function read(limit) { const e = await _readEntries(); return limit ? e.slice(-limit).reverse() : e.slice().reverse(); }
  async function clear() { await _storageSet({ [LOG_KEY]: null }); }
  async function head() { const e = await _readEntries(); return e.length ? e[e.length - 1].hash : null; }
  async function count() { return (await _readEntries()).length; }

  // ── Verify the local hash-chain is intact (tamper-evident) ───────────────
  async function verify() {
    const e = await _readEntries();
    let prev = "GENESIS";
    for (let i = 0; i < e.length; i++) {
      const { hash, ...rest } = e[i];
      if (rest.prevHash !== prev) return { ok: false, brokenAt: i, reason: "prevHash mismatch" };
      const recomputed = await _sha256Hex(JSON.stringify(rest));
      if (recomputed !== hash) return { ok: false, brokenAt: i, reason: "hash mismatch" };
      prev = hash;
    }
    return { ok: true, length: e.length };
  }

  global.ZeqTrail = {
    getConsent, setEnabled, setMode, allowHost, removeHost, isAllowed,
    capture, read, clear, head, count, verify,
    _zid,
  };
})(typeof self !== "undefined" ? self : this);
