import { ageOn } from '../core/GameClock';
import type { IdCard } from '../data/types';

// The ID minigame's date math. Everything the player can catch on a licence
// resolves here so the card art, the verdict and the audit can never disagree.

// Reading order on the card, not severity — the player scans top-left to
// bottom-right, and a stable order keeps tests and toast text deterministic.
//
// This tuple is the SOURCE of IdTell, not a parallel list of it. Typed the other
// way round (`const TELL_ORDER: readonly IdTell[]`) a sixth tell added to the
// union would still compile against a stale tuple, and `tells` would silently
// drop it — an uncatchable fake, the exact bug this module exists to prevent.
const TELL_ORDER = [
  'peelingLaminate',
  'wrongHologram',
  'jokeName',
  'photoMismatch',
  'expired',
] as const;

export type IdTell = (typeof TELL_ORDER)[number];

export interface IdVerdict {
  claimedAge: number;
  underage: boolean;
  expired: boolean;
  /** Valid, but only just — flavour text, never a violation. */
  expiresTonight: boolean;
  turnsEighteenTonight: boolean;
  tells: IdTell[];
  looksFake: boolean;
}

/** ISO date → local midnight ms. Local, not UTC: the licence is a local document. */
const midnightMs = (iso: string): number => new Date(`${iso.slice(0, 10)}T00:00:00`).getTime();

/** Drop any time-of-day the caller's nightDate carries, so comparisons are day-vs-day. */
const dayStart = (d: Date): Date => new Date(d.getFullYear(), d.getMonth(), d.getDate());

const dayBefore = (d: Date): Date => new Date(d.getFullYear(), d.getMonth(), d.getDate() - 1);

export function idVerdict(id: IdCard, nightDate: Date): IdVerdict {
  const night = dayStart(nightDate);
  const nightMs = night.getTime();
  const expiryMs = midnightMs(id.expiry);

  const claimedAge = ageOn(id.dob, night);
  // A licence is good THROUGH its expiry date, so equality is not expired.
  const expired = expiryMs < nightMs;

  // "Was 17 yesterday, is 18 today" rather than matching month/day, so a 29 Feb
  // DOB still trips this on the day the age math actually hands them their 18th.
  const turnsEighteenTonight = claimedAge === 18 && ageOn(id.dob, dayBefore(night)) === 17;

  const flags = id.fake;
  const present: Record<IdTell, boolean> = {
    peelingLaminate: flags?.peelingLaminate === true,
    wrongHologram: flags?.wrongHologram === true,
    jokeName: flags?.jokeName === true,
    photoMismatch: flags?.photoMismatch === true,
    // The generator ships fakes whose ONLY tell is the date, with no fake flags
    // set at all. Deriving this here is what makes those catchable.
    expired,
  };
  const tells = TELL_ORDER.filter((t) => present[t]);

  return {
    claimedAge,
    underage: claimedAge < 18,
    expired,
    expiresTonight: expiryMs === nightMs,
    turnsEighteenTonight,
    tells,
    looksFake: tells.length > 0,
  };
}

/** DD/MM/YYYY — Australian order, hand-rolled because Intl output varies by host. */
/**
 * Australian DD/MM/YYYY. Exported because the DESK needs to show tonight's date
 * in the same format the card shows the DOB — without it the player is asked to
 * subtract a birthdate from a date the game never told them, which made
 * `underage` (the tell that costs the licence) pure guesswork.
 */
export const formatDate = (d: Date): string =>
  `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;

const ddmmyyyy = (iso: string): string => {
  const [y = '', m = '', d = ''] = iso.slice(0, 10).split('-');
  return `${d.padStart(2, '0')}/${m.padStart(2, '0')}/${y.padStart(4, '0')}`;
};

export function idDisplayLines(
  id: IdCard,
  nightDate: Date,
): { name: string; dob: string; expiry: string; age: string } {
  return {
    name: id.name,
    dob: ddmmyyyy(id.dob),
    expiry: ddmmyyyy(id.expiry),
    age: String(ageOn(id.dob, dayStart(nightDate))),
  };
}
