/**
 * Spread rheology. The whole difficulty curve of this game lives in these numbers.
 *
 * The central tension: you need PRESSURE to make a stiff spread flow, but pressure
 * comes from tilting the knife steeper — and past `SCRAPE_ANGLE` a steep knife stops
 * spreading and starts scraping. So cold butter can want more pressure than you are
 * physically allowed to apply without flipping into scrape mode. The way out isn't a
 * better technique, it's warmer toast: don't dawdle in the drawer.
 */

export type SpreadId = 'butter' | 'peanut' | 'crunchy' | 'mitey' | 'marmalade';
export type AmountClass = 'thin' | 'normal' | 'thick';

/**
 * Solid bits suspended in a spread — marmalade rind. They ride the knife with
 * the jelly, drop off as the blade travels, and the judge scores how evenly
 * they ended up scattered. Dabbing makes a pile; a moving knife lays a trail;
 * covering the whole slice takes several dips placed with intent.
 */
export interface ParticleDef {
  /** What the judge calls them. */
  name: string;
  color: [number, number, number];
  /** World-space size of a bit. */
  size: number;
  /** How many the knife picks up per dip. */
  perDip: number;
  /**
   * Fraction of the carried bits shed per WORLD-UNIT of stroke, at a full load.
   * Distance-based on purpose: a moving knife lays an even trail, a dabbing one
   * makes a pile.
   */
  shedRate: number;
}

export interface SpreadDef {
  id: SpreadId;
  name: string;
  blurb: string;
  color: [number, number, number];
  gloss: number;
  /** Thickness at which the film fully hides the toast underneath. */
  opaqueAt: number;
  /** Surface relief strength. */
  bump: number;
  /** Suspended solids, if any. */
  particles?: ParticleDef;
  /**
   * Natural nut butters stratify: how badly the jar has separated when the
   * order starts (0..1). The oil rises, the paste below dries out — the first
   * unstirred dip is a slick and the ones after it are grout. Swirl the knife
   * in the jar to mix it back; the fix costs time, and time costs toast warmth.
   */
  separates?: number;

  /** Pressure needed to make it flow, before any warming. */
  baseYield: number;
  /** How much heat (soft butter + hot toast) reduces that. */
  tempSoftening: number;
  /** 0 self-levels instantly, 1 stays exactly where you put it. */
  viscosity: number;
  /** How much the knife picks up per dip. */
  pickup: number;
  /** How fast it leaves the knife once it IS flowing. */
  transferRate: number;
  /** How much the spread resists the knife (drag on the cursor). */
  drag: number;
  /** How readily it comes back off when you scrape. */
  scrapeEase: number;

  amounts: Record<AmountClass, [number, number]>;
}

