import type { Stage } from '../scenes/stage';
import { box, Food, FOODS, makeFood } from './food';

/**
 * The sando. Ordered layers carried onto a plate zone. Stability is honest
 * Matter stacking — a tower falls because its centre of mass left the base, not
 * because a script said so — but every layer is SCORED: lean, COM drift,
 * overhang. The order ticket (M5) names the target; here it's just layers.
 */

interface LayerDef {
  w: number;
  h: number;
  color: number;
  face: string;
}

const L = (kind: string, w: number, h: number): LayerDef => ({
  w, h, color: FOODS[kind].color, face: FOODS[kind].face,
});

export const LAYERS: Record<string, LayerDef> = {
  bread: L('bread', 94, 24),
  tomato: L('tomato', 78, 18),
  cheese: L('cheese', 82, 16),
  katsu: L('katsu', 88, 22),
  egg: L('egg', 70, 20),
};

export interface SandoStats {
  layers: number;
  leanDeg: number; // tilt of the stack axis from vertical
  comDrift: number; // px the worst layer sits off the plate centre
  overhang: number; // px the worst layer juts past the one below its support
  standing: boolean;
}

const deg = (r: number): number => +((r * 180) / Math.PI).toFixed(1);
const halfW = (b: MatterJS.BodyType): number => (b.bounds.max.x - b.bounds.min.x) / 2;

export class Sando {
  layers: Food[] = [];

  constructor(private stage: Stage, public plateX = 980, public plateTopY = 634) {}

  /** Top surface of the current stack (or the plate if empty). */
  private topY(): number {
    if (!this.layers.length) return this.plateTopY;
    return Math.min(...this.layers.map((f) => f.body.bounds.min.y));
  }

  /** Place one layer on top, offset offsetX from the plate centre, then settle. */
  addLayer(kind: string, offsetX = 0, settleFrames = 45): Food {
    const def = LAYERS[kind] ?? LAYERS.bread;
    const mat = FOODS[kind]?.material ?? FOODS.bread.material;
    const y = this.topY() - def.h / 2 - 1;
    const food = makeFood(this.stage, box(def.w, def.h), this.plateX + offsetX, y, mat, def.color, def.face, kind);
    this.layers.push(food);
    this.stage.auto = false;
    for (let i = 0; i < settleFrames; i++) this.stage.stepSim();
    return food;
  }

  clear(): void {
    for (const f of this.layers) this.stage.removeProp(f.body);
    this.layers = [];
  }

  stats(): SandoStats {
    const n = this.layers.length;
    if (n === 0) return { layers: 0, leanDeg: 0, comDrift: 0, overhang: 0, standing: false };
    const bottom = this.layers[0].body, top = this.layers[n - 1].body;
    const leanDeg = deg(Math.atan2(top.position.x - bottom.position.x, Math.max(1, bottom.position.y - top.position.y)));
    let comDrift = 0, overhang = 0, stackH = 0;
    for (let i = 0; i < n; i++) {
      const b = this.layers[i].body;
      comDrift = Math.max(comDrift, Math.abs(b.position.x - this.plateX));
      stackH += b.bounds.max.y - b.bounds.min.y;
      if (i > 0) {
        const below = this.layers[i - 1].body;
        const off = Math.abs(b.position.x - below.position.x);
        overhang = Math.max(overhang, off - Math.max(0, halfW(below) - halfW(b)));
      }
    }
    // Standing = the tower still has its height: a collapse drops the top layer.
    // A tall sando that survives keeps the top near stackH above the plate; a
    // toppled one spills and the top sinks toward the plate. Lean is reported,
    // not a pass/fail — a stepped-but-intact sando still stands.
    const elevation = this.plateTopY - top.position.y;
    const standing = elevation > 0.45 * stackH;
    return { layers: n, leanDeg, comDrift: +comDrift.toFixed(1), overhang: +overhang.toFixed(1), standing };
  }

  /** The earthquake: shove every layer sideways (px/step). A centred stack rides
   *  it out; one already leaning past its base goes over. */
  nudge(force: number): void {
    const b = this.stage.matter.body;
    for (const f of this.layers) {
      b.setVelocity(f.body, { x: f.body.velocity.x + force, y: f.body.velocity.y });
    }
  }
}
