import { describe, expect, it } from 'vitest';
import { SeededRNG } from '../src/core/SeededRNG';
import { dollPlan, DOLL_SIZES } from '../src/patrons/dollPlan';
import { generatePatron, _resetPatronSerial } from '../src/patrons/generator';
import type { Patron } from '../src/data/types';

const NIGHT = new Date('2026-07-18T00:00:00');
const somePatrons = (seed: number, n: number): Patron[] => {
  _resetPatronSerial();
  const ctx = { rng: new SeededRNG(seed), nightDate: NIGHT };
  return Array.from({ length: n }, () => generatePatron(ctx, 0));
};

describe('dollPlan', () => {
  it('is deterministic per (patron, pose)', () => {
    for (const p of somePatrons(5, 20)) {
      expect(dollPlan(p, 'queue')).toEqual(dollPlan(p, 'queue'));
      expect(dollPlan(p, 'idPhoto')).toEqual(dollPlan(p, 'idPhoto'));
      expect(dollPlan(p, 'floorTopDown')).toEqual(dollPlan(p, 'floorTopDown'));
    }
  });

  it('respects the pose size contract and stays in bounds', () => {
    for (const p of somePatrons(9, 30)) {
      for (const pose of ['queue', 'idPhoto', 'floorTopDown'] as const) {
        const plan = dollPlan(p, pose);
        expect(plan.width).toBe(DOLL_SIZES[pose].width);
        expect(plan.height).toBe(DOLL_SIZES[pose].height);
        for (const r of plan.rects) {
          expect(r.x).toBeGreaterThanOrEqual(-2); // jacket edges may hug the border
          expect(r.x + r.w).toBeLessThanOrEqual(plan.width + 2);
          expect(r.y).toBeGreaterThanOrEqual(0);
          expect(r.y + r.h).toBeLessThanOrEqual(plan.height);
        }
      }
    }
  });

  it('drunk stages produce sway + bloodshot tells; sober does not', () => {
    const [p] = somePatrons(3, 1);
    const sober = dollPlan({ ...p!, intoxication: 0.1 }, 'queue');
    const maggot = dollPlan({ ...p!, intoxication: 0.95 }, 'queue');
    expect(sober.swayPx).toBe(0);
    expect(maggot.swayPx).toBe(3);
    expect(maggot.rects.length).toBeGreaterThan(sober.rects.length); // eye pixels
  });

  it('photoMismatch renders the ID photo from the WRONG seed (visible tell)', () => {
    const [p] = somePatrons(21, 1);
    const honest: Patron = { ...p!, idCard: { ...p!.idCard, photoSeed: p!.dollSeed, fake: undefined } };
    const mismatched: Patron = {
      ...p!,
      idCard: { ...p!.idCard, photoSeed: p!.dollSeed + 7777, fake: { photoMismatch: true } },
    };
    expect(dollPlan(mismatched, 'idPhoto')).not.toEqual(dollPlan(honest, 'idPhoto'));
    // and the patron themselves renders identically — only the card lies
    expect(dollPlan(mismatched, 'queue')).toEqual(dollPlan(honest, 'queue'));
  });

  it('outfit data drives visible pixels: white vs black sneakers differ', () => {
    const [p] = somePatrons(33, 1);
    const white: Patron = {
      ...p!,
      outfit: p!.outfit.map((l) => (l.slot === 'shoes' ? { ...l, type: 'sneaker', colour: 'white' } : l)),
    };
    const black: Patron = {
      ...p!,
      outfit: p!.outfit.map((l) => (l.slot === 'shoes' ? { ...l, type: 'sneaker', colour: 'black' } : l)),
    };
    expect(dollPlan(white, 'queue')).not.toEqual(dollPlan(black, 'queue'));
  });
});