export const SPREADS: Record<SpreadId, SpreadDef> = {
  butter: {
    id: 'butter',
    name: 'Butter',
    blurb: 'Straightforward — as long as something in this scene is warm.',
    color: [0.97, 0.72, 0.19],
    gloss: 0.85,
    opaqueAt: 0.5,
    bump: 0.10,
    baseYield: 0.72,
    tempSoftening: 0.78,
    viscosity: 0.72,
    pickup: 0.55,
    transferRate: 3.0,
    drag: 0.5,
    scrapeEase: 1.0,
    amounts: { thin: [0.1, 0.22], normal: [0.25, 0.5], thick: [0.55, 0.9] },
  },
  peanut: {
    id: 'peanut',
    name: 'Smooth Peanut Butter',
    blurb: 'Goes on thick, drags like wet sand — and the jar has been sitting.',
    color: [0.64, 0.41, 0.18],
    gloss: 0.3,
    opaqueAt: 0.16,
    bump: 0.28,
    separates: 0.75,
    baseYield: 0.38,
    tempSoftening: 0.3,
    viscosity: 0.96,
    pickup: 0.95,
    transferRate: 4.5,
    drag: 1.0,
    scrapeEase: 0.75,
    amounts: { thin: [0.18, 0.32], normal: [0.38, 0.68], thick: [0.75, 1.1] },
  },
  crunchy: {
    id: 'crunchy',
    name: 'Crunchy Peanut Butter',
    blurb: 'Everything smooth is, plus chunks with opinions about where they sit.',
    color: [0.6, 0.38, 0.16],
    gloss: 0.28,
    opaqueAt: 0.17,
    bump: 0.32,
    // Chunks sink; the jar splits worse than the smooth stuff.
    separates: 0.9,
    particles: {
      name: 'peanut chunks',
      color: [0.78, 0.6, 0.34],
      size: 0.042,
      perDip: 8,
      shedRate: 0.85,
    },
    baseYield: 0.42,
    tempSoftening: 0.3,
    viscosity: 0.97,
    pickup: 0.95,
    transferRate: 4.3,
    drag: 1.1,
    scrapeEase: 0.7,
    amounts: { thin: [0.18, 0.32], normal: [0.38, 0.68], thick: [0.75, 1.1] },
  },
  mitey: {
    id: 'mitey',
    name: 'MITEY',
    blurb: 'Spreads like nothing. The skill is taking almost all of it back off.',
    color: [0.14, 0.08, 0.05],
    gloss: 0.62,
    opaqueAt: 0.26,
    bump: 0.03,
    baseYield: 0.14,
    tempSoftening: 0.5,
    viscosity: 0.34,
    pickup: 0.5,
    transferRate: 3.5,
    drag: 0.25,
    scrapeEase: 1.25,
    amounts: { thin: [0.03, 0.11], normal: [0.14, 0.28], thick: [0.32, 0.6] },
  },
  marmalade: {
    id: 'marmalade',
    name: 'Marmalade',
    blurb: 'The jelly is easy. The rind goes where it wants — your job is to argue.',
    color: [0.91, 0.45, 0.08],
    gloss: 0.95,
    // Jelly stays translucent even laid on thick — you always see toast through it.
    opaqueAt: 0.85,
    bump: 0.05,
    particles: {
      name: 'rind',
      color: [0.62, 0.24, 0.04],
      size: 0.055,
      perDip: 9,
      // ~0.12 world-units between bits at a full load — and wider as the blade
      // empties, so one dip can't do the whole slice. Several dips, placed with
      // intent, is the skill.
      shedRate: 0.95,
    },
    baseYield: 0.2,
    tempSoftening: 0.45,
    viscosity: 0.55,
    pickup: 0.62,
    transferRate: 3.2,
    drag: 0.35,
    scrapeEase: 1.1,
    amounts: { thin: [0.08, 0.18], normal: [0.22, 0.42], thick: [0.48, 0.8] },
  },
};

/** Knife angle past which contact narrows enough to scrape instead of spread. */
export const SCRAPE_ANGLE = 0.62;

/** Angle (0 flat .. 1 on-edge) -> contact pressure. */
export function pressureFromAngle(angle: number): number {
  return 0.1 + 0.9 * Math.pow(Math.max(0, Math.min(1, angle)), 1.3);
}

/** Angle -> contact patch radius in UV units. Flat knife = wide, steep = a line. */
export function contactRadius(angle: number): number {
  return 0.115 - 0.078 * Math.max(0, Math.min(1, angle));
}

/**
 * What it actually takes to make this spread flow right now. Butter on a cold
 * slice with fridge-hard butter is the worst case in the game, by design.
 */
export function effectiveYield(def: SpreadDef, softness: number, toastWarmth: number): number {
  const heat = Math.min(1, softness * 0.55 + toastWarmth * 0.65);
  return def.baseYield * (1 - def.tempSoftening * heat);
}

export function amountClassOf(def: SpreadDef, mean: number): AmountClass | 'none' | 'obscene' {
  if (mean < def.amounts.thin[0] * 0.5) return 'none';
  if (mean <= def.amounts.thin[1]) return 'thin';
  if (mean <= def.amounts.normal[1]) return 'normal';
  if (mean <= def.amounts.thick[1]) return 'thick';
  return 'obscene';
}
