/**
 * VaultClient — Equation-based Password Manager Storage
 * Ported from apps/zeq-vault/ client-side logic.
 * AES-256-GCM encryption, PBKDF2 key derivation, localStorage persistence.
 *
 * @version 1.287.5
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */
const VaultClient = (() => {
  'use strict';

  const STORAGE_KEY = 'zeq-vault-data';
  const LOCK_TIMEOUT = 300; // 5 minutes in seconds
  const VAULT_CLIENT_ID = 'zeq-chrome-vault';
  // K1b: zero-knowledge server sync. The vault is encrypted client-side under
  // the user's equation (never sent); the SW PUT/GETs the opaque {salt,iv,data}
  // blob to /api/vault/sync keyed by the account ZID, so it syncs across the
  // user's devices. localStorage remains the working copy.
  const SERVER_SYNC_ENABLED = true;
  const AUTH_BASE_URL   = ''; // unused — sync routes through the service worker
  const SYNC_DEBOUNCE   = 2000;

  let masterEquation = null;
  let entries = [];
  let autoLockRemaining = LOCK_TIMEOUT;
  let autoLockInterval = null;
  let _serverToken = null;
  let _syncTimer = null;
  let _syncing = false;

  const listeners = { lock: [], unlock: [], change: [], autoLockTick: [], sync: [] };

  // ── Constants & Variables for Equation Parser ─────────────────────────────
  const CONSTANTS = { pi: Math.PI, e: Math.E, phi: (1 + Math.sqrt(5)) / 2 };
  const FUNCTIONS = {
    sin: Math.sin, cos: Math.cos, tan: Math.tan,
    asin: Math.asin, acos: Math.acos, atan: Math.atan,
    sqrt: Math.sqrt, abs: Math.abs, log: Math.log, ln: Math.log,
    log10: Math.log10, exp: Math.exp, floor: Math.floor, ceil: Math.ceil,
    round: Math.round, sign: Math.sign
  };
  const VARIABLES = { x: 1.287, y: 0.777 };

  // ── Tokenizer ─────────────────────────────────────────────────────────────
  class Tokenizer {
    constructor(input) {
      this.input = input;
      this.pos = 0;
      this.tokens = [];
      this.tokenize();
      this.current = 0;
    }

    tokenize() {
      while (this.pos < this.input.length) {
        const ch = this.input[this.pos];
        if (/\s/.test(ch)) { this.pos++; continue; }
        if (/[0-9.]/.test(ch)) {
          let num = '';
          let hasDot = false;
          while (this.pos < this.input.length && (/[0-9]/.test(this.input[this.pos]) || (this.input[this.pos] === '.' && !hasDot))) {
            if (this.input[this.pos] === '.') hasDot = true;
            num += this.input[this.pos++];
          }
          this.tokens.push({ type: 'NUMBER', value: parseFloat(num) });
          continue;
        }
        if (/[a-zA-Z_]/.test(ch)) {
          let id = '';
          while (this.pos < this.input.length && /[a-zA-Z0-9_]/.test(this.input[this.pos])) {
            id += this.input[this.pos++];
          }
          this.tokens.push({ type: 'IDENT', value: id });
          continue;
        }
        if ('+-*/^(),'.includes(ch)) {
          this.tokens.push({ type: ch, value: ch });
          this.pos++;
          continue;
        }
        throw new Error('Unexpected character: ' + ch);
      }
      this.tokens.push({ type: 'EOF', value: null });
    }

    peek() { return this.tokens[this.current]; }
    advance() { return this.tokens[this.current++]; }
    expect(type) {
      const tok = this.advance();
      if (tok.type !== type) throw new Error('Expected ' + type + ' but got ' + tok.type);
      return tok;
    }
  }

  // ── Parser (Recursive Descent) ────────────────────────────────────────────
  class Parser {
    constructor(tokenizer) { this.tok = tokenizer; }

    parse() {
      const result = this.expr();
      if (this.tok.peek().type !== 'EOF') throw new Error('Unexpected token after expression');
      return result;
    }

    expr() {
      let left = this.term();
      while (this.tok.peek().type === '+' || this.tok.peek().type === '-') {
        const op = this.tok.advance().type;
        const right = this.term();
        left = op === '+' ? left + right : left - right;
      }
      return left;
    }

    term() {
      let left = this.unary();
      while (this.tok.peek().type === '*' || this.tok.peek().type === '/') {
        const op = this.tok.advance().type;
        const right = this.unary();
        left = op === '*' ? left * right : left / right;
      }
      return left;
    }

    unary() {
      if (this.tok.peek().type === '-') { this.tok.advance(); return -this.unary(); }
      if (this.tok.peek().type === '+') { this.tok.advance(); return this.unary(); }
      return this.power();
    }

    power() {
      let base = this.call();
      if (this.tok.peek().type === '^') {
        this.tok.advance();
        const exp = this.unary();
        base = Math.pow(base, exp);
      }
      return base;
    }

    call() {
      if (this.tok.peek().type === 'IDENT') {
        const name = this.tok.peek().value.toLowerCase();
        if (FUNCTIONS[name] && this.tok.tokens[this.tok.current + 1] && this.tok.tokens[this.tok.current + 1].type === '(') {
          this.tok.advance();
          this.tok.expect('(');
          const arg = this.expr();
          this.tok.expect(')');
          return FUNCTIONS[name](arg);
        }
      }
      return this.primary();
    }

    primary() {
      const tok = this.tok.peek();
      if (tok.type === 'NUMBER') { this.tok.advance(); return tok.value; }
      if (tok.type === 'IDENT') {
        this.tok.advance();
        const name = tok.value.toLowerCase();
        if (VARIABLES.hasOwnProperty(name)) return VARIABLES[name];
        if (CONSTANTS.hasOwnProperty(name)) return CONSTANTS[name];
        throw new Error('Unknown variable: ' + tok.value);
      }
      if (tok.type === '(') {
        this.tok.advance();
        const val = this.expr();
        this.tok.expect(')');
        return val;
      }
      throw new Error('Unexpected token: ' + tok.type);
    }
  }

  // ── Evaluate Equation ─────────────────────────────────────────────────────
  function evaluateEquation(equation) {
    if (!equation || !equation.trim()) return null;
    try {
      const tokenizer = new Tokenizer(equation.trim());
      const parser = new Parser(tokenizer);
      const result = parser.parse();
      if (!isFinite(result)) return null;
      return result;
    } catch (e) {
      return null;
    }
  }

  // ── Equation Strength ─────────────────────────────────────────────────────
  function equationStrength(eq) {
    if (!eq || !eq.trim()) return { level: 0, label: '' };
    const s = eq.trim();
    let score = 0;
    const ops = (s.match(/[+\-*/^]/g) || []).length;
    score += Math.min(ops, 6);
    const fns = Object.keys(FUNCTIONS);
    let fnCount = 0;
    for (const fn of fns) {
      const regex = new RegExp(fn + '\\s*\\(', 'gi');
      fnCount += (s.match(regex) || []).length;
    }
    score += fnCount * 2;
    if (/\bx\b/i.test(s)) score += 1;
    if (/\by\b/i.test(s)) score += 1;
    if (/\bpi\b/i.test(s)) score += 1;
    if (/\be\b/i.test(s)) score += 0.5;
    let depth = 0, maxDepth = 0;
    for (const ch of s) {
      if (ch === '(') { depth++; maxDepth = Math.max(maxDepth, depth); }
      if (ch === ')') depth--;
    }
    score += maxDepth;
    score += Math.min(s.length / 10, 3);
    score += (s.match(/\d+\.\d+/g) || []).length * 0.5;

    if (score < 3) return { level: 1, label: 'Weak' };
    if (score < 7) return { level: 2, label: 'Medium' };
    if (score < 12) return { level: 3, label: 'Strong' };
    return { level: 4, label: 'Very Strong' };
  }

  // ── Random Equation Generator ─────────────────────────────────────────────
  function generateEquation(complexity) {
    complexity = Math.max(1, Math.min(5, complexity || 3));
    const fns = ['sin', 'cos', 'sqrt', 'abs', 'tan', 'exp', 'log'];
    const vars = ['x', 'y'];
    const consts = ['pi', 'e'];
    const randNum = () => (Math.random() * 40 - 20).toFixed(2).replace(/\.?0+$/, '') || '0';
    const randVar = () => vars[Math.floor(Math.random() * vars.length)];
    const randFn = () => fns[Math.floor(Math.random() * fns.length)];
    const randConst = () => consts[Math.floor(Math.random() * consts.length)];
    const opsList = ['+', '-', '*', '/'];
    const randOp = () => opsList[Math.floor(Math.random() * opsList.length)];

    function buildTerm(depth) {
      if (depth <= 0) {
        const r = Math.random();
        if (r < 0.4) return randVar();
        if (r < 0.6) return randConst();
        return randNum();
      }
      const r = Math.random();
      if (r < 0.35) return randFn() + '(' + buildTerm(depth - 1) + ')';
      if (r < 0.55) return buildTerm(depth - 1) + '^' + (Math.floor(Math.random() * 3) + 2);
      if (r < 0.75) return randFn() + '(' + buildTerm(depth - 1) + ' * ' + randNum() + ')';
      return '(' + buildTerm(depth - 1) + ' ' + randOp() + ' ' + buildTerm(depth - 1) + ')';
    }

    const termCount = complexity + Math.floor(Math.random() * 2);
    const parts = [];
    for (let i = 0; i < termCount; i++) {
      parts.push(buildTerm(Math.min(complexity - 1, 2)));
    }
    let eq = parts.join(' ' + randOp() + ' ');
    const result = evaluateEquation(eq);
    if (result === null) return 'x^2 + sin(y * pi) - ' + randNum();
    return eq;
  }

  // ── Phrase-to-Equation (client-side, no server needed) ────────────────────
  // Full algorithm matching standalone zeqtext & zeq-vault apps exactly.
  // Uses domain detection + operator selection for dynamic harmony calculation.
  const _PTE_HZ = 1.287;

  function _fnv1a(s) {
    let h = 0x811c9dc5;
    for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); }
    return h >>> 0;
  }

  function _hashStr(q) {
    let h = 0;
    for (let i = 0; i < q.length; i++) { h = ((h << 5) - h) + q.charCodeAt(i); h = h & h; }
    return Math.abs(h) / 2147483647;
  }

  const _domainKw = {
    quantum: ['quantum', 'superposition', 'entanglement', 'photon', 'qubit'],
    consciousness: ['consciousness', 'aware', 'mind', 'thought', 'soul', 'dream'],
    field: ['field', 'wave', 'energy', 'force', 'vibration', 'frequency', 'resonance'],
    temporal: ['time', 'temporal', 'clock', 'moment', 'past', 'future'],
    thermodynamics: ['heat', 'entropy', 'temperature', 'thermal', 'fire', 'cold', 'ice'],
    relativistic: ['relativity', 'spacetime', 'gravity', 'einstein', 'light speed'],
    cosmological: ['dark matter', 'cosmic', 'universe', 'galaxy', 'star', 'moon', 'sun', 'space'],
    resonance: ['resonance', 'harmonic', 'vibrate', 'pulse', 'rhythm', 'music', 'sound'],
    information: ['information', 'data', 'code', 'signal', 'message', 'binary'],
    marine: ['whale', 'dolphin', 'ocean', 'sea', 'coral', 'water', 'wave', 'tide'],
  };

  function _detectDomains(phrase) {
    const q = phrase.toLowerCase();
    const domains = [];
    for (const d in _domainKw) {
      const kws = _domainKw[d];
      for (let i = 0; i < kws.length; i++) {
        if (q.indexOf(kws[i]) !== -1) { domains.push(d); break; }
      }
    }
    if (!domains.length) domains.push('field', 'information', 'consciousness');
    return domains;
  }

  const _opMap = {
    quantum: ['QM3', 'QM4', 'QRO1'],
    consciousness: ['HRO00', 'CBCM', 'CAO19'],
    field: ['FC-QA', 'FC-GS', 'FC-SC'],
    temporal: ['ZEQ10-TR', 'PS-H3'],
    thermodynamics: ['TH1', 'TH2', 'TH3'],
    relativistic: ['GR31', 'GR32'],
    cosmological: ['CDO1', 'CDO2', 'CDO3'],
    resonance: ['ZEQ-TETHER-001', 'ZEQ-TETHER-002'],
    information: ['CS43', 'CS44'],
    marine: ['MIO1', 'MIO2'],
  };

  function _selectOperators(phrase, domains) {
    let ops = ['KO42', 'HRO000', 'CS87'];
    for (let i = 0; i < domains.length; i++) {
      if (_opMap[domains[i]]) ops = ops.concat(_opMap[domains[i]]);
    }
    ops.push('ZEQ-PROTECT-001', 'ZEQ-PROTECT-002');
    const seen = {};
    const unique = [];
    for (let j = 0; j < ops.length; j++) {
      if (!seen[ops[j]]) { seen[ops[j]] = 1; unique.push(ops[j]); }
    }
    return unique.slice(0, 15);
  }

  function phraseToEquation(phrase) {
    const words = phrase.trim().toLowerCase().split(/\s+/).filter(w => w.length > 0);
    if (words.length < 2) return null;

    const now = Date.now() / 1000;
    const phase = (now * _PTE_HZ) % 1;
    const ko42 = 0.00129 * Math.sin(2 * Math.PI * _PTE_HZ * now);
    const zeqond = Math.floor(now / 0.777);
    const phraseHash = _hashStr(phrase.trim().toLowerCase());
    const domains = _detectDomains(phrase);
    const operators = _selectOperators(phrase, domains);

    const q = phrase.toLowerCase();
    const charSet = {};
    let uniqueChars = 0;
    for (let c = 0; c < q.length; c++) {
      if (!charSet[q[c]]) { charSet[q[c]] = 1; uniqueChars++; }
    }
    const consciousness = Math.min(2.0, 0.5 + (uniqueChars / 26) + (Math.log(words.length + 1) / 10));
    const harmony = Math.min(0.999, 0.618 + (domains.length / 10) * 0.2 + (operators.length / 15) * 0.15);

    const fns = ['sin', 'cos', 'sqrt', 'abs'];
    const terms = [];
    const numTerms = Math.min(operators.length, 6);
    for (let i = 0; i < numTerms; i++) {
      const w = (i < words.length) ? words[i] + ':' + i : operators[i] + ':' + i;
      const wh = _fnv1a(w);
      const coeff = ((wh % 997) / 100);
      const fn = fns[wh % fns.length];
      const useX = (wh % 2 === 0);
      const sign = (i === 0) ? '' : ((wh % 5 < 2) ? ' - ' : ' + ');
      terms.push(sign + coeff.toFixed(2) + '*' + fn + '(' + (useX ? 'x' : 'y') + ')');
    }
    terms.push(' + ' + consciousness.toFixed(3) + '*x');
    terms.push(' + ' + harmony.toFixed(3) + '*y');
    terms.push(' + ' + phase.toFixed(6));
    terms.push(' + ' + ko42.toFixed(8));
    terms.push(' + ' + (zeqond % 100000) + '.' + (phraseHash * 42.19).toFixed(4).split('.')[1]);

    return terms.join('');
  }

  // ── Crypto (Web Crypto API) ───────────────────────────────────────────────
  function arrayToBase64(arr) {
    let binary = '';
    for (let i = 0; i < arr.length; i++) binary += String.fromCharCode(arr[i]);
    return btoa(binary);
  }

  function base64ToArray(b64) {
    const binary = atob(b64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
    return bytes;
  }

  // Per-vault random salt (64 hex). Replaces the old hard-coded
  // 'zeq-vault-1.287' constant salt — every vault now derives its key under a
  // unique salt, so two vaults with the same equation produce different keys
  // and a precomputed/rainbow attack can't span vaults. Legacy blobs (no salt
  // field) transparently fall back to the old constant so they still open.
  const LEGACY_SALT = 'zeq-vault-1.287';
  let _vaultSalt = null;
  function _newSaltHex() {
    const raw = crypto.getRandomValues(new Uint8Array(32));
    let s = ''; for (let i = 0; i < raw.length; i++) s += raw[i].toString(16).padStart(2, '0');
    return s;
  }

  async function deriveKey(equation, saltStr) {
    const result = evaluateEquation(equation);
    if (result === null) throw new Error('Invalid equation');
    const seed = equation.trim() + ':' + result.toString();
    const encoded = new TextEncoder().encode(seed);
    const keyMaterial = await crypto.subtle.importKey('raw', encoded, 'PBKDF2', false, ['deriveKey']);
    return crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: new TextEncoder().encode(saltStr || LEGACY_SALT),
        iterations: 100000,
        hash: 'SHA-256'
      },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt', 'decrypt']
    );
  }

  async function encryptData(plaintext, equation, saltStr) {
    const salt = saltStr || _vaultSalt || (_vaultSalt = _newSaltHex());
    const key = await deriveKey(equation, salt);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const data = new TextEncoder().encode(plaintext);
    const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data);
    return { salt, iv: arrayToBase64(iv), data: arrayToBase64(new Uint8Array(ciphertext)) };
  }

  async function decryptData(encrypted, equation) {
    // Legacy blobs have no salt → fall back to the old constant.
    const salt = encrypted.salt || LEGACY_SALT;
    const key = await deriveKey(equation, salt);
    const iv = base64ToArray(encrypted.iv);
    const data = base64ToArray(encrypted.data);
    const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
    if (encrypted.salt) _vaultSalt = encrypted.salt; // adopt the blob's salt for re-saves
    return new TextDecoder().decode(decrypted);
  }

  // ── Persistence ───────────────────────────────────────────────────────────
  function hasStoredVault() {
    return !!localStorage.getItem(STORAGE_KEY);
  }

  async function saveVault() {
    if (!masterEquation) return;
    const plaintext = JSON.stringify(entries);
    const encrypted = await encryptData(plaintext, masterEquation);
    localStorage.setItem(STORAGE_KEY, JSON.stringify(encrypted));
    // Debounced server sync after every local save
    _scheduleSyncToServer();
  }

  async function loadVault(equation) {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return [];
    const encrypted = JSON.parse(raw);
    const plaintext = await decryptData(encrypted, equation);
    return JSON.parse(plaintext);
  }

  // ── Vault Operations ──────────────────────────────────────────────────────
  async function createVault(equation) {
    const result = evaluateEquation(equation);
    if (result === null) throw new Error('Invalid equation');
    entries = [];
    masterEquation = equation;
    await saveVault();
    _startAutoLock();
    _notify('unlock');
    _notify('change');
  }

  async function unlockVault(equation) {
    const result = evaluateEquation(equation);
    if (result === null) throw new Error('Invalid equation');
    if (hasStoredVault()) {
      entries = await loadVault(equation);
    } else {
      entries = [];
    }
    masterEquation = equation;
    await saveVault();
    _startAutoLock();
    _notify('unlock');
    _notify('change');
    // K1b — pull any newer blob from the server (another device) and merge.
    // Best-effort: wrong equation / not-signed-in just no-ops.
    try { await syncFromServer(equation); } catch (_) {}
  }

  function lockVault() {
    masterEquation = null;
    entries = [];
    _stopAutoLock();
    _notify('lock');
  }

  function isUnlocked() {
    return masterEquation !== null;
  }

  // ── Entry CRUD ────────────────────────────────────────────────────────────
  function getEntries() {
    return [...entries];
  }

  function getEntry(id) {
    return entries.find(e => e.id === id) || null;
  }

  async function addEntry({ service, url, username, equation, notes }) {
    if (!service) throw new Error('Service name is required');
    if (!equation) throw new Error('Equation is required');
    if (evaluateEquation(equation) === null) throw new Error('Invalid equation');
    const now = Date.now();
    const newEntry = {
      id: crypto.randomUUID ? crypto.randomUUID() : 'e-' + now + '-' + Math.random().toString(36).slice(2, 8),
      service,
      url: url || '',
      username: username || '',
      equation,
      notes: notes || '',
      created: now,
      modified: now
    };
    entries.push(newEntry);
    await saveVault();
    _notify('change');
    return newEntry;
  }

  async function updateEntry(id, updates) {
    const entry = entries.find(e => e.id === id);
    if (!entry) throw new Error('Entry not found');
    if (updates.equation && evaluateEquation(updates.equation) === null) throw new Error('Invalid equation');
    if (updates.service !== undefined) entry.service = updates.service;
    if (updates.url !== undefined) entry.url = updates.url;
    if (updates.username !== undefined) entry.username = updates.username;
    if (updates.equation !== undefined) entry.equation = updates.equation;
    if (updates.notes !== undefined) entry.notes = updates.notes;
    entry.modified = Date.now();
    await saveVault();
    _notify('change');
    return entry;
  }

  async function deleteEntry(id) {
    const idx = entries.findIndex(e => e.id === id);
    if (idx === -1) throw new Error('Entry not found');
    entries.splice(idx, 1);
    await saveVault();
    _notify('change');
  }

  // ── Server Sync (K1b) — zero-knowledge blob via the SW → /api/vault/sync ──
  // The vault stays encrypted under the user's equation (never sent). The SW
  // holds the account session and PUT/GETs the opaque {salt,iv,data} blob.
  let _serverVersion = 0;
  function _sw(msg) { return new Promise((res) => { try { chrome.runtime.sendMessage(msg, (r) => res(chrome.runtime.lastError ? null : r)); } catch (_) { res(null); } }); }
  function setServerCredentials(token) { _serverToken = token; } // retained for API compat; auth now lives in the SW

  function _scheduleSyncToServer() {
    if (!SERVER_SYNC_ENABLED || !masterEquation) return;
    if (_syncTimer) clearTimeout(_syncTimer);
    _syncTimer = setTimeout(() => syncToServer(), SYNC_DEBOUNCE);
  }

  async function syncToServer() {
    if (!SERVER_SYNC_ENABLED || !masterEquation || _syncing) return;
    _syncing = true;
    try {
      const encrypted = await encryptData(JSON.stringify(entries), masterEquation); // {salt, iv, data}
      const r = await _sw({ type: 'vaultSync', op: 'put', blob: encrypted, version: _serverVersion });
      if (r && r.success && r.data && r.data.ok) {
        _serverVersion = r.data.version || (_serverVersion + 1);
        _notify('sync', { direction: 'up', entries: entries.length, version: _serverVersion });
      } else if (r && r.status === 409) {
        // A newer save exists on the server (another device) — pull + merge,
        // then the next save pushes the union forward.
        await syncFromServer();
      } else if (r && !r.success && r.error !== 'not signed in') {
        console.warn('[VaultClient] sync up:', (r && r.error) || 'failed');
      }
    } catch (e) { console.warn('[VaultClient] sync up failed:', e.message); }
    finally { _syncing = false; }
  }

  async function syncFromServer(equation) {
    if (!SERVER_SYNC_ENABLED) return false;
    const eq = equation || masterEquation;
    if (!eq) return false;
    try {
      const r = await _sw({ type: 'vaultSync', op: 'get' });
      if (!r || !r.success || !r.data || !r.data.ok || !r.data.blob) return false;
      const blob = r.data.blob; // opaque { salt, iv, data }
      _serverVersion = r.data.version || _serverVersion;
      if (!blob.iv || !blob.data) return false;
      const serverEntries = JSON.parse(await decryptData(blob, eq));
      if (!Array.isArray(serverEntries)) return false;
      // Merge by id; newer `modified` wins.
      const localIds = new Set(entries.map(e => e.id));
      let added = 0;
      for (const entry of serverEntries) {
        if (!localIds.has(entry.id)) { entries.push(entry); added++; }
        else { const local = entries.find(e => e.id === entry.id); if (local && entry.modified > local.modified) { Object.assign(local, entry); added++; } }
      }
      if (added > 0) { await saveVault(); _notify('change'); }
      _notify('sync', { direction: 'down', entries: serverEntries.length, added, version: _serverVersion });
      return true;
    } catch (e) { console.warn('[VaultClient] pull failed (wrong equation?):', e.message); return false; }
  }

  // ── Export / Import ───────────────────────────────────────────────────────
  async function exportBackup() {
    if (entries.length === 0) throw new Error('No entries to export');
    const encrypted = await encryptData(JSON.stringify(entries), masterEquation);
    return {
      version: '1.0.0',
      type: 'zeq-vault-backup',
      created: new Date().toISOString(),
      encrypted
    };
  }

  async function importBackup(backupJson, equation) {
    if (!backupJson.encrypted || !backupJson.encrypted.iv || !backupJson.encrypted.data) {
      throw new Error('Invalid backup file format');
    }
    const decrypted = await decryptData(backupJson.encrypted, equation);
    const importedEntries = JSON.parse(decrypted);
    if (!Array.isArray(importedEntries)) throw new Error('Invalid entries data');
    const existingIds = new Set(entries.map(e => e.id));
    let added = 0;
    for (const entry of importedEntries) {
      if (!existingIds.has(entry.id)) {
        entries.push(entry);
        added++;
      }
    }
    await saveVault();
    _notify('change');
    return { added, skipped: importedEntries.length - added };
  }

  // ── Auto-Lock ─────────────────────────────────────────────────────────────
  function _startAutoLock() {
    autoLockRemaining = LOCK_TIMEOUT;
    if (autoLockInterval) clearInterval(autoLockInterval);
    autoLockInterval = setInterval(() => {
      autoLockRemaining--;
      _notify('autoLockTick', autoLockRemaining);
      if (autoLockRemaining <= 0) {
        lockVault();
      }
    }, 1000);
  }

  function _stopAutoLock() {
    if (autoLockInterval) clearInterval(autoLockInterval);
    autoLockInterval = null;
    autoLockRemaining = LOCK_TIMEOUT;
  }

  function resetAutoLock() {
    if (masterEquation) {
      autoLockRemaining = LOCK_TIMEOUT;
    }
  }

  function getAutoLockRemaining() {
    return autoLockRemaining;
  }

  // ── Color Helpers ─────────────────────────────────────────────────────────
  const ENTRY_COLORS = [
    '#22d3ee', '#8b5cf6', '#f43f5e', '#f97316', '#eab308',
    '#22c55e', '#06b6d4', '#a855f7', '#ec4899', '#14b8a6',
    '#3b82f6', '#ef4444', '#84cc16', '#d946ef', '#0ea5e9'
  ];

  function entryColor(name) {
    let hash = 0;
    for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
    return ENTRY_COLORS[Math.abs(hash) % ENTRY_COLORS.length];
  }

  function entryInitial(name) {
    return (name || '?')[0].toUpperCase();
  }

  // ── Event System ──────────────────────────────────────────────────────────
  function on(event, callback) {
    if (listeners[event]) listeners[event].push(callback);
  }

  function off(event, callback) {
    if (listeners[event]) {
      listeners[event] = listeners[event].filter(cb => cb !== callback);
    }
  }

  function _notify(event, data) {
    if (listeners[event]) {
      listeners[event].forEach(cb => {
        try { cb(data); } catch (e) { console.error('[VaultClient] listener error:', e); }
      });
    }
  }

  return {
    // Vault lifecycle
    hasStoredVault,
    createVault,
    unlockVault,
    lockVault,
    isUnlocked,
    // Entry CRUD
    getEntries,
    getEntry,
    addEntry,
    updateEntry,
    deleteEntry,
    // Import/Export
    exportBackup,
    importBackup,
    // Server Sync
    setServerCredentials,
    syncToServer,
    syncFromServer,
    // Equation tools
    evaluateEquation,
    equationStrength,
    generateEquation,
    phraseToEquation,
    // Auto-lock
    resetAutoLock,
    getAutoLockRemaining,
    // Color helpers
    entryColor,
    entryInitial,
    ENTRY_COLORS,
    // Events
    on,
    off,
    // Constants
    LOCK_TIMEOUT,
  };
})();
