// Per-variant alphabet table cache. The client is alphabet-agnostic: it caches
// each variant's (index, letter, value) table — sent by the server on a per-variant cache
// miss, behind game.state's include_alphabet flag — and renders the rack and the blank
// chooser with it while live play exchanges bare alphabet indices on the wire. Letters are
// stored upper-cased for display (the rest of the UI works in upper case) and index lookups
// are case-insensitive. A blank rides as the sentinel index 255 in a rack/exchange list; a
// placed blank instead travels as its designated letter's index with a separate blank flag.

import type { Variant } from './model';

/** BLANK_INDEX is the wire sentinel for a blank tile in a rack/exchange index list. */
export const BLANK_INDEX = 255;

/** BLANK_LETTER is the glyph a blank rack tile decodes to (matches placement.BLANK). */
const BLANK_LETTER = '?';

/** AlphabetEntryWire is one raw alphabet row as received from the wire or a mock fixture. */
export interface AlphabetEntryWire {
  index: number;
  letter: string;
  value: number;
}

interface Table {
  letters: string[]; // by index, upper-cased
  values: number[]; // by index
  indexByLetter: Map<string, number>; // upper-cased letter -> index
}

const cache = new Map<Variant, Table>();

/** setAlphabet caches a variant's table, upper-casing letters for display. */
export function setAlphabet(variant: Variant, entries: AlphabetEntryWire[]): void {
  let size = 0;
  for (const e of entries) size = Math.max(size, e.index + 1);
  const letters = new Array<string>(size).fill('');
  const values = new Array<number>(size).fill(0);
  const indexByLetter = new Map<string, number>();
  for (const e of entries) {
    const up = e.letter.toUpperCase();
    letters[e.index] = up;
    values[e.index] = e.value;
    indexByLetter.set(up, e.index);
  }
  cache.set(variant, { letters, values, indexByLetter });
}

/** hasAlphabet reports whether a variant's table is cached (so the client can skip asking
 * the server to resend it). */
export function hasAlphabet(variant: Variant): boolean {
  return cache.has(variant);
}

/** alphabetLetters lists a variant's letters (upper-cased) for the blank chooser and the
 * word-check input filter; empty when the table is not yet cached. */
export function alphabetLetters(variant: Variant): string[] {
  return cache.get(variant)?.letters.slice() ?? [];
}

/** letterForIndex maps a wire rack index to its display letter: the blank sentinel maps to
 * "?", an unknown index to "". */
export function letterForIndex(variant: Variant, index: number): string {
  if (index === BLANK_INDEX) return BLANK_LETTER;
  return cache.get(variant)?.letters[index] ?? '';
}

/** valueForLetter returns a tile's point value; a blank ("?") and an unknown letter score 0. */
export function valueForLetter(variant: Variant, letter: string): number {
  if (!letter || letter === BLANK_LETTER) return 0;
  const t = cache.get(variant);
  if (!t) return 0;
  const i = t.indexByLetter.get(letter.toUpperCase());
  return i === undefined ? 0 : t.values[i];
}

/** alphabetValues returns a variant's tile values indexed by alphabet letter index, or an
 * empty array when the table is not yet cached. The local move validator scores with it. */
export function alphabetValues(variant: Variant): readonly number[] {
  return cache.get(variant)?.values ?? [];
}

/** indexForLetter maps a display letter to its wire index; a blank ("?") maps to the blank
 * sentinel. It throws when the letter is outside the cached alphabet — a placement bug, not
 * user input (the UI constrains every entry point to the variant's alphabet). */
export function indexForLetter(variant: Variant, letter: string): number {
  if (letter === BLANK_LETTER) return BLANK_INDEX;
  const i = cache.get(variant)?.indexByLetter.get(letter.toUpperCase());
  if (i === undefined) throw new Error(`alphabet: no index for "${letter}" in ${variant}`);
  return i;
}
