import Phaser from 'phaser';
import { installHarness } from '../dev';
import { Marionette } from '../sim/marionette';
import { Cutter } from '../sim/cutting';
import { spawnFood } from '../sim/food';
import { Sando } from '../sim/stack';
import { Game } from '../game/game';
import type { Verdict } from '../game/judging';
import { sfx } from '../game/audio';

const PORTRAITS = ['samurai_calm', 'samurai_watch', 'samurai_annoyed', 'samurai_angry', 'samurai_fury'];

export const STAGE_W = 1280;
export const STAGE_H = 720;
export const FLOOR_Y = 640;

/** A physics body paired with the flat black silhouette that renders it.
 *  This is the whole game's render contract: the body is truth, the shape follows. */
export interface Prop {
  body: MatterJS.BodyType;
  view: Phaser.GameObjects.Shape | Phaser.GameObjects.Image | Phaser.GameObjects.Container;
}

/**
 * The kitchen stage. Matter's auto-step is OFF so the sim advances only when we
 * call stepSim — deterministic, drivable frame-by-frame from the dev harness.
 * In normal play scene.update maps the pointer to the bar and steps once; the
 * harness sets auto=false and drives the puppet itself.
 */
export class Stage extends Phaser.Scene {
  auto = true;
  props: Prop[] = [];
  puppet!: Marionette;
  cutter!: Cutter;
  sando!: Sando;
  session!: Game;
  crate!: MatterJS.BodyType;
  cleaver!: MatterJS.BodyType;
  board!: MatterJS.BodyType;
  plate!: MatterJS.BodyType;
  private orderText?: Phaser.GameObjects.Text;
  private space!: Phaser.Input.Keyboard.Key;
  private samuraiImg?: Phaser.GameObjects.Image;
  private angerBar?: Phaser.GameObjects.Graphics;
  private strings?: Phaser.GameObjects.Graphics;
  private vignette?: Phaser.GameObjects.Rectangle;
  private scorecard?: Phaser.GameObjects.Container;

  constructor() {
    super('stage');
  }

  preload(): void {
    // BASE_URL is '/' in dev, '/sandoniette/' in the built subpath deploy — so
    // asset paths resolve regardless of a trailing slash on the URL.
    const base = import.meta.env.BASE_URL;
    const img = (k: string) => this.load.image(k, `${base}assets/img/${k}.png`);
    ['bg_washi', 'title_art', ...PORTRAITS].forEach(img);
  }

  create(): void {
    this.matter.world.autoUpdate = false;

    // washi backdrop (generated art; the game never blocks on it — a plain fill
    // shows if it failed to load).
    if (this.textures.exists('bg_washi')) {
      this.add.image(STAGE_W / 2, STAGE_H / 2, 'bg_washi').setDisplaySize(STAGE_W, STAGE_H).setDepth(-10);
    }

    // floor + side walls
    this.addBody(this.matter.add.rectangle(STAGE_W / 2, FLOOR_Y + 20, STAGE_W, 40, { isStatic: true }), STAGE_W, 40, 0x2a211a);
    this.matter.add.rectangle(-20, STAGE_H / 2, 40, STAGE_H * 2, { isStatic: true });
    this.matter.add.rectangle(STAGE_W + 20, STAGE_H / 2, 40, STAGE_H * 2, { isStatic: true });

    this.puppet = new Marionette(this, 400);

    // a crate on the floor for the M1 grip test — low mass so a hand can lift it.
    this.crate = this.matter.add.rectangle(470, FLOOR_Y - 16, 48, 48, { restitution: 0.1, friction: 0.8, density: 0.0006 });
    this.addBody(this.crate, 48, 48, 0x3a2d20);
    this.puppet.gripTargets.push(this.crate);

    // cutting board (a static counter) + a food resting on it for M2.
    this.board = this.matter.add.rectangle(760, 560, 240, 24, { isStatic: true });
    this.addBody(this.board, 240, 24, 0x2a211a);
    this.cutter = new Cutter(this);
    this.cutter.add(spawnFood(this, 'tomato', 760, 512));

    // the cleaver — a grabbable blade. Held in a hand, its edge cuts on the swing.
    this.cleaver = this.matter.add.rectangle(632, FLOOR_Y - 42, 12, 78, { density: 0.0008, friction: 0.6 });
    this.addBody(this.cleaver, 12, 78, 0x151515);
    this.puppet.gripTargets.push(this.cleaver);

    // the plate zone (a static dish on the floor) where the sando gets stacked.
    this.plate = this.matter.add.rectangle(980, 630, 140, 12, { isStatic: true });
    this.addBody(this.plate, 140, 12, 0x2a211a);
    this.sando = new Sando(this, 980, 624);

    this.session = new Game(this);

    // HUD: the samurai judge (top-right), the anger meter, the unsheath vignette.
    if (this.textures.exists('samurai_calm')) {
      this.samuraiImg = this.add.image(STAGE_W - 150, 210, 'samurai_calm').setScale(0.42).setDepth(5);
    }
    this.angerBar = this.add.graphics().setDepth(6);
    this.strings = this.add.graphics().setDepth(3); // strings + control bar, over the silhouettes
    this.vignette = this.add.rectangle(STAGE_W / 2, STAGE_H / 2, STAGE_W, STAGE_H, 0x1a0000, 0).setDepth(4);

    // order ticket (top-left)
    this.orderText = this.add.text(28, 34, '', { fontFamily: 'monospace', fontSize: '16px', color: '#2a1a0e' }).setDepth(6);
    this.refreshOrder();

    this.space = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
    this.input.once('pointerdown', () => sfx.enable()); // unblock audio on first click
    installHarness(this);

    // title over the top; dismissed on click (the harness can skip it).
    this.scene.launch('title');
  }

