import type { App } from '../core/app';
import { Rng } from '../core/rng';
import { KitchenView } from '../scenes/kitchen';
import { JudgeView } from '../scenes/judge';
import { DrawerView } from '../scenes/drawer';
import { TOOLS, type ToolId } from '../sim/cutlery';
import { audio } from '../core/audio';
import { coolStep } from '../sim/toasting';
import { SPREADS } from '../sim/spreads';
import { judge, type Grade } from './judging';
import { DAYS, proceduralOrder, nameBrowning, type Order } from './orders';
import { el, Panel } from '../ui/hud';
import { Title } from '../ui/title';

const SAVE_KEY = 'toastsim.save';

interface Save {
  day: number;
  total: number;
  best: Record<string, number>;
}

/**
 * The arcade loop: an order, a slice, a verdict, the next order. The day counter
 * is the whole progression — everything else is the orders getting nastier.
 */
export class Game {
  private kitchen: KitchenView;
  private judgeView: JudgeView;
  private drawer: DrawerView;
  private rng = new Rng(20260716);
  private order!: Order;
  private startedAt = 0;
  private save: Save;
  private ticket: Panel;
  private ticketEl!: HTMLElement;

  constructor(private app: App) {
    this.save = loadSave();
    this.kitchen = new KitchenView(app, 3);
    this.judgeView = new JudgeView(app);
    this.drawer = new DrawerView(app, 7);
    app.scene.add(this.kitchen.root);
    app.scene.add(this.judgeView.root);
    app.scene.add(this.drawer.root);
    this.judgeView.root.visible = false;
    this.drawer.root.visible = false;

    this.ticket = new Panel('ticket');
    this.ticketEl = el('div', 'ticket-body', this.ticket.root);

    // toast -> drawer -> spread. The drawer sits AFTER the toaster on purpose:
    // its cost is your toast going cold, and cold toast is what makes butter
    // impossible. Dawdling here is the punishment.
    this.kitchen.onPopped = () => this.openDrawer();
    this.kitchen.onServed = () => this.serve();
    this.drawer.onPicked = (tool) => this.closeDrawer(tool);
    this.judgeView.onNext = () => {
      this.save.day++;
      writeSave(this.save);
      this.beginDay();
    };

    // Cooling runs at the app level, not in the kitchen's update: the kitchen
    // stops updating the moment the drawer opens, and "your toast goes cold
    // while you rummage" is the entire reason the drawer exists.
    app.onTick((dt) => {
      const k = this.kitchen;
      if (k.phase === 'ready' || k.phase === 'toasting') return;
      coolStep(k.currentSlice, dt);
      this.drawer.warmth = k.currentSlice.warmth;
    });

    // Sit on the title until the player clicks — which also gives us the user
    // gesture the browser wants before any audio is allowed to make a sound.
    this.kitchen.root.visible = false;
    this.ticket.hide();
    new Title(this.save.day, () => {
      audio.resume();
      this.beginDay();
    });
  }

  get currentOrder(): Order {
    return this.order;
  }
  get kitchenView(): KitchenView {
    return this.kitchen;
  }
  get judgeScreen(): JudgeView {
    return this.judgeView;
  }
  get drawerView(): DrawerView {
    return this.drawer;
  }

  async init(): Promise<void> {
    await this.drawer.init();
  }

  /** Fresh out of the toaster, and now you need something to spread with. */
  private openDrawer(): void {
    this.drawer.reset(this.order.tool);
    this.drawer.warmth = this.kitchen.currentSlice.warmth;
    this.kitchen.root.visible = false;
    this.drawer.root.visible = true;
    this.app.setView(this.drawer);
    this.ticket.hide();
  }

  private closeDrawer(tool: ToolId): void {
    this.kitchen.setTool(tool);
    this.drawer.root.visible = false;
    this.kitchen.root.visible = true;
    this.app.setView(this.kitchen);
    this.ticket.show();
    this.kitchen.beginSpreading();
    if (tool !== this.order.tool) {
      this.kitchen.flash(`you brought a ${TOOLS[tool].name.toLowerCase()}`);
    }
  }

  beginDay(): void {
    const day = this.save.day;
    const o =
      day <= DAYS.length
        ? DAYS[day - 1]
        : proceduralOrder(day, () => this.rng.next());
    if (!o.text) o.text = `${o.browningName}, ${o.amount} ${o.spread === 'mitey' ? 'MITEY' : o.spread}. ${o.noChar ? 'No burnt bits.' : ''}`;
    this.order = o;
    this.startedAt = performance.now();

    const s = this.judgeView.release();
    if (s) this.kitchen.root.add(s.mesh);

    this.kitchen.applyOrder(o);
    this.drawer.root.visible = false;
    this.app.setView(this.kitchen);
    this.judgeView.root.visible = false;
    this.kitchen.root.visible = true;
    this.ticket.show();
    this.renderTicket();
  }

  private renderTicket(): void {
    const o = this.order;
    this.ticketEl.innerHTML = '';
    el('div', 'ticket-day', this.ticketEl, `DAY ${o.day}`);
    el('div', 'ticket-who', this.ticketEl, o.who);
    el('div', 'ticket-text', this.ticketEl, `“${o.text}”`);
    const spec = el('div', 'ticket-spec', this.ticketEl);
    el('span', 'chip', spec, o.browningName);
    el('span', 'chip', spec, `${o.amount} ${o.spread === 'mitey' ? 'MITEY' : o.spread}`);
    if (o.noChar) el('span', 'chip warn', spec, 'no burnt bits');
    el('span', 'chip tool', spec, `use the ${o.tool.replace(/_/g, ' ')}`);
    el(
      'div',
      'ticket-hint',
      this.ticketEl,
      `butter is ${o.butterSoftness > 0.6 ? 'soft' : o.butterSoftness > 0.3 ? 'firm' : 'fridge-hard'} today`,
    );
    if (SPREADS[o.spread].separates) {
      el('div', 'ticket-hint', this.ticketEl, 'the jar has been sitting — stir it before you dip');
    }
  }

  /** Called when the player hands the plate over. */
  serve(): void {
    const seconds = (performance.now() - this.startedAt) / 1000;
    const slice = this.kitchen.currentSlice;
    const v = judge(slice, this.order, this.kitchen.toolId, seconds);
    this.save.total += v.total;
    const key = `day${this.order.day}`;
    this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total);
    writeSave(this.save);

    this.kitchen.root.visible = false;
    this.judgeView.root.visible = true;
    this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next());
    audio.stamp();
    this.app.setView(this.judgeView);
    this.ticket.hide();
  }

  /** Test hook. */
  gradeNow(): { total: number; grade: Grade } {
    const v = judge(this.kitchen.currentSlice, this.order, this.kitchen.toolId, 60);
    return { total: v.total, grade: v.grade };
  }
}

function loadSave(): Save {
  try {
    const raw = localStorage.getItem(SAVE_KEY);
    if (raw) {
      const s = JSON.parse(raw) as Partial<Save>;
      if (typeof s.day === 'number' && s.day >= 1) {
        return { day: s.day, total: s.total ?? 0, best: s.best ?? {} };
      }
    }
  } catch {
    /* a corrupt save is not worth a crash */
  }
  return { day: 1, total: 0, best: {} };
}

function writeSave(s: Save): void {
  try {
    localStorage.setItem(SAVE_KEY, JSON.stringify(s));
  } catch {
    /* private mode; the day counter just won't persist */
  }
}

export { nameBrowning };
