/**
 * Zeq Chrome Extension — Background Service Worker
 * HulyaPulse at 1.287 Hz, CKO routing, auth state, HITE session management.
 *
 * @version 1.287.5
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */

// Shared config + charter-exact constants + live endpoints.
importScripts('../lib/zeq-config.js');
// K3 — consent-based, encrypted, hash-linked browser audit trail.
importScripts('../lib/zeq-trail.js');
// SC.1 — offline compute engine (bundled SDK solver, pure math). Powers the
// state-channel local tier: compute works with no internet / no credit.
try { importScripts('../lib/zeq-local-engine.js'); } catch (e) { console.warn('[Zeq] local engine unavailable', e); }

// ── HulyaPulse Core (charter-exact, from ZeqConfig) ──────────────────────
const HZ     = ZeqConfig.HZ;          // 1.287 Hz system clock
const PERIOD = ZeqConfig.TAU_S;       // 0.777000777 s (1 Zeqond = 777,000,777 ns)
const ALPHA  = ZeqConfig.ALPHA_KO42;  // 1e-3 exactly (KO42 modulation, NOT econ 1.29e-3)
// Operator catalogue size is never a precise number on a surface (P21.R13).
const OPERATORS_SURFACE = ZeqConfig.OPERATORS_HERO; // "42+ Kinematic operators"

// ── State ────────────────────────────────────────────────────────────────
let pulseCount = 0;
let lastCKO    = null;
let syncWs     = null;
let syncWsReady = false;
let syncReconnectAttempts = 0;
let syncReconnectTimer    = null;
let meshNodeCount = 0;

// ── HulyaPulse Tick ─────────────────────────────────────────────────────
function tick() {
  const now     = Date.now() / 1000;
  const phase   = (now % PERIOD) / PERIOD;
  const ko42    = ALPHA * Math.sin(2 * Math.PI * HZ * now);
  const zeqond  = now / PERIOD;
  pulseCount++;

  return { phase, ko42, zeqond, pulseCount, timestamp: now, hz: HZ };
}

// ── Extension Install ───────────────────────────────────────────────────
chrome.runtime.onInstalled.addListener((details) => {
  console.log(`[Zeq] Extension ${details.reason} — v1.287.6 — ${OPERATORS_SURFACE} — 1.287 Hz HulyaPulse`);

  // Create context menus
  chrome.contextMenus.create({
    id:       'zeq-verify',
    title:    'Verify with ZeqVM',
    contexts: ['selection'],
  });

  chrome.contextMenus.create({
    id:       'zeq-search',
    title:    'Search with ZeqVM',
    contexts: ['selection'],
  });

  chrome.contextMenus.create({
    id:       'zeq-cko',
    title:    'View CKO Waveform',
    contexts: ['selection'],
  });

  // Set default storage
  chrome.storage.local.get('zeq_platforms', (result) => {
    if (!result.zeq_platforms) {
      chrome.storage.local.set({
        zeq_platforms: {
          chatgpt: true, claude: true, deepseek: true, grok: true,
          groq: true, perplexity: true, poe: true, gemini: true,
        }
      });
    }
  });
});

// ── Context Menu Handlers ───────────────────────────────────────────────
chrome.contextMenus.onClicked.addListener((info, tab) => {
  const text = info.selectionText;
  if (!text) return;

  if (info.menuItemId === 'zeq-verify') {
    // Send selected text to side panel for verification
    chrome.sidePanel.open({ tabId: tab.id });
    setTimeout(() => {
      chrome.runtime.sendMessage({
        type:    'verify_selection',
        text:    text,
        tabId:   tab.id,
      });
    }, 500);
  }

  if (info.menuItemId === 'zeq-search') {
    chrome.tabs.create({
      url: `https://duckduckgo.com/?q=${encodeURIComponent(text + ' physics zeq os')}`,
    });
  }

  if (info.menuItemId === 'zeq-cko') {
    // Open side panel to CKO tab with analysis of selection
    chrome.sidePanel.open({ tabId: tab.id });
    setTimeout(() => {
      chrome.runtime.sendMessage({
        type:    'analyze_cko',
        text:    text,
        tabId:   tab.id,
      });
    }, 500);
  }
});

// ── Side Panel on Action Click ──────────────────────────────────────────
chrome.action.onClicked.addListener((tab) => {
  chrome.sidePanel.open({ tabId: tab.id });
});

// ── K8 — live active-tab context (the extension knows where you are) ──────
function _ctxFrom(tab) {
  if (!tab || !tab.url) return null;
  let host = '';
  try { host = new URL(tab.url).hostname.replace(/^www\./, ''); } catch (_) {}
  return { url: tab.url, host, title: tab.title || '', tabId: tab.id };
}
function _broadcastActiveTab(tab) {
  const ctx = _ctxFrom(tab);
  if (!ctx) return;
  chrome.runtime.sendMessage({ type: 'zeq_active_tab', ctx }).catch(() => {});
}
chrome.tabs.onActivated.addListener(({ tabId }) => {
  chrome.tabs.get(tabId, (tab) => { if (!chrome.runtime.lastError) _broadcastActiveTab(tab); });
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (tab && tab.active && (changeInfo.status === 'complete' || changeInfo.url || changeInfo.title)) _broadcastActiveTab(tab);
});

