/**
 * Zeq Auth Client (v3) — one ZID identity for the whole extension.
 *
 * Talks to the LIVE fork's zero-knowledge v3 endpoints (register-v3 / login-v3 /
 * salt/:zid / verify-v3) via ZeqConfig — never the dead localhost:3015/auth.
 * The client only ever sends {equation_hash, equation_salt} (register) or
 * {zid, candidate_hash} (login); the password/equation never leaves the device.
 * Sessions are bearer-token based (cross-origin — no same-origin cookie).
 *
 * Three sign-in methods, all resolving to the same v3 identity:
 *   • Simple  — email + password (ZeqSimpleAuth)
 *   • Expert  — ZID + equation   (salt-fetch → HMAC → login-v3)
 *   • .ZEQ    — encrypted recovery file (HITE decrypt → derive → login/register)
 *
 * Depends on: lib/zeq-config.js, lib/hite-auth.js (HiteCrypto), lib/zeq-simple-auth.js
 *
 * @version 1.287.6
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */
const ZeqAuth = (() => {
  "use strict";

  const STORAGE_KEY = "zeq_auth_session";
  const TOKEN_KEY = "zeq_token";

  let _session = null;     // { token, user:{zid,name,plan}, loginTime }
  let _listeners = [];

  // ── Helpers ──────────────────────────────────────────────────────────────
  async function _api() { return await ZeqConfig.api(); }

  function _mkSession(token, zid, displayName, plan) {
    return {
      token: token,
      user: { id: zid, zid: zid, name: displayName || zid, plan: plan || "free" },
      loginTime: Date.now(),
    };
  }

  function _hmac(message, saltHex) {
    if (!self.HiteCrypto || typeof self.HiteCrypto.hmacHite !== "function") {
      throw new Error("HiteCrypto required (load lib/hite-auth.js first)");
    }
    return self.HiteCrypto.hmacHite(message, saltHex);
  }

  // ── Init — restore + verify an existing session ───────────────────────────
  async function init() {
    const stored = await _getStored();
    if (!stored || !stored.token) return null;
    _session = stored;
    // Best-effort server verify; on hard failure clear the session.
    try {
      const api = await _api();
      const resp = await fetch(api + "/zeq-auth/verify-v3", {
        method: "POST",
        headers: { "Content-Type": "application/json", "Authorization": "Bearer " + stored.token },
        body: JSON.stringify({ token: stored.token }),
      });
      if (resp.status === 401 || resp.status === 403) { await logout(); return null; }
      // Any other status (200, or transient 5xx/offline) → keep the stored session.
    } catch (_) { /* offline — keep stored session, pulse fallback */ }
    return _session;
  }

  // ── Simple tier (email + password) ────────────────────────────────────────
  async function loginSimple(email, password) {
    const r = await ZeqSimpleAuth.login(email, password);
    if (!r.ok) throw new Error((r.data && (r.data.error?.message || r.data.error)) || "Login failed (" + r.status + ")");
    _session = _mkSession(r.data.token, r.zid, r.data.displayName, r.data.plan);
    await _persist(); _notify("login", _session);
    return { success: true, user: _session.user, created: false };
  }

  async function registerSimple(email, password, displayName) {
    const r = await ZeqSimpleAuth.register(email, password, displayName);
    if (!r.ok) throw new Error((r.data && (r.data.error?.message || r.data.error)) || "Registration failed (" + r.status + ")");
    _session = _mkSession(r.data.token, r.zid, r.data.displayName || displayName, r.data.plan);
    await _persist(); _notify("login", _session);
    return { success: true, user: _session.user, created: true, autoMachine: r.data.auto_orb || r.data.autoOrb || null };
  }

  // ── Expert tier (ZID + equation) ──────────────────────────────────────────
  async function loginExpert(zid, equation) {
    const api = await _api();
    const sres = await fetch(api + "/zeq-auth/salt/" + encodeURIComponent(zid));
    const sjson = await sres.json().catch(() => ({}));
    if (!sres.ok || !sjson.salt) throw new Error("Could not fetch salt for ZID");
    const candidate = await _hmac(equation, sjson.salt);
    const lres = await fetch(api + "/zeq-auth/login-v3", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ zid: zid, candidate_hash: candidate }),
    });
    const ljson = await lres.json().catch(() => ({}));
    if (!lres.ok || !ljson.ok) throw new Error((ljson.error?.message) || "Login failed (" + lres.status + ")");
    _session = _mkSession(ljson.token, ljson.zid, ljson.displayName, ljson.plan);
    await _persist(); _notify("login", _session);
    return { success: true, user: _session.user };
  }

  async function registerExpert(displayName, equation) {
    const salt = self.HiteCrypto.randomSaltHex();
    const equation_hash = await _hmac(equation, salt);
    const api = await _api();
    const resp = await fetch(api + "/zeq-auth/register-v3", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ equation_hash: equation_hash, equation_salt: salt, display_name: displayName || "zeq-expert", hash_version: 2 }),
    });
    const data = await resp.json().catch(() => ({}));
    if (!resp.ok || !data.ok) throw new Error((data.error?.message) || "Registration failed (" + resp.status + ")");
    _session = _mkSession(data.token, data.zid, data.displayName || displayName, data.plan);
    await _persist(); _notify("login", _session);
    return { success: true, user: _session.user, salt: salt, equation: equation };
  }

  // ── .ZEQ recovery file (HITE-encrypted {equation, salt}) ───────────────────
  async function loginWithZeqFile(bytes, pin) {
    if (!self.HiteCrypto) throw new Error("HiteCrypto required");
    const dec = await self.HiteCrypto.decrypt(bytes, pin);
    if (!dec || typeof dec.plaintext !== "string") throw new Error("Wrong PIN or not a .ZEQ file");
    let payload; try { payload = JSON.parse(dec.plaintext); } catch { throw new Error("Corrupt .ZEQ payload"); }
    const equation = payload.equation, salt = payload.salt;
    if (!equation || !salt) throw new Error("Incomplete .ZEQ credential");
    const equation_hash = await _hmac(equation, salt);
    const zid = await ZeqSimpleAuth.deriveZid(equation_hash);
    const api = await _api();
    // Try login first; fall back to register (idempotent cross-domain → same ZID).
    let resp = await fetch(api + "/zeq-auth/login-v3", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ zid: zid, candidate_hash: equation_hash }),
    });
    let data = await resp.json().catch(() => ({}));
    if (!(resp.ok && data.ok)) {
      resp = await fetch(api + "/zeq-auth/register-v3", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ equation_hash: equation_hash, equation_salt: salt, display_name: "zeq-recovered", hash_version: 2 }),
      });
      data = await resp.json().catch(() => ({}));
      if (!(resp.ok && data.ok)) throw new Error((data.error?.message) || "Recovery failed (" + resp.status + ")");
    }
    _session = _mkSession(data.token, data.zid || zid, data.displayName, data.plan);
    await _persist(); _notify("login", _session);
    return { success: true, user: _session.user };
  }

  // ── Legacy aliases (the panel + tabs call these names) ─────────────────────
  async function loginWithPassword(password, email) { return loginSimple(email, password); }
  async function registerWithPassword(displayName, password, email) { return registerSimple(email, password, displayName); }
  async function loginWithPhrase(equation, zid) {
    if (!zid) throw new Error("Expert login needs a ZID");
    return loginExpert(zid, equation);
  }
  async function registerWithPhrase(displayName, equation) { return registerExpert(displayName, equation); }

  // ── Session accessors ──────────────────────────────────────────────────────
  async function logout() {
    _session = null;
    if (typeof chrome !== "undefined" && chrome.storage?.local) {
      await chrome.storage.local.remove([STORAGE_KEY, TOKEN_KEY]);
    }
    _notify("logout", null);
  }
  function getHeaders() {
    if (!_session?.token) return {};
    return {
      "Authorization": "Bearer " + _session.token,
      "X-Zeq-Client": "chrome-extension/1.287.6",
      "X-Zeq-Phase": ZeqConfig.phase().toFixed(6),
    };
  }
  function getUser() { return _session?.user || null; }
  function isAuthenticated() { return !!(_session && _session.token); }
  function getToken() { return _session?.token || null; }
  function onAuthChange(cb) { _listeners.push(cb); return () => { _listeners = _listeners.filter((f) => f !== cb); }; }

  // ── Internals ──────────────────────────────────────────────────────────────
  function _notify(event, data) {
    _listeners.forEach((fn) => { try { fn(event, data); } catch (_) {} });
    if (typeof chrome !== "undefined" && chrome.runtime?.sendMessage) {
      chrome.runtime.sendMessage({
        type: "zeq_auth_event", event,
        payload: data ? { user: data.user, authenticated: true } : { authenticated: false },
      }).catch(() => {});
    }
  }
  async function _persist() {
    if (typeof chrome !== "undefined" && chrome.storage?.local) {
      await chrome.storage.local.set({ [STORAGE_KEY]: _session, [TOKEN_KEY]: _session?.token || "" });
    }
  }
  async function _getStored() {
    if (typeof chrome !== "undefined" && chrome.storage?.local) {
      return new Promise((resolve) => chrome.storage.local.get(STORAGE_KEY, (r) => resolve(r[STORAGE_KEY] || null)));
    }
    return null;
  }

  return {
    init,
    loginSimple, registerSimple,
    loginExpert, registerExpert,
    loginWithZeqFile,
    loginWithPassword, registerWithPassword, loginWithPhrase, registerWithPhrase,
    logout, getHeaders, getUser, getToken, isAuthenticated, onAuthChange,
  };
})();

if (typeof module !== "undefined" && module.exports) { module.exports = ZeqAuth; }
