/**
 * Seeded randomness. The engine never calls Math.random: every random choice (question picks,
 * category order, Second Chance removals, bot answers) derives from a string seed so a replay of
 * the same commands reproduces the same game. Only 32-bit integer ops (Math.imul, >>>) are used,
 * which behave identically on every JS engine and platform.
 */

/** cyrb128 (public-domain hash by bryc): string → four well-mixed uint32 words. */
function cyrb128(str: string): [number, number, number, number] {
  let h1 = 1779033703;
  let h2 = 3144134277;
  let h3 = 1013904242;
  let h4 = 2773480762;
  for (let i = 0; i < str.length; i++) {
    const k = str.charCodeAt(i);
    h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
    h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
    h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
    h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
  }
  h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
  h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
  h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
  h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
  h1 ^= h2 ^ h3 ^ h4;
  h2 ^= h1;
  h3 ^= h1;
  h4 ^= h1;
  return [h1 >>> 0, h2 >>> 0, h3 >>> 0, h4 >>> 0];
}

/** Stable non-negative 32-bit hash of a string. */
export function hashString(s: string): number {
  return cyrb128(s)[0];
}

/** sfc32 PRNG seeded from cyrb128(seed). Returns floats in [0, 1). */
export function createRng(seed: string | number): () => number {
  let [a, b, c, d] = cyrb128(String(seed));
  const next = (): number => {
    a |= 0;
    b |= 0;
    c |= 0;
    d |= 0;
    const t = (((a + b) | 0) + d) | 0;
    d = (d + 1) | 0;
    a = b ^ (b >>> 9);
    b = (c + (c << 3)) | 0;
    c = (c << 21) | (c >>> 11);
    c = (c + t) | 0;
    return (t >>> 0) / 4294967296;
  };
  // Discard the first outputs: sfc32 needs a few rounds to decorrelate similar seeds.
  for (let i = 0; i < 12; i++) next();
  return next;
}

/** Integer in [0, maxExclusive). */
export function randomInt(rng: () => number, maxExclusive: number): number {
  return Math.floor(rng() * maxExclusive);
}

/** Fisher–Yates shuffle; returns a new array and leaves the input untouched. */
export function shuffle<T>(arr: readonly T[], rng: () => number): T[] {
  const out = arr.slice();
  for (let i = out.length - 1; i > 0; i--) {
    const j = randomInt(rng, i + 1);
    const tmp = out[i] as T;
    out[i] = out[j] as T;
    out[j] = tmp;
  }
  return out;
}

/** One element chosen uniformly. Throws on an empty array (a caller bug, never a game state). */
export function pick<T>(arr: readonly T[], rng: () => number): T {
  if (arr.length === 0) throw new Error('pick() called with an empty array');
  return arr[randomInt(rng, arr.length)] as T;
}