// ── K3/KB.1 — consent-based browser audit trail capture ──────────────────
// Capture is gated by ZeqTrail.isAllowed(host): opt-in mode → only opted-in
// hosts; track-all mode → every host (still requires the master switch ON and
// a signed-in identity). Nothing is captured otherwise.
function _trailHost(url) {
  try { if (/^chrome(-extension)?:|^about:|^edge:|^devtools:/.test(url)) return null; return new URL(url).hostname.replace(/^www\./, ''); } catch (_) { return null; }
}

// Every main-frame navigation (track-all uses this; opt-in mode filters by host).
if (chrome.webNavigation && chrome.webNavigation.onCompleted) {
  chrome.webNavigation.onCompleted.addListener((d) => {
    if (d.frameId !== 0 || !d.url) return;
    const host = _trailHost(d.url);
    if (!host) return;
    try {
      chrome.tabs.get(d.tabId, (tab) => {
        const title = (tab && tab.title) || '';
        ZeqTrail.capture({ host, title, url: d.url, kind: 'navigation' }).catch(() => {});
      });
    } catch (_) {}
  });
}

// Downloads (consent-gated the same way, keyed by the download's host).
if (chrome.downloads && chrome.downloads.onCreated) {
  chrome.downloads.onCreated.addListener((item) => {
    const host = _trailHost(item.url || item.finalUrl || '');
    if (!host) return;
    try { ZeqTrail.capture({ host, title: 'download: ' + (item.filename || '').split('/').pop(), url: item.url || item.finalUrl || '', kind: 'download' }).catch(() => {}); } catch (_) {}
  });
}

// ── Message Router ──────────────────────────────────────────────────────
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // SEC.1 — defense-in-depth: only accept messages from this extension's own
  // contexts (content scripts + panel). Internal senders always carry our id;
  // there is no externally_connectable, so this never rejects a legit message.
  if (sender && sender.id && sender.id !== chrome.runtime.id) return;
  switch (message.type) {
    case 'getState':
      sendResponse({
        ...tick(),
        meshNodes:  meshNodeCount,
        syncStatus: syncWsReady ? 'connected' : 'disconnected',
      });
      break;

    case 'getOperators':
      sendResponse({ surface: OPERATORS_SURFACE, catalogue: ZeqConfig.OPERATORS_CATALOGUE });
      break;

    case 'getSession':
      // Auth bridge — hand the current session to fork-origin pages so they
      // sign in with the extension's identity (no second login).
      chrome.storage.local.get('zeq_auth_session', (r) => {
        const s = r.zeq_auth_session;
        if (s && s.token) {
          const zid = s.user && s.user.zid;
          sendResponse({ token: s.token, zid: zid || null, zidEmail: zid ? (String(zid).toLowerCase() + '@zeq.dev') : null });
        } else {
          sendResponse({});
        }
      });
      return true; // async

    case 'getCKO':
      sendResponse(lastCKO);
      break;

    case 'zeq_cko_update':
      lastCKO = message.payload;
      broadcastToAllTabs({ type: 'zeq_cko_update', payload: message.payload });
      break;

    case 'zeq_auth_event':
      broadcastToAllTabs(message);
      break;

    case 'verifyText':
      handleVerifyText(message, sendResponse);
      return true; // async — real SDK grounding for the badge

    case 'oracleDeploy':
      handleOracleDeploy(message, sendResponse);
      return true; // async — deploy a page-watching oracle contract on the machine

    case 'oracleFire':
      handleOracleFire(message, sendResponse);
      return true; // async — feed a reading → transition → Observer

    case 'securityScan':
      handleSecurityScan(message, sendResponse);
      return true; // async — score page signals + ZSP-seal the verdict

    case 'daemonList':
      handleDaemonList(message, sendResponse);
      return true; // async — list this machine's audit-source agents

    case 'daemonSpawn':
      handleDaemonSpawn(message, sendResponse);
      return true; // async — spawn a server-side source agent

    case 'daemonAction':
      handleDaemonAction(message, sendResponse);
      return true; // async — sweep | kill an agent

    case 'sslList':
      handleSslList(message, sendResponse);
      return true; // async — list this machine's SSL credentials
    case 'sslIssue':
      handleSslIssue(message, sendResponse);
      return true; // async — issue a new SSL credential
    case 'sslRevoke':
      handleSslRevoke(message, sendResponse);
      return true; // async — revoke a credential
    case 'avStatus':
      handleAvStatus(message, sendResponse);
      return true; // async — AV threats + devices for this machine

    case 'zscList':
      handleZscList(message, sendResponse);
      return true; // async — list ZSC secrets (zsm_-gated)
    case 'zscPut':
      handleZscPut(message, sendResponse);
      return true; // async — set a ZSC secret
    case 'edgeList':
      handleEdgeList(message, sendResponse);
      return true; // async — list Edge sites
    case 'edgeAdd':
      handleEdgeAdd(message, sendResponse);
      return true; // async — connect a site to Zeq Edge

    case 'physics':
      handlePhysics(message, sendResponse);
      return true; // async — KP.1 window.zeq engine calls (compute/solve/…)

    case 'vaultSync':
      handleVaultSync(message, sendResponse);
      return true; // async — K1b zero-knowledge vault blob sync

    case 'pendingState':
      _getPending().then((q) => sendResponse({ success: true, count: q.length, cap: MAX_UNSETTLED, entries: q.slice(-20).map((e) => ({ local_hash: e.local_hash, operators: e.operators, reason: e.reason, at: e.at })) }));
      return true; // async — SC.2 pending settlement backlog
    case 'settleNow':
      _settlePending().then(() => _getPending()).then((q) => sendResponse({ success: true, remaining: q.length }));
      return true; // async — force a settlement pass

    case 'processQuery':
      handleQueryProcess(message, sendResponse);
      return true; // async

    case 'processWizard':
      handleWizardProcess(message, sendResponse);
      return true;

    case 'fetchOperatorRegistry':
      handleFetchOperators(sendResponse);
      return true;

    case 'searchOperators':
      handleSearchOperators(message.query, sendResponse);
      return true;

    case 'generate_skill':
      handleSkillGeneration(message.skillId, sendResponse);
      return true;

    case 'getServiceStatus':
      handleServiceStatus(sendResponse);
      return true;

    case 'updateSettings':
      chrome.storage.local.set(message.settings || {});
      sendResponse({ success: true });
      break;

    default:
      break;
  }
});