  /** SHUBATTO — the string cut. Ragdoll the puppet, fling every loose food with
   *  the slash impulse, flash the slash, sound the gong. Deterministic launch
   *  (direction from each body's side) so the harness reproduces it. */
  stringCut(): void {
    this.puppet.cutStrings();
    const loose = [...this.cutter.foods.map((f) => f.body), ...this.sando.layers.map((l) => l.body)];
    for (const b of loose) {
      const dir = b.position.x < STAGE_W / 2 ? -1 : 1;
      this.matter.body.setVelocity(b, { x: dir * 7, y: -9 });
      this.matter.body.setAngularVelocity(b, dir * 0.3);
    }
    this.slashFx();
    sfx.slash();
    sfx.gong();
  }

  private slashFx(): void {
    const line = this.add.rectangle(STAGE_W / 2, STAGE_H / 2, STAGE_W * 1.5, 7, 0xffffff).setDepth(20).setRotation(-0.5);
    this.tweens.add({ targets: line, alpha: 0, duration: 450, onComplete: () => line.destroy() });
  }

  /** The scorecard over the wreckage: grade, the four groups, the bark. */
  showScorecard(v: Verdict): Phaser.GameObjects.Container {
    this.scorecard?.destroy();
    const g = this.add.container(0, 0).setDepth(30);
    const cx = STAGE_W / 2;
    g.add(this.add.rectangle(cx, 360, 640, 440, 0x120d08, 0.95).setStrokeStyle(3, 0x8a6a2a));
    g.add(this.add.text(cx, 190, v.grade, { fontFamily: 'serif', fontSize: '72px', color: '#efe4cb' }).setOrigin(0.5));
    let y = 262;
    for (const c of v.criteria) {
      const stars = '★'.repeat(Math.round(c.score * 5)).padEnd(5, '·');
      g.add(this.add.text(cx - 280, y, `${c.label.padEnd(12)} ${stars}  ${c.detail}`, { fontFamily: 'monospace', fontSize: '14px', color: '#d8c8a8' }));
      y += 30;
    }
    y += 12;
    for (const l of v.lines) {
      g.add(this.add.text(cx, y, l.jp, { fontFamily: 'serif', fontSize: '18px', color: '#e8b84a' }).setOrigin(0.5));
      y += 24;
      g.add(this.add.text(cx, y, l.en, { fontFamily: 'serif', fontSize: '14px', color: '#9a8a6a', fontStyle: 'italic' }).setOrigin(0.5));
      y += 28;
    }
    this.scorecard = g;
    return g;
  }

  clearScorecard(): void {
    this.scorecard?.destroy();
    this.scorecard = undefined;
  }

  /** Redraw the strings from the control bar to the puppet, plus the bar. */
  private drawStrings(): void {
    const g = this.strings;
    if (!g) return;
    g.clear();
    const segs = this.puppet.stringSegments();
    if (!segs.length) return; // cut — no strings to draw
    g.lineStyle(1.5, 0x1a1206, 0.55);
    for (const s of segs) {
      g.beginPath();
      g.moveTo(s.ax, s.ay);
      g.lineTo(s.bx, s.by);
      g.strokePath();
    }
    // the wooden control bar
    const b = this.puppet.barSegment();
    g.lineStyle(7, 0x3a2612, 0.95);
    g.beginPath();
    g.moveTo(b.x1, b.y1);
    g.lineTo(b.x2, b.y2);
    g.strokePath();
  }

