import Phaser from 'phaser';
import { renderDoll } from '../../../patrons/doll';
import { drunkify } from '../../../ui/typo';
import type { DrunkStage, Patron } from '../../../data/types';
import { judgeEscalation, type EscalationResult } from '../escalation';
import { ESCALATION_LINES, type EscalationLine } from '../../../data/strings/floor';

const W = 640;
const H = 360;

const PANEL_X = 60;
const PANEL_Y = 56;
const PANEL_W = 520;
const PANEL_H = 248;

const ROW_X = 214;
const ROW_W = 348;
const ROW_H = 34;
const ROW_TOP = 126;
const ROW_GAP = 42;

const PINK = 0xd03470;
const AMBER = 0xd8a020;
const PURPLE = 0x7b2fd0;

const OUTCOME_COLOUR: Readonly<Record<EscalationResult['outcome'], string>> = {
  clean: '#9fe8a0',
  scene: '#d03470',
  staggerFree: '#d8a020',
};

/** Softest first — index maps straight to the 1/2/3 keys. */
const ORDER: readonly EscalationLine[] = ['water', 'done', 'escort'];

// What the player can actually SEE across a dark room. The raw intoxication
// float never reaches the screen — you read the person, not a stat.
const LOOK: Readonly<Record<DrunkStage, string>> = {
  sober: 'steady as a rock',
  tipsy: 'talking with her hands',
  loose: 'leaning on things that move',
  messy: 'wearing some of the drink',
  maggot: 'gone. absolutely gone.',
};

const HOLD_MS = 1700;

interface Row {
  box: Phaser.GameObjects.Rectangle;
  text: Phaser.GameObjects.Text;
}

const mono = (size: number, colour: string): Phaser.Types.GameObjects.Text.TextStyle => ({
  fontFamily: 'monospace',
  fontSize: `${size}px`,
  color: colour,
});

/** Modal cut-off dialogue tree. A thin shell over judgeEscalation(). */
export class CutOffOverlay {
  private readonly root: Phaser.GameObjects.Container;
  private readonly doll: Phaser.GameObjects.Image;
  private readonly whoText: Phaser.GameObjects.Text;
  private readonly lookText: Phaser.GameObjects.Text;
  private readonly hintText: Phaser.GameObjects.Text;
  private readonly rows: Row[] = [];
  private readonly saidText: Phaser.GameObjects.Text;
  private readonly replyText: Phaser.GameObjects.Text;

  private open_ = false;
  private drunkenness = 0;
  private drunkRng: { next(): number } | null = null;
  private stage: DrunkStage = 'sober';
  private holdMs = 0;
  private pending: EscalationResult | null = null;
  private done: ((r: EscalationResult | null) => void) | null = null;

  constructor(private readonly scene: Phaser.Scene) {
    this.root = scene.add.container(0, 0).setDepth(5000).setScrollFactor(0).setVisible(false);

    this.root.add(scene.add.rectangle(W / 2, H / 2, W, H, 0x05050a, 0.82));
    this.root.add(
      scene.add
        .rectangle(PANEL_X + PANEL_W / 2, PANEL_Y + PANEL_H / 2, PANEL_W, PANEL_H, 0x0b0b12, 0.98)
        .setStrokeStyle(1, PURPLE),
    );
    this.root.add(scene.add.rectangle(PANEL_X + PANEL_W / 2, PANEL_Y + 1, PANEL_W, 2, PINK, 0.8));
    this.root.add(scene.add.text(PANEL_X + 12, PANEL_Y + 10, 'CUT-OFF', mono(9, '#d03470')));

    // '__DEFAULT' is Phaser's always-present blank texture — a placeholder so
    // every object exists before the first open().
    this.doll = scene.add.image(134, 252, '__DEFAULT').setOrigin(0.5, 1).setScale(2);
    this.root.add(this.doll);

    this.whoText = scene.add.text(134, 258, '', mono(8, '#9fe8a0')).setOrigin(0.5, 0);
    this.lookText = scene.add
      .text(134, 270, '', mono(7, '#8a7a96'))
      .setOrigin(0.5, 0)
      .setAlign('center')
      .setWordWrapWidth(120);
    this.root.add(this.whoText);
    this.root.add(this.lookText);

    ORDER.forEach((line, i) => {
      const y = ROW_TOP + i * ROW_GAP;
      const box = scene.add
        .rectangle(ROW_X + ROW_W / 2, y, ROW_W, ROW_H, 0x14141f)
        .setStrokeStyle(1, 0x2a2a3a)
        .setInteractive({ useHandCursor: true });
      const text = scene.add
        .text(ROW_X + 10, y, `${i + 1}   ${ESCALATION_LINES[line]}`, mono(8, '#cfc6d8'))
        .setOrigin(0, 0.5);

      box.on('pointerover', () => box.setStrokeStyle(1, AMBER));
      box.on('pointerout', () => box.setStrokeStyle(1, 0x2a2a3a));
      box.on('pointerup', () => this.pick(line));

      this.root.add(box);
      this.root.add(text);
      this.rows.push({ box, text });
    });

    this.saidText = scene.add
      .text(ROW_X, 136, '', mono(9, '#cfc6d8'))
      .setOrigin(0, 0)
      .setWordWrapWidth(ROW_W);
    this.replyText = scene.add
      .text(ROW_X, 176, '', mono(8, '#9fe8a0'))
      .setOrigin(0, 0)
      .setLineSpacing(3)
      .setWordWrapWidth(ROW_W);
    this.root.add(this.saidText);
    this.root.add(this.replyText);

    this.hintText = scene.add
      .text(W / 2, PANEL_Y + PANEL_H - 16, '1/2/3 choose · ESC back off', mono(7, '#5a5a70'))
      .setOrigin(0.5, 0);
    this.root.add(this.hintText);
  }

