/**
 * Privacy Web Worker — PBKDF2-SHA256 key derivation + HMAC verification.
 *
 * All cryptographic operations run off the main thread (RD-1). The passphrase
 * is handled here and discarded immediately after use; no key material leaves
 * this worker.
 *
 * Keep these constants in sync with lib/privacy-defaults.js:
 */
const PRIVACY_HMAC_LABEL = 'madarx-privacy-v1'; // PRIVACY_HMAC_LABEL
const PRIVACY_PBKDF2_ITERATIONS = 600000;        // PRIVACY_PBKDF2_ITERATIONS

// ---------------------------------------------------------------------------
// base64url helpers (no external deps)
// ---------------------------------------------------------------------------

function toBase64Url(buf) {
  let s = '';
  const bytes = new Uint8Array(buf);
  for (let i = 0; i < bytes.length; i += 1) s += String.fromCharCode(bytes[i]);
  return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function fromBase64Url(str) {
  const padded = str.replace(/-/g, '+').replace(/_/g, '/');
  const pad = (4 - (padded.length % 4)) % 4;
  const b64 = padded + '='.repeat(pad);
  const bin = atob(b64);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
  return out.buffer;
}

// ---------------------------------------------------------------------------
// Core crypto helpers
// ---------------------------------------------------------------------------

async function deriveBits(passphrase, salt, iterations) {
  const enc = new TextEncoder();
  const keyMaterial = await globalThis.crypto.subtle.importKey(
    'raw',
    enc.encode(passphrase),
    'PBKDF2',
    false,
    ['deriveBits'],
  );
  return globalThis.crypto.subtle.deriveBits(
    { name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },
    keyMaterial,
    256,
  );
}

async function computeHmac(keyBits, label) {
  const enc = new TextEncoder();
  const hmacKey = await globalThis.crypto.subtle.importKey(
    'raw',
    keyBits,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const sig = await globalThis.crypto.subtle.sign('HMAC', hmacKey, enc.encode(label));
  return sig;
}

// ---------------------------------------------------------------------------
// Message handler
// ---------------------------------------------------------------------------

self.onmessage = async (e) => {
  const { id, op, passphrase, salt, iter, mac } = e.data || {};

  try {
    if (op === 'setup') {
      // Generate a fresh random salt, derive key, compute verification HMAC.
      const saltBytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
      const iterations = iter ?? PRIVACY_PBKDF2_ITERATIONS;
      const keyBits = await deriveBits(passphrase, saltBytes.buffer, iterations);
      const hmacBuf = await computeHmac(keyBits, PRIVACY_HMAC_LABEL);

      self.postMessage({
        id,
        ok: true,
        salt_b64: toBase64Url(saltBytes.buffer),
        hmac_b64: toBase64Url(hmacBuf),
      });
      return;
    }

    if (op === 'verify') {
      // Derive key from provided salt, compute HMAC, compare to stored token.
      const saltBuf = fromBase64Url(salt);
      const iterations = iter ?? PRIVACY_PBKDF2_ITERATIONS;
      const keyBits = await deriveBits(passphrase, saltBuf, iterations);
      const hmacBuf = await computeHmac(keyBits, PRIVACY_HMAC_LABEL);
      const computed = toBase64Url(hmacBuf);

      // Constant-time-ish compare: same length check + char xor.
      let match = computed.length === (mac || '').length;
      if (match) {
        let diff = 0;
        for (let i = 0; i < computed.length; i += 1) {
          diff |= computed.charCodeAt(i) ^ mac.charCodeAt(i);
        }
        match = diff === 0;
      }

      self.postMessage({ id, ok: true, match });
      return;
    }

    self.postMessage({ id, ok: false, error: `Unknown op: ${op}` });
  } catch (err) {
    self.postMessage({ id, ok: false, error: err?.message || String(err) });
  }
};
