/**
 * # crypto/sha256 — the one portable, synchronous sha256 the runtime hashes with (kestrel-alw.17)
 *
 * Content-addressing is woven through the whole grade path as **pure, synchronous** functions:
 * {@link ../grade/receipt.ts createReceipt}, {@link ../blotter/project.ts project}, the {@link ../ledger
 * ledger}'s `deriveRunId`, and the sim driver's bus hash all take a sha256 of a canonical string and must
 * return it inline — no `await`. Bun's `Bun.CryptoHasher` gives exactly that (a synchronous `.digest`), but
 * it is a **Bun-only** global: it does not exist in a Cloudflare Worker isolate, which is where the grade
 * path must also run (ADR-0006, EPIC kestrel-markets-alw). WebCrypto (`crypto.subtle.digest`) IS present in
 * a Worker but is **async**, and threading `await` through every content-address would break the
 * pure-function doctrine the determinism check relies on (RUNTIME §0).
 *
 * The resolution of that sync-vs-async tension is this module: ONE synchronous `sha256(text)` that is
 * portable across environments. It uses Bun's native hasher when the `Bun` global is present (the fast path
 * under `bun test` / the CLI) and a self-contained pure-JS SHA-256 otherwise (the Worker isolate). Both code
 * paths produce **byte-identical** output — a determinism guarantee locked down by a parity test that runs
 * BOTH implementations over the same inputs ({@link sha256Pure} is exported so the Worker path is directly
 * testable even under Bun). No wall clock, no RNG.
 */

/** The narrow shape of Bun's native `CryptoHasher` this module uses: a fluent `update` (returns the
 * hasher so `.update(…).digest(…)` chains) and a hex `digest`. */
interface BunCryptoHasher {
  update(input: string): BunCryptoHasher;
  digest(enc: "hex"): string;
}

/** Bun's native `CryptoHasher` when the `Bun` global exists (fast path); `undefined` in a Worker isolate. */
const BUN_CRYPTO_HASHER: undefined | (new (algo: string) => BunCryptoHasher) =
  (globalThis as { Bun?: { CryptoHasher?: new (algo: string) => BunCryptoHasher } }).Bun?.CryptoHasher;

// ─────────────────────────────────────────────────────────────────────────────
// Pure-JS SHA-256 (the Worker-portable path)
// ─────────────────────────────────────────────────────────────────────────────

/** The SHA-256 round constants (first 32 bits of the fractional parts of the cube roots of the first 64
 * primes) — FIPS 180-4. */
const K = new Uint32Array([
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
]);

/** 32-bit right rotate. */
function rotr(x: number, n: number): number {
  return (x >>> n) | (x << (32 - n));
}

/**
 * Pure-JS SHA-256 over the UTF-8 bytes of `text` → a 64-char lowercase hex digest (FIPS 180-4). This is the
 * Worker-portable path (no `Bun` global, no async `crypto.subtle`). Exported so the parity test can exercise
 * it directly even under Bun, proving it matches the native hasher byte-for-byte.
 */
export function sha256Pure(text: string): string {
  const msg = new TextEncoder().encode(text);
  const bitLen = msg.length * 8;

  // Pad: append 0x80, then zeros up to 56 mod 64, then the 64-bit big-endian bit length.
  const withPad = new Uint8Array(((msg.length + 8) >> 6) * 64 + 64);
  withPad.set(msg);
  withPad[msg.length] = 0x80;
  // 64-bit length; JS bit ops are 32-bit, so write the two 32-bit halves.
  const hi = Math.floor(bitLen / 0x100000000);
  const lo = bitLen >>> 0;
  const lenOff = withPad.length - 8;
  withPad[lenOff] = (hi >>> 24) & 0xff;
  withPad[lenOff + 1] = (hi >>> 16) & 0xff;
  withPad[lenOff + 2] = (hi >>> 8) & 0xff;
  withPad[lenOff + 3] = hi & 0xff;
  withPad[lenOff + 4] = (lo >>> 24) & 0xff;
  withPad[lenOff + 5] = (lo >>> 16) & 0xff;
  withPad[lenOff + 6] = (lo >>> 8) & 0xff;
  withPad[lenOff + 7] = lo & 0xff;

  // Initial hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes).
  let h0 = 0x6a09e667;
  let h1 = 0xbb67ae85;
  let h2 = 0x3c6ef372;
  let h3 = 0xa54ff53a;
  let h4 = 0x510e527f;
  let h5 = 0x9b05688c;
  let h6 = 0x1f83d9ab;
  let h7 = 0x5be0cd19;

  const w = new Uint32Array(64);
  for (let off = 0; off < withPad.length; off += 64) {
    for (let i = 0; i < 16; i++) {
      const j = off + i * 4;
      w[i] = ((withPad[j]! << 24) | (withPad[j + 1]! << 16) | (withPad[j + 2]! << 8) | withPad[j + 3]!) >>> 0;
    }
    for (let i = 16; i < 64; i++) {
      const s0 = rotr(w[i - 15]!, 7) ^ rotr(w[i - 15]!, 18) ^ (w[i - 15]! >>> 3);
      const s1 = rotr(w[i - 2]!, 17) ^ rotr(w[i - 2]!, 19) ^ (w[i - 2]! >>> 10);
      w[i] = (w[i - 16]! + s0 + w[i - 7]! + s1) >>> 0;
    }

    let a = h0;
    let b = h1;
    let c = h2;
    let d = h3;
    let e = h4;
    let f = h5;
    let g = h6;
    let h = h7;

    for (let i = 0; i < 64; i++) {
      const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
      const ch = (e & f) ^ (~e & g);
      const t1 = (h + S1 + ch + K[i]! + w[i]!) >>> 0;
      const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
      const maj = (a & b) ^ (a & c) ^ (b & c);
      const t2 = (S0 + maj) >>> 0;
      h = g;
      g = f;
      f = e;
      e = (d + t1) >>> 0;
      d = c;
      c = b;
      b = a;
      a = (t1 + t2) >>> 0;
    }

    h0 = (h0 + a) >>> 0;
    h1 = (h1 + b) >>> 0;
    h2 = (h2 + c) >>> 0;
    h3 = (h3 + d) >>> 0;
    h4 = (h4 + e) >>> 0;
    h5 = (h5 + f) >>> 0;
    h6 = (h6 + g) >>> 0;
    h7 = (h7 + h) >>> 0;
  }

  return (
    hex32(h0) + hex32(h1) + hex32(h2) + hex32(h3) + hex32(h4) + hex32(h5) + hex32(h6) + hex32(h7)
  );
}

/** A 32-bit word as 8 lowercase hex chars. */
function hex32(x: number): string {
  return (x >>> 0).toString(16).padStart(8, "0");
}

// ─────────────────────────────────────────────────────────────────────────────
// The one portable sha256 the runtime hashes with
// ─────────────────────────────────────────────────────────────────────────────

/**
 * The portable, synchronous sha256 of a UTF-8 string → 64-char lowercase hex. Uses Bun's native
 * `CryptoHasher` when the `Bun` global is present (fast path under `bun test` / the CLI) and the pure-JS
 * {@link sha256Pure} otherwise (a Cloudflare Worker isolate). Both paths are byte-identical (parity test).
 * Deterministic — no wall clock, no RNG.
 */
export function sha256(text: string): string {
  if (BUN_CRYPTO_HASHER !== undefined) {
    return new BUN_CRYPTO_HASHER("sha256").update(text).digest("hex");
  }
  return sha256Pure(text);
}
