import type { Slice } from './slice';
import { Rng } from '../core/rng';

/**
 * Browning rate. Measured, not guessed: the heat map's mean is ~0.77 (coils,
 * edge falloff and the cool top all bite), so this is set so power 6 reaches a
 * golden 0.5 in ~10s, power 10 is nearly black by then, and power 3 takes ~20s.
 */
const RATE = 0.11;

/** Where the elements actually put their heat. */
export function buildHeatMap(slice: Slice, rng: Rng): Float32Array {
  const n = slice.mask.n;
  const out = new Float32Array(n * n);
  // Real toasters have vertical element wires spaced across the slot, so the
  // hot spots are stripes, not a wash. Jitter the phase per run.
  const coils = 4 + rng.int(0, 2);
  const phase = rng.range(0, Math.PI * 2);
  for (let y = 0; y < n; y++) {
    for (let x = 0; x < n; x++) {
      const u = x / (n - 1);
      const v = y / (n - 1);
      // Subtle: at much more than this the slice reads as a grill plate, not toast.
      const coil = 0.92 + 0.08 * Math.sin(u * Math.PI * 2 * coils + phase);
      // The top of the slice sticks up out of the slot and stays pale — the
      // single most recognisable thing about real toast.
      const vert = v > 0.72 ? 1 - 0.5 * Math.pow((v - 0.72) / 0.28, 1.5) : 1;
      // and the very bottom sits on the rack, slightly shaded
      const base = v < 0.06 ? 0.82 + (v / 0.06) * 0.18 : 1;
      // heat escapes at the left/right edges
      const edge = 0.82 + 0.18 * Math.sin(Math.min(1, Math.max(0, u)) * Math.PI);
      const bias = 0.88 + slice.heatBias[y * n + x] * 0.24;
      out[y * n + x] = coil * vert * base * edge * bias;
    }
  }
  return out;
}

export interface ToastTick {
  /** Mean browning over the slice — drives the smell cues. */
  mean: number;
  /** How much of the slice is past char. */
  charFrac: number;
  /** 0..1, how smoky it is right now. */
  smoke: number;
}

/**
 * Advance the browning one tick.
 *
 * Two things make this more than a timer:
 *  - moisture has to boil off before the surface can brown at full rate, so a wet
 *    crumb stalls and then accelerates once it's dry (sourdough's whole trick);
 *  - sugar browns early and burns early (raisin bread's whole trick).
 */
export function toastStep(slice: Slice, heat: Float32Array, power: number, dt: number): void {
  const b = slice.bread;
  const p = Math.max(0, Math.min(10, power)) / 10;
  const moistureCap = 0.4 + b.moisture * 2.2;
  const thick = 0.6 + b.thickness * 3;
  const sugar = 1 + b.sugar * 0.8;
  const br = slice.browning.data;
  const dry = slice.dryness.data;
  const mask = slice.mask.data;

  for (let i = 0; i < br.length; i++) {
    if (mask[i] < 0.5) continue;
    const h = heat[i] * p;
    if (h <= 0) continue;
    dry[i] = Math.min(1, dry[i] + (h * dt) / moistureCap);
    const rate = (RATE * h * (0.25 + 0.75 * dry[i]) * sugar) / thick;
    br[i] = Math.min(1.15, br[i] + rate * dt);
  }
  slice.touch();
}

/** Warmth decays in real time. Thick slices hold heat longer. */
export function coolStep(slice: Slice, dt: number): void {
  const tau = 18 + slice.bread.thickness * 90;
  slice.warmth *= Math.exp(-dt / tau);
  slice.material.uniforms.uWarmth.value = slice.warmth;
}

export function readToast(slice: Slice): ToastTick {
  const s = slice.browning.stats(slice.mask);
  const charFrac = slice.browning.fraction(slice.mask, (v) => v > 0.85);
  const smoke = Math.max(0, Math.min(1, (s.max - 0.78) / 0.3));
  return { mean: s.mean, charFrac, smoke };
}

/**
 * No timer, no numbers — you go by smell. Deliberately vague until it isn't.
 */
export function smellCue(t: ToastTick): string {
  if (t.smoke > 0.75) return 'SMOKE. ACTUAL SMOKE.';
  if (t.smoke > 0.45) return 'something is burning';
  if (t.mean > 0.62) return 'dark, and getting darker';
  if (t.mean > 0.42) return 'smells like toast';
  if (t.mean > 0.22) return 'smells toasty';
  if (t.mean > 0.08) return 'smells warm';
  if (t.mean > 0.01) return 'a faint warmth';
  return 'smells like bread';
}
