import type { OutfitSlot, Patron } from '../../data/types';
import { drunkStage } from '../../rules/drunk';

// Pure: what you SEE when you look at a patron. Deliberately never says
// "violates the dress code" — the card says what is banned, this says what is
// there, and closing that gap is the player's job (design §4.1).

const SHOE_WORDS: Record<string, string> = {
  sneaker: 'sneakers',
  boot: 'boots',
  dress: 'dress shoes',
  heel: 'heels',
  thong: 'thongs',
  loafer: 'loafers',
  platform: 'platforms',
};

const LEG_WORDS: Record<string, string> = {
  jeans: 'jeans',
  chinos: 'chinos',
  skirt: 'skirt',
  trackies: 'trackies',
  shorts: 'shorts',
  slacks: 'slacks',
  cargo: 'cargo pants',
};

const TOP_WORDS: Record<string, string> = {
  tee: 'tee',
  shirt: 'shirt',
  singlet: 'singlet',
  polo: 'polo',
  crop: 'crop top',
  dressShirt: 'dress shirt',
  jersey: 'footy jersey',
};

const OUTER_WORDS: Record<string, string> = {
  blazer: 'blazer',
  bomber: 'bomber jacket',
  hoodie: 'hoodie',
  leather: 'leather jacket',
  puffer: 'puffer',
};

const ACC_WORDS: Record<string, string> = {
  bucketHat: 'bucket hat',
  cap: 'cap',
  sunnies: 'sunnies',
  bumbag: 'bumbag',
  chain: 'chain',
};

const HAIR_WORDS: Record<string, string> = {
  short: 'short hair',
  long: 'long hair',
  bun: 'hair up',
  mullet: 'a mullet',
  shaved: 'a shaved head',
  dyed: 'dyed hair',
};

const WORDS: Partial<Record<OutfitSlot, Record<string, string>>> = {
  shoes: SHOE_WORDS,
  legs: LEG_WORDS,
  top: TOP_WORDS,
  outer: OUTER_WORDS,
  accessory: ACC_WORDS,
  hair: HAIR_WORDS,
};

/**
 * Does this slot/type pair have a hand-written word? Exported so a test can
 * assert coverage EXACTLY, rather than inferring it by string-comparing the
 * output against the raw type id — that heuristic silently stopped working the
 * moment a legitimate word ('skirt', 'cap') matched its own id.
 */
export function hasWordFor(slot: OutfitSlot, type: string): boolean {
  return type === 'none' || WORDS[slot]?.[type] !== undefined;
}

/**
 * One line about one slot. Colour comes first because colour is what the dress
 * code keeps changing its mind about — so the noun tables above hold BARE nouns
 * with no article, or you get "navy a blazer". Hair is the exception: it is
 * never colour-prefixed, so HAIR_WORDS keeps its articles.
 */
export function describeSlot(p: Patron, slot: OutfitSlot): string {
  const layer = p.outfit.find((l) => l.slot === slot);
  if (!layer || layer.type === 'none') {
    return slot === 'outer' ? 'no jacket' : slot === 'accessory' ? 'no accessories' : 'nothing notable';
  }
  const noun = WORDS[slot]?.[layer.type] ?? layer.type;
  const base = slot === 'hair' ? noun : `${layer.colour} ${noun}`;
  const notes: string[] = [];
  if (layer.vintage) notes.push('genuinely old');
  else if (layer.type === 'sneaker' && layer.colour === 'white') notes.push('look brand new');
  if (layer.logo) notes.push('big logo on it');
  return notes.length > 0 ? `${base} — ${notes.join(', ')}` : base;
}

/** The eyes. The single most reliable intoxication tell at the rope. */
export function describeEyes(p: Patron): string {
  switch (drunkStage(p.intoxication)) {
    case 'sober':
      return 'clear eyes, bit bored';
    case 'tipsy':
      return 'bright eyes. cheerful';
    case 'loose':
      return 'pink around the edges';
    case 'messy':
      return 'bloodshot. slow blink';
    case 'maggot':
      return 'not tracking. at all';
  }
}

/** How they are standing. Matches the sway the renderer bakes in. */
export function describeStance(p: Patron): string {
  switch (drunkStage(p.intoxication)) {
    case 'sober':
      return 'standing straight';
    case 'tipsy':
      return 'shifting foot to foot';
    case 'loose':
      return 'swaying, just a bit';
    case 'messy':
      return 'leaning on nothing';
    case 'maggot':
      return 'being held up by a mate';
  }
}

/** What they say when they step up, chosen deterministically from dollSeed. */
export function greetingIndex(p: Patron, poolSize: number): number {
  return poolSize > 0 ? p.dollSeed % poolSize : 0;
}

/** Accessories the renderer draws on the body rather than the head. */
function waistOrCollar(p: Patron): string[] {
  const acc = p.outfit.find((l) => l.slot === 'accessory');
  if (!acc || (acc.type !== 'bumbag' && acc.type !== 'chain')) return [];
  return [describeSlot(p, 'accessory')];
}

export type InspectZone = 'head' | 'face' | 'torso' | 'legs' | 'shoes';

export const ZONE_ORDER: readonly InspectZone[] = ['head', 'face', 'torso', 'legs', 'shoes'];

/**
 * Doll-local y bands (32x48 queue pose) each zone covers. Kept beside the
 * descriptions so a change to dollPlan.ts has one obvious place to land.
 */
export const ZONE_BANDS: Record<InspectZone, { y0: number; y1: number }> = {
  head: { y0: 0, y1: 8 },
  face: { y0: 8, y1: 16 },
  torso: { y0: 16, y1: 30 },
  legs: { y0: 30, y1: 44 },
  shoes: { y0: 44, y1: 48 },
};

/** Everything the tooltip for a zone should say, top line first. */
export function describeZone(p: Patron, zone: InspectZone): string[] {
  switch (zone) {
    case 'head':
      return [describeSlot(p, 'hair'), describeSlot(p, 'accessory')];
    case 'face':
      return [describeEyes(p), describeStance(p)];
    case 'torso':
      // A chain hangs at the collar and a bumbag at the waist — dollPlan draws
      // both on the torso, so the torso zone has to name them or the player
      // hovers the thing they can see and is told about a jacket instead.
      return [describeSlot(p, 'top'), describeSlot(p, 'outer'), ...waistOrCollar(p)];
    case 'legs':
      return [describeSlot(p, 'legs'), ...waistOrCollar(p)];
    case 'shoes':
      return [describeSlot(p, 'shoes')];
  }
}
