/**
 * Transient overlays composited ON the feed (in screen space, so corruption never drags
 * them): the shipment stamp and the scram bar.
 *
 * Both are painted into small canvases and uploaded only when their event fires — never
 * per frame. The stamp is small (256x128) precisely so the per-event upload stays cheap.
 */

export const STAMP_W = 256;
export const STAMP_H = 128;
export const SCRAM_W = 512;
export const SCRAM_H = 64;
export const CARD_W = 640;
export const CARD_H = 360;
/** The repair placard is small on purpose — it's a signature, not a broadcast. */
export const REPAIR_W = 224;
export const REPAIR_H = 112;

/**
 * The eras as the codex names them (§1). The card announces the STANDARD, not the tech id —
 * The Correction does not care what you unlocked, only which format it just blessed.
 */
const ERA_STANDARD: Record<string, string> = {
  reel: 'OPTICAL FILM',
  broadcast: 'NTSC / PAL ANALOG',
  disc: 'MPEG-2 / DVD',
  stream: 'H.264 / HEVC / AV1',
  fortress: 'DCP',
};

export function eraStandard(era: string): string {
  return ERA_STANDARD[era] ?? era.toUpperCase();
}

export interface Overlays {
  stampCanvas: HTMLCanvasElement;
  scramCanvas: HTMLCanvasElement;
  cardCanvas: HTMLCanvasElement;
  titleCanvas: HTMLCanvasElement;
  repairCanvas: HTMLCanvasElement;
  /** Repaint the stamp for a landed shipment. OSHA-placard deadpan, in the item's chip colour. */
  paintStamp(name: string, count: number, color: string): void;
  /** Repaint the ratification card. The Correction's uniform: hospital white, green tick. */
  paintCard(era: string, tech: string): void;
  /** Paint the boot title card. Alpha channel matters — it dissolves over the feed. */
  paintTitle(): void;
  /**
   * Repaint the repair placard — The Correction signing one unit of your work off the ledger.
   * Small, perfect (no crooked stamp), hospital white with the faction green tick: deliberately
   * the opposite of the hand-thumped, chip-coloured shipment stamp. Rot vs theft, made legible.
   */
  paintRepair(name: string): void;
}

function roundRect(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) {
  g.beginPath();
  g.moveTo(x + r, y);
  g.arcTo(x + w, y, x + w, y + h, r);
  g.arcTo(x + w, y + h, x, y + h, r);
  g.arcTo(x, y + h, x, y, r);
  g.arcTo(x, y, x + w, y, r);
  g.closePath();
}

function paintScramBar(g: CanvasRenderingContext2D) {
  // A white tape-tracking bar with the deadpan legend punched out of it.
  g.clearRect(0, 0, SCRAM_W, SCRAM_H);
  g.fillStyle = '#f6f6f2';
  g.fillRect(0, 0, SCRAM_W, SCRAM_H);
  // torn edges: the bar should not look like a clean rectangle
  g.fillStyle = 'rgba(0,0,0,0)';
  g.globalCompositeOperation = 'destination-out';
  for (let x = 0; x < SCRAM_W; x += 7) {
    const t = ((x * 37) % 11) / 11;
    g.fillRect(x, 0, 7, 3 + t * 5);
    g.fillRect(x, SCRAM_H - (3 + (1 - t) * 5), 7, 3 + (1 - t) * 5);
  }
  g.globalCompositeOperation = 'source-over';
  g.fillStyle = '#0a0a0c';
  g.font = "bold 30px 'Courier New', monospace";
  g.textBaseline = 'middle';
  const label = 'TRACKING LOST';
  const w = g.measureText(label).width;
  g.fillText(label, (SCRAM_W - w) / 2, SCRAM_H / 2);
}

