import { barkFor } from './lines';

/** What the ticket asked for. */
export interface Order {
  name: string;
  layers: string[]; // required kinds, bottom → top (e.g. ['bread','tomato','bread'])
  needsCut: boolean; // did a food need cutting first
  maxSeconds: number; // serve by this or it's late
}

/** Everything measured off one attempt — every number read off the sim. */
export interface RunResult {
  cut?: { cv: number; quality: 'clean' | 'ragged' | 'crime' | 'none'; pieces: number };
  sando: { layers: number; leanDeg: number; comDrift: number; standing: boolean; matches: boolean };
  service: { served: boolean; bowHeld: boolean; seconds: number; correct: boolean };
  floor: { splatMass: number; drops: number };
}

export type Group = 'CUT' | 'SANDO' | 'SERVICE' | 'FLOOR';
export type Grade = 'S' | 'A' | 'B' | 'C' | 'D';

export interface Criterion {
  key: string;
  group: Group;
  label: string;
  score: number; // 0..1
  weight: number;
  detail: string;
}

export interface Verdict {
  criteria: Criterion[];
  total: number; // 0..10
  grade: Grade;
  best: Criterion;
  worst: Criterion;
  lines: { jp: string; en: string }[]; // subtitled barks, worst-first
}

const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);

export function judge(order: Order, r: RunResult): Verdict {
  const criteria: Criterion[] = [];

  // THE CUT — clean, even pieces
  if (order.needsCut) {
    const q = r.cut?.quality ?? 'none';
    const cv = r.cut?.cv ?? 1;
    const qScore = q === 'clean' ? 1 : q === 'ragged' ? 0.55 : q === 'crime' ? 0.1 : 0.15;
    const evenScore = clamp01(1 - (cv - 0.05) / 0.32);
    criteria.push({
      key: 'cut', group: 'CUT', label: 'THE CUT', weight: 1.2,
      score: clamp01(0.6 * qScore + 0.4 * evenScore),
      detail: q === 'none' ? 'never cut' : `${q}, ${r.cut?.pieces ?? 0} pieces, CV ${cv.toFixed(2)}`,
    });
  }

  // THE SANDO — right layers, standing, plumb
  const lean = Math.abs(r.sando.leanDeg);
  const sandoScore =
    (r.sando.matches ? 0.4 : 0.05) +
    (r.sando.standing ? 0.35 : 0) +
    0.25 * clamp01(1 - lean / 45);
  criteria.push({
    key: 'sando', group: 'SANDO', label: 'THE SANDO', weight: 1.4,
    score: clamp01(sandoScore),
    detail: `${r.sando.layers} layers, ${r.sando.matches ? 'as ordered' : 'wrong build'}, lean ${lean.toFixed(0)}°, ${r.sando.standing ? 'standing' : 'collapsed'}`,
  });

  // THE SERVICE — served, correct, on time, bowed
  const late = Math.max(0, r.service.seconds - order.maxSeconds);
  const serviceScore = !r.service.served
    ? 0.05
    : (r.service.correct ? 0.5 : 0.15) + (r.service.bowHeld ? 0.25 : 0) + 0.25 * clamp01(1 - late / 20);
  criteria.push({
    key: 'service', group: 'SERVICE', label: 'THE SERVICE', weight: 1.1,
    score: clamp01(serviceScore),
    detail: !r.service.served
      ? 'never served'
      : `${r.service.correct ? 'correct' : 'wrong dish'}, ${r.service.seconds.toFixed(0)}s${late > 0 ? ` (${late.toFixed(0)}s late)` : ''}, ${r.service.bowHeld ? 'bowed' : 'no bow'}`,
  });

  // THE FLOOR — mess and drops (the bench criterion reborn)
  const floorScore = clamp01(1 - r.floor.splatMass / 6 - r.floor.drops / 4);
  criteria.push({
    key: 'floor', group: 'FLOOR', label: 'THE FLOOR', weight: 0.9,
    score: floorScore,
    detail: `splat ${r.floor.splatMass.toFixed(1)}, ${r.floor.drops} drop${r.floor.drops === 1 ? '' : 's'}`,
  });

  const wsum = criteria.reduce((a, c) => a + c.weight, 0);
  const total = +((criteria.reduce((a, c) => a + c.score * c.weight, 0) / wsum) * 10).toFixed(2);
  const grade: Grade = total >= 9.2 ? 'S' : total >= 8 ? 'A' : total >= 6.5 ? 'B' : total >= 4.5 ? 'C' : 'D';

  const sorted = [...criteria].sort((a, b) => a.score - b.score);
  const worst = sorted[0], best = sorted[sorted.length - 1];

  // barks: an S earns the nod; a real weak spot gets named; an otherwise-clean
  // run gets a grudging good word. Never praise and insult the same plate.
  const lines: { jp: string; en: string }[] = [];
  if (grade === 'S') lines.push({ jp: '…umai.', en: '…good.' });
  if (worst.score < 0.6) lines.push(barkFor(worst, false));
  else if (grade !== 'S' && best.score > 0.75) lines.push(barkFor(best, true));

  return { criteria, total, grade, best, worst, lines };
}
