/**
 * T1 — isomorphic crypto provider: entropy + constant-time compare + WebCrypto handle.
 *
 * Uses ONLY the WHATWG `globalThis.crypto` surface (present in browsers and Node >=20), so the
 * package stays isomorphic with zero `node:crypto` import. `timingSafeEqual` is hand-rolled
 * (no `Buffer`) so it runs unchanged in the browser bundle.
 */
import { CryptoUnavailableError } from './errors.js'

function globalCrypto(): Crypto {
  const c = (globalThis as { crypto?: Crypto }).crypto
  if (!c || typeof c.getRandomValues !== 'function') {
    throw new CryptoUnavailableError('globalThis.crypto.getRandomValues is unavailable')
  }
  return c
}

/** Return the WebCrypto SubtleCrypto (browser `crypto.subtle` / Node `webcrypto.subtle`). */
export function getWebCrypto(): SubtleCrypto {
  const subtle = globalCrypto().subtle
  if (!subtle) {
    throw new CryptoUnavailableError('WebCrypto SubtleCrypto is unavailable')
  }
  return subtle
}

/** CSPRNG bytes via `crypto.getRandomValues`. */
export function randomBytes(len: number): Uint8Array {
  if (!Number.isInteger(len) || len < 0) {
    throw new CryptoUnavailableError(`randomBytes length must be a non-negative integer; got ${len}`)
  }
  const out = new Uint8Array(len)
  globalCrypto().getRandomValues(out)
  return out
}

/**
 * Constant-time, length-safe byte equality. Returns `false` on any length mismatch WITHOUT a
 * content-dependent early exit: both branches XOR-accumulate over a fixed number of iterations.
 */
export function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {
  const lenDiff = a.length ^ b.length
  const n = Math.max(a.length, b.length)
  let acc = lenDiff
  for (let i = 0; i < n; i++) {
    const av = i < a.length ? a[i]! : 0
    const bv = i < b.length ? b[i]! : 0
    acc |= av ^ bv
  }
  return acc === 0
}