// ── Verify text via the LIVE SDK (real grounding for the badge) ──────────
// Runs the detected physics text through /api/zeq/wizard/compute and returns
// the grounded envelope (domain, operators, precision bound, phase, R(t)).
// Honest: this GROUNDS the topic in the framework — it does not assert the
// AI's answer is correct — so the badge says "grounded", never "verified".
async function handleVerifyText(message, sendResponse) {
  try {
    const apiUrl = await getApiUrl();
    const headers = { 'Content-Type': 'application/json' };
    const stored = await chrome.storage.local.get('zeq_auth_session');
    if (stored.zeq_auth_session?.token) headers['Authorization'] = `Bearer ${stored.zeq_auth_session.token}`;
    const resp = await fetch(`${apiUrl}${ZeqConfig.endpoints.compute}`, {
      method: 'POST', headers,
      body: JSON.stringify({ query: String(message.text || '').slice(0, 500) }),
    });
    const data = await resp.json().catch(() => ({}));
    if (!resp.ok || !data.ok) { sendResponse({ success: false, error: data.error || ('compute ' + resp.status) }); return; }
    sendResponse({
      success: true,
      domain: data.domain,
      operators: Array.isArray(data.operators) ? data.operators : [],
      precision: data.precision,
      phase: data.phase,
      R_t: data.R_t,
      envelopeId: data.envelope_id,
      equation: String(data.equation || '').slice(0, 600),
    });
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

// ── Oracle injection — deploy a page-watching contract + feed readings ───
async function _authCtx() {
  const apiUrl = await getApiUrl();
  const stored = await chrome.storage.local.get('zeq_auth_session');
  const s = stored.zeq_auth_session;
  const token = s && s.token; const zid = s && s.user && s.user.zid;
  return { apiUrl, token, zid, slug: zid ? String(zid).toLowerCase() : null,
    headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: 'Bearer ' + token } : {}) } };
}
async function handleOracleDeploy(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token || !c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const o = message.oracle || {};
    const label = String(o.label || message.name || 'page oracle').slice(0, 80);
    const name = (label.toLowerCase().replace(/[^a-z0-9_]+/g, '_')).slice(0, 40) || 'page_oracle';
    const host = String(o.host || '').toLowerCase();
    const selector = String(o.selector || '');
    // Slug-scoped uniqueness: same target (host + selector + label) → reuse,
    // never spawn a duplicate. Server enforces this idempotently on the key.
    const dedupe_key = `${host}|${selector}|${label.toLowerCase()}`.slice(0, 256);
    const oracle = {
      dedupe_key, label, target_host: host,
      target_url: String(o.url || '').slice(0, 1024),
      selector: selector.slice(0, 512),
      reading_type: o.reading_type || 'number',
      unit: String(o.unit || '').slice(0, 16),
      interval_zeqonds: parseInt(o.interval_zeqonds, 10) || 40,
    };
    const definition = {
      name, version: '1.0',
      states: { live: { initial: true } },
      transitions: [{ from: 'live', to: 'live', operator: 'KO42', auto: false, proof_required: false, post_actions: [{ type: 'emit', event: 'oracle_datum' }] }],
      oracle,
    };
    const r = await fetch(`${c.apiUrl}/chain/${encodeURIComponent(c.slug)}/contracts`, { method: 'POST', headers: c.headers, body: JSON.stringify({ definition }) });
    const j = await r.json().catch(() => ({}));
    if (!r.ok) { sendResponse({ success: false, error: j.error_description || j.error || ('deploy ' + r.status) }); return; }
    sendResponse({ success: true, reused: !!j.reused, slug: c.slug, contractId: j.contract?.id || j.id || j.contractId, origin: (c.apiUrl || '').replace(/\/api$/, '') });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleOracleFire(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token || !c.slug || !message.contractId) { sendResponse({ success: false, error: 'not ready' }); return; }
    const r = await fetch(`${c.apiUrl}/chain/${encodeURIComponent(c.slug)}/contracts/${encodeURIComponent(message.contractId)}/transition`, {
      method: 'POST', headers: c.headers,
      body: JSON.stringify({ to: 'live', input: { label: message.label, value: message.value, url: message.url, host: message.host, at_zeqond: Math.floor((Date.now() / 1000) / ZeqConfig.TAU_S) } }),
    });
    const j = await r.json().catch(() => ({}));
    if (!r.ok) { sendResponse({ success: false, error: j.error_description || j.error || ('fire ' + r.status) }); return; }
    sendResponse({ success: true });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
// KC.E.3 — security check: forward the page's collected signals to the ZSP
// scorer, which returns a verdict + a ZSP seal (entropy/HF/temporal HMAC).
async function handleSecurityScan(message, sendResponse) {
  try {
    const c = await _authCtx(); // optional auth — works signed-in or not
    const apiUrl = c.apiUrl || await getApiUrl();
    const headers = c.headers || { 'Content-Type': 'application/json' };
    const r = await fetch(`${apiUrl}/zsp/scan`, { method: 'POST', headers, body: JSON.stringify(message.signals || {}) });
    const j = await r.json().catch(() => ({}));
    if (!r.ok || !j.ok) { sendResponse({ success: false, error: j.error || ('scan ' + r.status) }); return; }
    sendResponse({ success: true, data: j });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
// ── Audit Daemon (KC.E.4) — server-side source agents on the user's machine ──
async function handleDaemonList(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const r = await fetch(`${c.apiUrl}/zeq/audit-source/list?slug=${encodeURIComponent(c.slug)}`, { headers: c.headers });
    const j = await r.json().catch(() => ({}));
    sendResponse({ success: r.ok, data: j, slug: c.slug, origin: (c.apiUrl || '').replace(/\/api$/, '') });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleDaemonSpawn(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token || !c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const body = { slug: c.slug, source_type: message.source_type, source_config: message.source_config || {}, display_name: message.display_name || undefined };
    const r = await fetch(`${c.apiUrl}/zeq/audit-source/spawn`, { method: 'POST', headers: c.headers, body: JSON.stringify(body) });
    const j = await r.json().catch(() => ({}));
    if (!r.ok) { sendResponse({ success: false, error: j.error || ('spawn ' + r.status) }); return; }
    sendResponse({ success: true, data: j, slug: c.slug, origin: (c.apiUrl || '').replace(/\/api$/, '') });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleDaemonAction(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const act = message.action === 'kill' ? 'kill' : 'sweep';
    const r = await fetch(`${c.apiUrl}/zeq/audit-source/${encodeURIComponent(message.agentId)}/${act}`, { method: 'POST', headers: c.headers });
    const j = await r.json().catch(() => ({}));
    sendResponse({ success: r.ok, data: j });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
// ── Security (KC.D) — native SSL credential manager + AV status ──────────────
async function handleSslList(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const r = await fetch(`${c.apiUrl}/ssl/credentials?machine=${encodeURIComponent(c.slug)}`, { headers: c.headers });
    const j = await r.json().catch(() => ({}));
    sendResponse({ success: r.ok, data: j, slug: c.slug });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleSslIssue(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token || !c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const r = await fetch(`${c.apiUrl}/ssl/credentials/issue`, { method: 'POST', headers: c.headers, body: JSON.stringify({ machine: c.slug, mode: message.mode === 'B' ? 'B' : 'A' }) });
    const j = await r.json().catch(() => ({}));
    if (!r.ok) { sendResponse({ success: false, error: j.error || ('issue ' + r.status) }); return; }
    sendResponse({ success: true, data: j });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleSslRevoke(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token) { sendResponse({ success: false, error: 'not signed in' }); return; }
    const r = await fetch(`${c.apiUrl}/ssl/credentials/revoke`, { method: 'POST', headers: c.headers, body: JSON.stringify({ credential_id: message.credentialId }) });
    const j = await r.json().catch(() => ({}));
    sendResponse({ success: r.ok, data: j });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleAvStatus(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.slug) { sendResponse({ success: false, error: 'not signed in' }); return; }
    let threats = [], threatCount = 0, devices = [];
    try { const tj = await fetch(`${c.apiUrl}/av/threats?slug=${encodeURIComponent(c.slug)}`, { headers: c.headers }).then((r) => r.json()); threats = tj.threats || []; threatCount = tj.count || threats.length; } catch (_) {}
    try { const dj = await fetch(`${c.apiUrl}/av/devices`, { headers: c.headers }).then((r) => r.ok ? r.json() : ({})); devices = dj.devices || dj.rows || []; } catch (_) {}
    sendResponse({ success: true, threats, threatCount, devices, slug: c.slug, origin: (c.apiUrl || '').replace(/\/api$/, '') });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}

// ── Machine-key path (KC.D follow-up) — obtain a zsm_ key for zsm_-gated
// surfaces (ZSC vault, Edge). session-key (GET) returns only the prefix; this
// mints a fresh admin key for the caller's machine and returns the raw key
// once. Cached in chrome.storage; re-minted transparently on a 401.
async function getSmKey(force) {
  if (!force) { const cached = await chrome.storage.local.get('zeq_sm_key'); if (cached.zeq_sm_key) return cached.zeq_sm_key; }
  const a = await _authCtx();
  if (!a.token) return null;
  const r = await fetch(`${a.apiUrl}/zeq-auth/session-key/mint`, { method: 'POST', headers: a.headers });
  const j = await r.json().catch(() => ({}));
  if (j && j.ok && j.api_key) { await chrome.storage.local.set({ zeq_sm_key: j.api_key }); return j.api_key; }
  return null;
}
// Fetch a zsm_-gated endpoint, minting/re-minting the key on 401.
async function smFetch(path, opts) {
  const a = await _authCtx();
  if (!a.token) return { httpStatus: 401, json: { error: 'not signed in' } };
  let key = await getSmKey(false);
  const doFetch = async (k) => {
    const headers = Object.assign({ 'Content-Type': 'application/json', 'X-Zeq-SM-Key': k }, (opts && opts.headers) || {});
    const r = await fetch(`${a.apiUrl}${path}`, Object.assign({}, opts, { headers }));
    return { httpStatus: r.status, json: await r.json().catch(() => ({})) };
  };
  if (!key) return { httpStatus: 401, json: { error: 'no_machine_key' } };
  let res = await doFetch(key);
  if (res.httpStatus === 401) { key = await getSmKey(true); if (key) res = await doFetch(key); }
  return res;
}
async function handleZscList(message, sendResponse) {
  try {
    const app = encodeURIComponent(message.app || 'default');
    const env = encodeURIComponent(message.env || 'production');
    const res = await smFetch(`/zsc/public/list?app=${app}&env=${env}`, { method: 'GET' });
    sendResponse({ success: res.httpStatus < 400 && res.json.ok !== false, data: res.json, status: res.httpStatus });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleZscPut(message, sendResponse) {
  try {
    const body = { app: message.app || 'default', env: message.env || 'production', name: message.name, value: message.value };
    const res = await smFetch('/zsc/public/put', { method: 'POST', body: JSON.stringify(body) });
    sendResponse({ success: res.httpStatus < 400 && res.json.ok !== false, data: res.json, status: res.httpStatus });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleEdgeList(message, sendResponse) {
  try {
    const res = await smFetch('/cf/sites', { method: 'GET' });
    sendResponse({ success: res.httpStatus < 400 && res.json.ok !== false, data: res.json, status: res.httpStatus });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
async function handleEdgeAdd(message, sendResponse) {
  try {
    const body = { hostname: message.hostname || message.host, origin: message.origin || undefined };
    const res = await smFetch('/cf/sites', { method: 'POST', body: JSON.stringify(body) });
    sendResponse({ success: res.httpStatus < 400 && res.json.ok !== false, data: res.json, status: res.httpStatus });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}

// ── KP.1 — physics engine bridge for window.zeq ──────────────────────────────
// Maps a window.zeq.<method>(args) call to a real /api/zeq|/api/framework route.
// compute/solve/multibody need the session key (authenticateZeqAuth/Key → Bearer
// works); verify/pulse are public. modulate is a local KO42 calc (no network).
// The page never sees the token — calls ride _authCtx headers here in the SW.
const PHYSICS_ROUTES = {
  compute:   { method: 'POST', path: '/zeq/compute',          auth: true },
  solve:     { method: 'POST', path: '/solve',                 auth: true },
  multibody: { method: 'POST', path: '/multibody',             auth: true },
  lattice:   { method: 'POST', path: '/zeq/lattice',           auth: true },
  shift:     { method: 'POST', path: '/zeq/shift',             auth: true },
  prove:     { method: 'POST', path: '/zeq/prove',             auth: false },
  verify:    { method: 'POST', path: '/zeq/verify',            auth: true },
  pulse:     { method: 'GET',  path: '/zeq/pulse',             auth: false },
  operators: { method: 'GET',  path: '/operators',             auth: false },
};
// SEC.1 — throttle page-originated METERED engine calls so a granted site can't
// drain the user's ZEQ. Free methods (pulse/operators/verify/prove/modulate)
// are unlimited; metered ones get a rolling per-minute cap from page origin.
const _PHYS_METERED = new Set(['compute', 'solve', 'multibody', 'lattice', 'shift']);
const _physPageHits = [];
function _physPageRateLimited() {
  const now = Date.now(), windowMs = 60_000, cap = 30;
  while (_physPageHits.length && now - _physPageHits[0] > windowMs) _physPageHits.shift();
  if (_physPageHits.length >= cap) return true;
  _physPageHits.push(now);
  return false;
}
async function handlePhysics(message, sendResponse) {
  try {
    const method = String(message.method || '');
    if (message.origin === 'page' && _PHYS_METERED.has(method) && _physPageRateLimited()) {
      sendResponse({ success: false, status: 429, error: 'rate limit: too many metered engine calls from this page (30/min). Use the panel for bulk compute.' });
      return;
    }
    // Local KO42 modulation — R(t) = value·[1 + α·sin(2π f t)] — no network.
    if (method === 'modulate') {
      const v = Number((message.args && message.args.value));
      if (!Number.isFinite(v)) { sendResponse({ success: false, error: 'modulate(value): number required' }); return; }
      const tSec = Date.now() / 1000;
      const R = v * (1 + ALPHA * Math.sin(2 * Math.PI * HZ * tSec));
      sendResponse({ success: true, data: { value: R, base: v, alpha: ALPHA, hz: HZ, equation: 'R(t)=S·[1+α·sin(2π·1.287·t)]' } });
      return;
    }
    const route = PHYSICS_ROUTES[method];
    if (!route) { sendResponse({ success: false, error: 'unknown method: ' + method }); return; }
    const c = await _authCtx();
    const apiUrl = c.apiUrl || await getApiUrl();
    if (route.auth && !c.token) { sendResponse({ success: false, error: 'sign in to use ' + method + ' (metered engine call)' }); return; }
    const headers = route.auth ? c.headers : { 'Content-Type': 'application/json' };
    let url = apiUrl + route.path;
    const opts = { method: route.method, headers };
    const a = message.args || {};
    if (route.method === 'GET') {
      const qs = Object.entries(a).filter(([, v]) => v != null).map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(String(v))).join('&');
      if (qs) url += (url.includes('?') ? '&' : '?') + qs;
    } else {
      opts.body = JSON.stringify(a);
    }
    let r, j;
    try {
      r = await fetch(url, opts);
      j = await r.json().catch(() => ({}));
    } catch (netErr) {
      // OFFLINE — no internet. State-channel local tier: compute on the bundled
      // engine so the framework keeps working. Queued for settlement on reconnect.
      if (await _settleCapReached()) { sendResponse(_capResponse(await _getPending())); return; }
      const local = _localCompute(method, a, 'offline');
      if (local) { const p = await _enqueuePending(a, 'offline'); local.local_hash = p.local_hash; local.pending = p.pending; sendResponse({ success: true, status: 200, data: local, local: true }); return; }
      sendResponse({ success: false, error: 'offline — no local compute path for ' + method });
      return;
    }
    // NO CREDIT (402) on metered compute → free tier (/zeq/prove); if that's
    // also unreachable, drop to the local engine. Either way it's a DEFERRED
    // charge: queued + capped so the free tier stays finite (settle/subscribe).
    if (r.status === 402 && method === 'compute') {
      if (await _settleCapReached()) { sendResponse(_capResponse(await _getPending())); return; }
      try {
        const fr = await fetch(`${apiUrl}/zeq/prove`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(a) });
        const fj = await fr.json().catch(() => ({}));
        if (fr.ok) {
          fj.free_tier = true; fj.degraded = true;
          const p = await _enqueuePending(a, 'no_credit'); fj.local_hash = p.local_hash; fj.pending = p.pending;
          fj.note = 'Free tier — out of ZEQ. Queued to settle when you top up (' + p.pending + ' pending).';
          sendResponse({ success: true, status: 200, data: fj, free_tier: true });
          return;
        }
      } catch (_) { /* prove unreachable — try local below */ }
      const local = _localCompute(method, a, 'no_credit');
      if (local) { const p = await _enqueuePending(a, 'no_credit'); local.local_hash = p.local_hash; local.pending = p.pending; sendResponse({ success: true, status: 200, data: local, local: true }); return; }
    }
    // Successful metered call → we're online with credit: settle any backlog.
    if (r.ok && _PHYS_METERED.has(method)) { _settlePending(); }
    sendResponse({ success: r.ok, status: r.status, data: j });
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}
// Cap: the offline/free tier is finite. Past MAX_UNSETTLED (unless an
// enterprise/air-gap license is present) the user must settle or subscribe.
async function _settleCapReached() {
  const q = await _getPending();
  if (q.length < MAX_UNSETTLED) return false;
  return !(await _hasAirgapLicense());
}
function _capResponse(q) {
  return { success: false, status: 402, needs_settlement: true, pending: q.length,
    error: 'You have ' + q.length + ' unsettled off-line computes. Reconnect to settle, or subscribe (enterprise/air-gap) to keep computing offline.' };
}
// SC.1 — local compute via the bundled engine (offline / no-credit tier).
// Returns a degraded result (no charged state envelope) flagged for settlement.
function _localCompute(method, a, reason) {
  if (typeof ZeqLocalEngine === 'undefined' || method !== 'compute') return null;
  try {
    const res = ZeqLocalEngine.compute({ domain: a.domain, operators: a.operators, inputs: a.inputs });
    return Object.assign({}, res, {
      local: true, free_tier: true, settle_pending: true, reason,
      note: reason === 'offline'
        ? 'Computed locally (offline) — no charged state envelope. Will settle to your machine on reconnect.'
        : 'Computed locally (out of ZEQ) — no charged state envelope. Will settle when you top up.',
    });
  } catch (_) { return null; }
}

// SC.2 — pending ledger + settlement. Off-main computes (local/free) are a
// DEFERRED charge: queued here, settled to the machine on reconnect. The cap
// makes the offline/free tier finite — past it, the user must settle/subscribe
// (an enterprise/air-gap license raises the cap). Not a free ride.
const MAX_UNSETTLED = 100;
async function _getPending() { const c = await chrome.storage.local.get('zeq_pending_compute'); return Array.isArray(c.zeq_pending_compute) ? c.zeq_pending_compute : []; }
async function _setPending(arr) { await chrome.storage.local.set({ zeq_pending_compute: arr }); }
async function _hasAirgapLicense() { const c = await chrome.storage.local.get('zeq_license'); return !!(c.zeq_license && c.zeq_license.airgap); }
async function _hashEntry(operators, inputs) {
  const data = new TextEncoder().encode(JSON.stringify({ o: operators || [], i: inputs || {}, t: Date.now(), r: Math.random() }));
  const buf = await crypto.subtle.digest('SHA-256', data);
  const b = btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(buf)))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
  return 'lh_' + b.slice(0, 40);
}
async function _enqueuePending(a, reason) {
  const q = await _getPending();
  const local_hash = await _hashEntry(a.operators, a.inputs);
  q.push({ local_hash, operators: a.operators || ['KO42'], inputs: a.inputs || {}, reason, at: Date.now() });
  await _setPending(q);
  return { local_hash, pending: q.length };
}
async function _settlePending() {
  try {
    const c = await _authCtx();
    if (!c.token) return;
    const q = await _getPending();
    if (!q.length) return;
    const r = await fetch(`${c.apiUrl}/zeq/settle`, { method: 'POST', headers: c.headers, body: JSON.stringify({ entries: q.slice(0, 100).map((e) => ({ local_hash: e.local_hash, operators: e.operators, inputs: e.inputs })) }) });
    const j = await r.json().catch(() => ({}));
    if (j && j.ok) {
      const done = new Set((j.results || []).filter((x) => x.settled).map((x) => x.local_hash));
      if (done.size) await _setPending(q.filter((e) => !done.has(e.local_hash)));
    }
  } catch (_) { /* stay queued, retry later */ }
}
// Settle whenever connectivity returns.
try { self.addEventListener('online', () => { _settlePending(); }); } catch (_) {}
// ── K1b — zero-knowledge vault sync. PUT/GET the opaque {salt,iv,data} blob to
// /api/vault/sync with the account session token (the vault equation that
// decrypts it never leaves the panel).
async function handleVaultSync(message, sendResponse) {
  try {
    const c = await _authCtx();
    if (!c.token) { sendResponse({ success: false, error: 'not signed in' }); return; }
    if (message.op === 'put') {
      const r = await fetch(`${c.apiUrl}/vault/sync`, { method: 'PUT', headers: c.headers, body: JSON.stringify({ blob: message.blob, version: message.version }) });
      const j = await r.json().catch(() => ({}));
      sendResponse({ success: r.ok, status: r.status, data: j });
    } else {
      const r = await fetch(`${c.apiUrl}/vault/sync`, { headers: c.headers });
      const j = await r.json().catch(() => ({}));
      sendResponse({ success: r.ok, status: r.status, data: j });
    }
  } catch (e) { sendResponse({ success: false, error: e.message }); }
}

// ── Process Query via API (SSE streaming) ────────────────────────────────
async function handleQueryProcess(message, sendResponse) {
  try {
    const apiUrl = await getApiUrl();
    const headers = { 'Content-Type': 'application/json' };

    // Add auth headers if token exists
    const stored = await chrome.storage.local.get('zeq_auth_session');
    if (stored.zeq_auth_session?.token) {
      headers['Authorization'] = `Bearer ${stored.zeq_auth_session.token}`;
    }

    const response = await fetch(`${apiUrl}${ZeqConfig.endpoints.compute}`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ query: message.query }),
    });

    // Handle SSE streaming response
    if (response.headers.get('content-type')?.includes('text/event-stream')) {
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let fullText = '';
      let ckoData = null;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        const chunk = decoder.decode(value, { stream: true });
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const payload = line.slice(6).trim();
            if (payload === '[DONE]') continue;
            try {
              const parsed = JSON.parse(payload);
              if (parsed.token) fullText += parsed.token;
              if (parsed.waveformOutput) ckoData = parsed.waveformOutput;
              if (parsed.zeqMetadata) {
                lastCKO = {
                  cko:             ckoData?.cko || parsed.waveformOutput?.cko,
                  computationTime: ckoData?.computationTime,
                  metadata:        parsed.zeqMetadata,
                  timestamp:       Date.now(),
                };
              }
            } catch {}
          }
        }
      }

      sendResponse({ success: true, data: { text: fullText, cko: lastCKO } });
    } else {
      // Fallback: JSON response
      const data = await response.json();
      if (data.waveformOutput) {
        lastCKO = {
          cko:             data.waveformOutput.cko,
          computationTime: data.waveformOutput.computationTime,
          metadata:        data.zeqMetadata,
          timestamp:       Date.now(),
        };
      }
      sendResponse({ success: true, data });
    }
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

// ── 7-Step Wizard Process ────────────────────────────────────────────────
async function handleWizardProcess(message, sendResponse) {
  try {
    const apiUrl = await getApiUrl();
    const response = await fetch(`${apiUrl}${ZeqConfig.endpoints.compute}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ query: message.query, wizard: true }),
    });
    const data = await response.json();
    sendResponse({ success: true, data });
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

// ── Operator Registry ────────────────────────────────────────────────────
async function handleFetchOperators(sendResponse) {
  try {
    // Check cache first (24h TTL)
    const cached = await chrome.storage.local.get('zeq_operator_cache');
    if (cached.zeq_operator_cache?.timestamp &&
        Date.now() - cached.zeq_operator_cache.timestamp < 86400000) {
      sendResponse({ success: true, data: cached.zeq_operator_cache.data });
      return;
    }

    const apiUrl = await getApiUrl();
    const response = await fetch(`${apiUrl}${ZeqConfig.endpoints.operators}`);
    const data = await response.json();

    // Cache the result
    chrome.storage.local.set({
      zeq_operator_cache: { data, timestamp: Date.now() }
    });

    sendResponse({ success: true, data });
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

async function handleSearchOperators(query, sendResponse) {
  try {
    const apiUrl = await getApiUrl();
    const response = await fetch(`${apiUrl}${ZeqConfig.endpoints.operators}?q=${encodeURIComponent(query)}`);
    const data = await response.json();
    sendResponse({ success: true, data });
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

// ── Skill Generation ────────────────────────────────────────────────────
async function handleSkillGeneration(skillId, sendResponse) {
  try {
    const apiUrl = await getApiUrl();
    const response = await fetch(`${apiUrl}/skills/generate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ template: skillId }),
    });
    const data = await response.json();
    sendResponse({ success: true, data });
  } catch (e) {
    sendResponse({ success: false, error: e.message });
  }
}

// ── Service Status Check ─────────────────────────────────────────────────
async function handleServiceStatus(sendResponse) {
  const apiUrl = await getApiUrl();
  const e = ZeqConfig.endpoints;
  const services = [
    { name: 'API',            url: `${apiUrl}${e.health}`,        key: 'apiGateway' },
    { name: 'Operators',      url: `${apiUrl}${e.operators}`,     key: 'operators' },
    { name: 'Compute',        url: `${apiUrl}${e.compute}`,       key: 'compute', method: 'OPTIONS' },
    { name: 'Entangled state',url: `${apiUrl}${e.chainRecent}`,   key: 'chain' },
    { name: 'ZeqSSL',         url: `${apiUrl}${e.sslStatus}`,     key: 'ssl' },
  ];

  const status = {
    syncEngine: syncWsReady ? 'online' : 'offline',
    meshNodes:  meshNodeCount,
  };

  const checks = services.map(async (svc) => {
    try {
      const res = await fetch(svc.url, { method: svc.method || 'GET', signal: AbortSignal.timeout(3000) });
      // Liveness probe: any response below 500 means the service answered
      // (400/401/405 = reachable-but-needs-params/auth). 5xx or throw = down.
      status[svc.key] = res.status < 500 ? 'online' : 'error';
    } catch {
      status[svc.key] = 'offline';
    }
  });

  await Promise.allSettled(checks);
  sendResponse({ success: true, status });
}

// ── Broadcast to all tabs ───────────────────────────────────────────────
function broadcastToAllTabs(message) {
  chrome.tabs.query({}, (tabs) => {
    for (const tab of tabs) {
      chrome.tabs.sendMessage(tab.id, message).catch(() => {});
    }
  });
}

// ── Get API URL from storage ────────────────────────────────────────────
async function getApiUrl() {
  // Single source of truth: ZeqConfig resolves the user-set origin or the
  // live fork default (https://zeqstate.com) — never the dead localhost stack.
  return ZeqConfig.api();
}

// ── Sync Engine WebSocket (Mesh System) ──────────────────────────────────
// Optional: if the fork exposes a /ws mesh endpoint we join it for the real
// HulyaPulse tick; otherwise this fails closed and we fall back to the local
// computed tick (handled in onclose). Derived from the live origin — no
// hard-coded localhost.
async function connectSyncEngine() {
  if (syncWs) { try { syncWs.close(); } catch {} }

  const origin = await ZeqConfig.origin();
  const WS_URL = origin.replace(/^http/, 'ws') + '/ws';
  syncWs = new WebSocket(WS_URL);

  syncWs.onopen = () => {
    syncReconnectAttempts = 0;
    syncWsReady = true;
    console.log('[Zeq] Sync Engine WebSocket connected — mesh joined');

    // Register as chrome extension mesh node
    syncWs.send(JSON.stringify({
      type: 'register',
      nodeType: 'chrome-extension',
      capabilities: ['interceptor', 'verification', 'chat', 'encryption'],
      version: '1.287.5',
    }));
  };

  syncWs.onmessage = (event) => {
    let data;
    try { data = JSON.parse(event.data); } catch { return; }

    switch (data.type) {
      case 'tick':
        // Real HulyaPulse tick from sync engine — use instead of local tick()
        pulseCount++;
        meshNodeCount = data.connections || 0;

        // Update badge with real phase
        const phase = data.phase ?? ((Date.now() / 1000 % PERIOD) / PERIOD);
        chrome.action.setBadgeText({ text: phase.toFixed(2) });
        chrome.action.setBadgeBackgroundColor({ color: '#06b6d4' });

        // Broadcast real tick to all contexts
        broadcastToAllTabs({
          type: 'zeq_sync_tick',
          phase:     data.phase,
          zeqond:    data.zeqond,
          ko42:      data.ko42,
          frequency: data.frequency || HZ,
          meshNodes: data.connections || 0,
          timestamp: Date.now() / 1000,
        });
        break;

      case 'mesh_update':
        meshNodeCount = data.nodeCount || 0;
        break;

      default:
        break;
    }
  };

  syncWs.onclose = () => {
    syncWsReady = false;
    meshNodeCount = 0;
    console.log('[Zeq] Sync Engine disconnected — falling back to local tick');

    const delay = Math.min(1000 * Math.pow(2, syncReconnectAttempts), 30000);
    syncReconnectAttempts++;
    syncReconnectTimer = setTimeout(connectSyncEngine, delay);
  };

  syncWs.onerror = () => {};
}

// ── Periodic HulyaPulse heartbeat (fallback when sync engine disconnected) ─
setInterval(() => {
  if (!syncWsReady) {
    const state = tick();
    chrome.action.setBadgeText({ text: state.phase.toFixed(2) });
    chrome.action.setBadgeBackgroundColor({ color: '#334155' }); // Gray when local-only
  }
}, PERIOD * 1000);

// ── Initialize ──────────────────────────────────────────────────────────
connectSyncEngine();

console.log(`[Zeq] Service worker started — 1.287 Hz HulyaPulse — ${OPERATORS_SURFACE} — v1.287.6`);
