// The pat-down minigame's pure half: where the pockets are, what's in them,
// and what it costs you. Phaser-free so the scoring stays testable.

import type { RngStream } from '../../core/SeededRNG';
import { CONTRABAND } from '../../data/contraband';
import type { Patron } from '../../data/types';
import { PATDOWN_CLEAR, PATDOWN_FOUND, PATDOWN_MISSED } from '../../data/strings/floor';

export const PATDOWN_MS = 10_000;
export const POCKET_COUNT = 6;

export interface Pocket {
  id: string;
  label: string;
  /** Position on a 64x96 patron-outline overlay coordinate space, top-left origin. */
  x: number;
  y: number;
  itemId?: string;
  revealed: boolean;
  binned: boolean;
}

/**
 * Fixed anchors down the outline — chest, jacket, hips, back, sock. Spaced so no
 * two hit areas touch at a ~12px footprint; the search is about time pressure,
 * not pixel-hunting.
 */
const POCKET_SPOTS: ReadonlyArray<{ readonly label: string; readonly x: number; readonly y: number }> = [
  { label: 'inside pocket', x: 26, y: 22 },
  { label: 'left jacket', x: 10, y: 30 },
  { label: 'right jacket', x: 40, y: 30 },
  { label: 'front hip', x: 12, y: 52 },
  { label: 'back pocket', x: 42, y: 54 },
  { label: 'sock', x: 20, y: 80 },
];

const SEVERITY_BY_ID = new Map(CONTRABAND.map((c) => [c.id, c.severity] as const));
const NAME_BY_ID = new Map(CONTRABAND.map((c) => [c.id, c.name] as const));

/** Deterministic: same patron + same rng stream state => same layout. */
export function layoutPockets(patron: Patron, rng: RngStream): Pocket[] {
  const pockets: Pocket[] = POCKET_SPOTS.map((spot, i) => ({
    id: `pk${i}`,
    label: spot.label,
    x: spot.x,
    y: spot.y,
    revealed: false,
    binned: false,
  }));

  // Fisher-Yates over slot indices, then fill from the front: distinct pockets
  // for free, and anything past POCKET_COUNT simply doesn't fit on them.
  const order = pockets.map((_, i) => i);
  for (let i = order.length - 1; i > 0; i--) {
    const j = rng.int(0, i);
    const a = order[i]!;
    order[i] = order[j]!;
    order[j] = a;
  }

  const items = patron.flags.contraband ?? [];
  const placeable = Math.min(items.length, POCKET_COUNT);
  for (let i = 0; i < placeable; i++) {
    pockets[order[i]!]!.itemId = items[i]!;
  }
  return pockets;
}

export interface PatDownResult {
  foundIds: string[];
  missedIds: string[];
  vibeDelta: number;
  aggroDelta: number;
  /** Severity-3 contraband found => they are out. */
  eject: boolean;
  /** Player-facing summary line, built from strings.ts. */
  summary: string;
}

// Scores the pockets as laid out, not the patron's flags — what you didn't open,
// you didn't find, even if the generator put it there.
export function scorePatDown(_patron: Patron, pockets: readonly Pocket[]): PatDownResult {
  const foundIds: string[] = [];
  const missedIds: string[] = [];
  for (const p of pockets) {
    if (p.itemId === undefined) continue;
    (p.binned ? foundIds : missedIds).push(p.itemId);
  }

  const carried = foundIds.length + missedIds.length;
  // An unnecessary pat-down is a small indignity — that's the moral cost.
  const aggroDelta = carried === 0 ? 1.5 : 0;
  const eject = foundIds.some((id) => SEVERITY_BY_ID.get(id) === 3);

  let summary: string;
  if (carried === 0) {
    summary = PATDOWN_CLEAR;
  } else {
    const parts = foundIds.map(foundLine);
    if (missedIds.length > 0) parts.push(PATDOWN_MISSED);
    summary = parts.join(' ');
  }

  return {
    foundIds,
    missedIds,
    vibeDelta: foundIds.length * 2 - missedIds.length * 1.5,
    aggroDelta,
    eject,
    summary,
  };
}

/** Flavour line for a found item; falls back gracefully on an unknown id. */
export function foundLine(itemId: string): string {
  const line = PATDOWN_FOUND[itemId];
  if (line !== undefined) return line;
  const name = NAME_BY_ID.get(itemId);
  return name !== undefined ? `${name}. Into the bin, mate.` : 'Into the bin, mate.';
}
