/** Small deterministic RNG (mulberry32) — seeded so a run can be replayed. */
export class Rng {
  private s: number;
  constructor(seed = 1) {
    this.s = seed >>> 0;
  }
  next(): number {
    this.s = (this.s + 0x6d2b79f5) >>> 0;
    let t = this.s;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  }
  range(a: number, b: number): number {
    return a + this.next() * (b - a);
  }
  int(a: number, b: number): number {
    return Math.floor(this.range(a, b + 1));
  }
  pick<T>(arr: readonly T[]): T {
    return arr[Math.floor(this.next() * arr.length)];
  }
  chance(p: number): boolean {
    return this.next() < p;
  }
}

/** Cheap 2D value noise, used to give each toasting run its own character. */
export function valueNoise2D(rng: Rng, w: number, h: number, octaves = 3): Float32Array {
  const out = new Float32Array(w * h);
  let amp = 1;
  let total = 0;
  for (let o = 0; o < octaves; o++) {
    const cells = 2 << o;
    const grid = new Float32Array((cells + 1) * (cells + 1));
    for (let i = 0; i < grid.length; i++) grid[i] = rng.next();
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const fx = (x / (w - 1)) * cells;
        const fy = (y / (h - 1)) * cells;
        const x0 = Math.floor(fx);
        const y0 = Math.floor(fy);
        const tx = fx - x0;
        const ty = fy - y0;
        const sx = tx * tx * (3 - 2 * tx);
        const sy = ty * ty * (3 - 2 * ty);
        // Clamp: at x === w-1, fx lands exactly on `cells`, so the x0+1 lookup
        // runs one past the end of the grid. A typed array returns undefined
        // there, which silently turns the whole field into NaN.
        const g = (gx: number, gy: number) =>
          grid[Math.min(gy, cells) * (cells + 1) + Math.min(gx, cells)];
        const a = g(x0, y0) * (1 - sx) + g(x0 + 1, y0) * sx;
        const b = g(x0, y0 + 1) * (1 - sx) + g(x0 + 1, y0 + 1) * sx;
        out[y * w + x] += (a * (1 - sy) + b * sy) * amp;
      }
    }
    total += amp;
    amp *= 0.5;
  }
  for (let i = 0; i < out.length; i++) out[i] /= total;
  return out;
}