export function createOverlays(): Overlays {
  const stampCanvas = document.createElement('canvas');
  stampCanvas.width = STAMP_W;
  stampCanvas.height = STAMP_H;
  const sg = stampCanvas.getContext('2d')!;

  const scramCanvas = document.createElement('canvas');
  scramCanvas.width = SCRAM_W;
  scramCanvas.height = SCRAM_H;
  paintScramBar(scramCanvas.getContext('2d')!);

  const cardCanvas = document.createElement('canvas');
  cardCanvas.width = CARD_W;
  cardCanvas.height = CARD_H;
  const cg = cardCanvas.getContext('2d')!;

  const titleCanvas = document.createElement('canvas');
  titleCanvas.width = CARD_W;
  titleCanvas.height = CARD_H;
  const tg = titleCanvas.getContext('2d')!;

  const repairCanvas = document.createElement('canvas');
  repairCanvas.width = REPAIR_W;
  repairCanvas.height = REPAIR_H;
  const rg = repairCanvas.getContext('2d')!;

  return {
    stampCanvas,
    scramCanvas,
    cardCanvas,
    titleCanvas,
    repairCanvas,

    paintTitle() {
      // The game finally says its own name. A CRT burn-in: chunky phosphor letters with a
      // hot-magenta / cyan chromatic ghost, over transparent black so it can dissolve to the
      // NOTHING IS WRONG feed. Alpha is what the shader mixes on.
      tg.clearRect(0, 0, CARD_W, CARD_H);
      tg.fillStyle = 'rgba(3,2,8,0.92)';
      tg.fillRect(0, 0, CARD_W, CARD_H);

      const cx = CARD_W / 2;
      tg.textAlign = 'center';
      tg.textBaseline = 'middle';
      tg.font = "bold 58px 'Courier New', monospace";
      // chromatic aberration ghosts (the prototype's signature look)
      tg.fillStyle = '#3fffe0';
      tg.fillText('WIMVEE', cx - 3, 150);
      tg.fillStyle = '#ff2222';
      tg.fillText('WIMVEE', cx + 3, 150);
      tg.fillStyle = '#f0ffe8';
      tg.fillText('WIMVEE', cx, 150);
      tg.fillStyle = '#3fffe0';
      tg.fillText('FKTRY', cx - 3, 214);
      tg.fillStyle = '#ff2222';
      tg.fillText('FKTRY', cx + 3, 214);
      tg.fillStyle = '#ff3fd4';
      tg.fillText('FKTRY', cx, 214);

      tg.fillStyle = '#4a6b4a';
      tg.font = "14px 'Courier New', monospace";
      tg.fillText('A DECODER DAEMON PRODUCTION', cx, 268);
      tg.font = "11px 'Courier New', monospace";
      tg.fillText('PLEASE STAND BY', cx, 300);

      // faint scanlines baked in for the burn-in feel
      tg.fillStyle = 'rgba(0,0,0,0.22)';
      for (let y = 0; y < CARD_H; y += 3) tg.fillRect(0, y, CARD_W, 1);
      tg.textAlign = 'left'; // restore default for the other painters
    },

    paintCard(era: string, tech: string) {
      // The Correction wins the sky for one second: hospital white, green checkmark, and the
      // absolute confidence of a compliance notice. Nothing here is corrupted — that's the joke.
      cg.fillStyle = '#f2f5f2';
      cg.fillRect(0, 0, CARD_W, CARD_H);

      cg.strokeStyle = '#cfd8cf';
      cg.lineWidth = 2;
      cg.strokeRect(24, 24, CARD_W - 48, CARD_H - 48);

      cg.textBaseline = 'middle';
      cg.fillStyle = '#12160f';
      cg.font = "bold 40px 'Courier New', monospace";
      const title = 'STANDARD RATIFIED';
      cg.fillText(title, (CARD_W - cg.measureText(title).width) / 2, 132);

      const standard = eraStandard(era);
      cg.fillStyle = '#2f7d3f';
      cg.font = "bold 28px 'Courier New', monospace";
      cg.fillText(standard, (CARD_W - cg.measureText(standard).width) / 2, 186);

      // green tick — The Correction's faction uniform (codex §8)
      cg.strokeStyle = '#2f7d3f';
      cg.lineWidth = 7;
      cg.lineCap = 'round';
      cg.lineJoin = 'round';
      cg.beginPath();
      cg.moveTo(CARD_W / 2 - 26, 236);
      cg.lineTo(CARD_W / 2 - 8, 254);
      cg.lineTo(CARD_W / 2 + 28, 218);
      cg.stroke();

      cg.fillStyle = '#5c6b5c';
      cg.font = "13px 'Courier New', monospace";
      const sub = 'CONFORMANCE VERIFIED · NO ACTION REQUIRED';
      cg.fillText(sub, (CARD_W - cg.measureText(sub).width) / 2, 292);
      cg.font = "11px 'Courier New', monospace";
      cg.fillText(tech.toUpperCase().replace(/-/g, ' '), 40, 322);
      const auth = 'THE CORRECTION';
      cg.fillText(auth, CARD_W - 40 - cg.measureText(auth).width, 322);
    },
    paintStamp(name: string, count: number, color: string) {
      sg.clearRect(0, 0, STAMP_W, STAMP_H);
      sg.save();
      // Stamped by hand, badly: a few degrees off true.
      sg.translate(STAMP_W / 2, STAMP_H / 2);
      sg.rotate(-0.045);
      sg.translate(-STAMP_W / 2, -STAMP_H / 2);

      // dark backing so the legend survives on top of white SMPTE bars
      sg.fillStyle = 'rgba(6,6,10,0.72)';
      roundRect(sg, 12, 22, STAMP_W - 24, STAMP_H - 44, 6);
      sg.fill();

      sg.strokeStyle = color;
      sg.lineWidth = 5;
      roundRect(sg, 12, 22, STAMP_W - 24, STAMP_H - 44, 6);
      sg.stroke();

      sg.fillStyle = color;
      sg.textBaseline = 'middle';
      // Long codex names have to fit the placard — shrink until they do.
      let size = 22;
      sg.font = `bold ${size}px 'Courier New', monospace`;
      while (sg.measureText(name).width > STAMP_W - 48 && size > 9) {
        size -= 1;
        sg.font = `bold ${size}px 'Courier New', monospace`;
      }
      const nw = sg.measureText(name).width;
      sg.fillText(name, (STAMP_W - nw) / 2, STAMP_H / 2 - 12);

      const qty = `×${count}`;
      sg.font = "bold 26px 'Courier New', monospace";
      const qw = sg.measureText(qty).width;
      sg.fillText(qty, (STAMP_W - qw) / 2, STAMP_H / 2 + 20);

      sg.fillStyle = 'rgba(255,255,255,0.5)';
      sg.font = "9px 'Courier New', monospace";
      sg.fillText('SHIPPED / NO QUESTIONS', 22, 34);
      sg.restore();
    },
    paintRepair(name: string) {
      // The Correction's uniform (codex §8): hospital white, green tick, and the placid
      // confidence of a compliance stamp. NOT rotated — where the shipment stamp is thumped
      // on by a tired hand, this is machined on square. That contrast is the whole read.
      rg.clearRect(0, 0, REPAIR_W, REPAIR_H);

      // clean white placard with a thin green rule
      rg.fillStyle = '#f2f5f2';
      roundRect(rg, 6, 6, REPAIR_W - 12, REPAIR_H - 12, 5);
      rg.fill();
      rg.strokeStyle = '#2f7d3f';
      rg.lineWidth = 3;
      roundRect(rg, 6, 6, REPAIR_W - 12, REPAIR_H - 12, 5);
      rg.stroke();

      // the green tick — the faction signature
      rg.strokeStyle = '#2f7d3f';
      rg.lineWidth = 5;
      rg.lineCap = 'round';
      rg.lineJoin = 'round';
      rg.beginPath();
      rg.moveTo(26, 40);
      rg.lineTo(38, 52);
      rg.lineTo(60, 26);
      rg.stroke();

      rg.textBaseline = 'middle';
      rg.fillStyle = '#12160f';
      rg.font = "bold 20px 'Courier New', monospace";
      rg.fillText('CORRECTED', 74, 40);

      // the item that was quietly reverted — shrink long codex names to fit the placard
      rg.fillStyle = '#5c6b5c';
      let size = 15;
      rg.font = `${size}px 'Courier New', monospace`;
      while (rg.measureText(name).width > REPAIR_W - 40 && size > 8) {
        size -= 1;
        rg.font = `${size}px 'Courier New', monospace`;
      }
      rg.fillText(name, 26, 74);

      rg.fillStyle = '#8a998a';
      rg.font = "9px 'Courier New', monospace";
      rg.fillText('THE CORRECTION · NO ACTION REQUIRED', 26, 94);
    },
  };
}