  get isOpen(): boolean {
    return this.open_;
  }

  open(args: { patron: Patron; stage: DrunkStage }, done: (r: EscalationResult | null) => void): void {
    this.done = done;
    this.stage = args.stage;
    this.pending = null;
    this.holdMs = 0;
    this.open_ = true;

    this.doll.setTexture(renderDoll(this.scene, args.patron, 'queue'));
    // Their words wear their blood-alcohol. Deterministic per patron: a tiny
    // LCG off dollSeed, so the same bloke slurs the same way every run.
    this.drunkenness = args.stage === 'maggot' ? 0.8 : args.stage === 'messy' ? 0.55 : 0.3;
    let lcg = (Math.abs(args.patron.dollSeed) % 2147483646) + 1;
    this.drunkRng = { next: () => (lcg = (lcg * 48271) % 2147483647) / 2147483647 };
    this.whoText.setText(args.patron.archetype);
    this.lookText.setText(LOOK[args.stage]);
    this.showChoices(true);
    this.root.setVisible(true);
  }

  update(deltaMs: number): void {
    if (!this.open_ || this.holdMs <= 0) return;
    this.holdMs -= deltaMs;
    if (this.holdMs > 0) return;
    const result = this.pending;
    this.hide();
    this.resolve(result);
  }

  handleKey(key: string): void {
    if (!this.open_ || this.pending) return;
    const k = key.toUpperCase();
    if (k === 'ESC' || k === 'ESCAPE') {
      this.hide();
      this.resolve(null);
      return;
    }
    const line = ORDER[Number(k) - 1];
    if (line) this.pick(line);
  }

  destroy(): void {
    this.open_ = false;
    this.done = null;
    this.root.destroy();
  }

  private pick(line: EscalationLine): void {
    if (!this.open_ || this.pending) return;
    const result = judgeEscalation(this.stage, line);
    this.pending = result;
    this.holdMs = HOLD_MS;

    this.showChoices(false);
    this.saidText.setText(
      `"${this.drunkRng ? drunkify(result.line, { drunkenness: this.drunkenness, rng: this.drunkRng }) : result.line}"`,
    );
    this.replyText.setText(result.reply).setColor(OUTCOME_COLOUR[result.outcome]);
  }

  private showChoices(on: boolean): void {
    for (const r of this.rows) {
      r.box.setVisible(on).setStrokeStyle(1, 0x2a2a3a);
      r.text.setVisible(on);
    }
    this.hintText.setVisible(on);
    this.saidText.setVisible(!on);
    this.replyText.setVisible(!on);
  }

  private hide(): void {
    this.open_ = false;
    this.pending = null;
    this.holdMs = 0;
    this.root.setVisible(false);
  }

  /** done() fires exactly once per open(), always after hide(). */
  private resolve(result: EscalationResult | null): void {
    const cb = this.done;
    this.done = null;
    cb?.(result);
  }
}