  /** Refresh the order ticket from the current day. */
  refreshOrder(): void {
    const o = this.session.order;
    this.orderText?.setText(`DAY ${this.session.day + 1}   ${o.name}\n${o.layers.join('  ·  ')}`);
  }

  /** World position of the cleaver's cutting edge (its lower tip). */
  cleaverTip(): { x: number; y: number } {
    const a = this.cleaver.angle, half = 39;
    return { x: this.cleaver.position.x - Math.sin(a) * half, y: this.cleaver.position.y + Math.cos(a) * half };
  }

  /** A gripped cleaver is the live blade; otherwise the blade parks. Called each
   *  frame in play, and by the harness after its own steps. */
  driveCleaver(): void {
    if (this.puppet.held().includes(this.cleaver)) this.cutter.moveBlade(this.cleaverTip());
    else this.cutter.resetBlade(this.cleaverTip());
  }

  private updateHUD(): void {
    const sam = this.session.samurai;
    if (this.samuraiImg && this.textures.exists(sam.portrait)) this.samuraiImg.setTexture(sam.portrait);
    const bar = this.angerBar;
    if (bar) {
      bar.clear();
      bar.fillStyle(0x2a211a, 0.6).fillRect(STAGE_W - 270, 40, 224, 18);
      bar.fillStyle(sam.anger < 0.5 ? 0xc8a24a : 0x9a2020, 1).fillRect(STAGE_W - 268, 42, 220 * sam.anger, 14);
    }
    this.vignette?.setFillStyle(0x1a0000, sam.vignette * 0.42);
  }

  /** Register a body with a flat rectangular silhouette that tracks it. */
  addBody(body: MatterJS.BodyType, w: number, h: number, color: number): MatterJS.BodyType {
    const view = this.add.rectangle(body.position.x, body.position.y, w, h, color);
    this.props.push({ body, view });
    return body;
  }

  /** Register a body with a circular silhouette (the head). */
  addCircle(body: MatterJS.BodyType, r: number, color: number): MatterJS.BodyType {
    const view = this.add.circle(body.position.x, body.position.y, r, color);
    this.props.push({ body, view });
    return body;
  }

  /** Register a body with a polygon silhouette (food, food pieces).
   *  localVerts are centred on the body origin. */
  addPoly(body: MatterJS.BodyType, localVerts: { x: number; y: number }[], color: number): MatterJS.BodyType {
    const view = this.add.polygon(body.position.x, body.position.y, localVerts, color);
    this.props.push({ body, view });
    return body;
  }

  /** A wet-food splat decal — a dark blob left on the stage (no body). */
  splat(x: number, y: number, r: number, color: number): void {
    const blob = this.add.ellipse(x, y, r * 2.2, r * 1.3, color, 0.85);
    blob.setDepth(-1);
    sfx.splat();
  }

  /** Punch a comical cut-out face into a food silhouette — two eyes + a worried
   *  little mouth, in the bright washi tone so they read as backlit holes. */
  addFace(body: MatterJS.BodyType, w: number, h: number): void {
    const hole = 0xf4e8c4;
    const r = Math.max(2.8, Math.min(w, h) * 0.11);
    const ex = Math.min(w * 0.2, 18), ey = -h * 0.1;
    const c = this.add.container(body.position.x, body.position.y).setDepth(1);
    c.add(this.add.circle(-ex, ey, r, hole));
    c.add(this.add.circle(ex, ey, r, hole));
    c.add(this.add.circle(0, h * 0.18, r * 0.8, hole)); // small "o" of alarm
    this.props.push({ body, view: c });
  }

  /** Destroy every view tracking a body (silhouette + any face) and remove it. */
  removeProp(body: MatterJS.BodyType): void {
    this.props = this.props.filter((p) => {
      if (p.body !== body) return true;
      p.view.destroy();
      return false;
    });
    this.matter.world.remove(body);
  }

  stepSim(dt = 1000 / 60): void {
    this.matter.world.step(dt);
    this.puppet?.update();
  }

  update(): void {
    if (this.auto) {
      const p = this.input.activePointer;
      const grip = p.rightButtonDown() || this.space.isDown;
      this.puppet.applyInput(p.worldX, p.worldY || this.puppet.barHomeY, p.leftButtonDown(), grip);
      this.stepSim();
      this.driveCleaver(); // a gripped cleaver is the live blade — swinging cuts
    }
    for (const pr of this.props) {
      pr.view.setPosition(pr.body.position.x, pr.body.position.y);
      pr.view.setRotation(pr.body.angle);
    }
    this.drawStrings();
    this.updateHUD();
  }
}
