import { describe, expect, it } from 'vitest';
import { adoptCastIdentity, buildRegularCast, REGULAR_CAST_SIZE } from '../src/patrons/regularCast';
import { disguisedFromMemory, recordDenialBy } from '../src/patrons/memory';
import { freshGameState } from '../src/core/save';
import { dollPlan } from '../src/patrons/dollPlan';
import { SeededRNG } from '../src/core/SeededRNG';
import { generatePatron, _resetPatronSerial } from '../src/patrons/generator';

const NIGHT = new Date('2026-07-16T00:00:00');

describe('the regular cast (design §4.1)', () => {
  it('is stable per run seed — same six faces, every night, every venue', () => {
    const a = buildRegularCast(4207, NIGHT);
    const b = buildRegularCast(4207, NIGHT);
    expect(a).toHaveLength(REGULAR_CAST_SIZE);
    expect(a.map((p) => p.dollSeed)).toEqual(b.map((p) => p.dollSeed));
    expect(a.map((p) => p.idCard.name)).toEqual(b.map((p) => p.idCard.name));
    expect(new Set(a.map((p) => p.flags.regularId)).size).toBe(REGULAR_CAST_SIZE);
  });

  it('different runs meet different regulars', () => {
    const a = buildRegularCast(1, NIGHT).map((p) => p.dollSeed);
    const b = buildRegularCast(2, NIGHT).map((p) => p.dollSeed);
    expect(a).not.toEqual(b);
  });

  it('cast members carry real, boring licences — regulars are not ID puzzles', () => {
    for (const p of buildRegularCast(4207, NIGHT)) {
      expect(p.idCard.fake).toBeUndefined();
      expect(p.age).toBeGreaterThanOrEqual(18);
    }
  });

  it('adoption keeps tonight\'s roll but wears the cast identity', () => {
    _resetPatronSerial();
    const rolled = generatePatron({ rng: new SeededRNG(9), nightDate: NIGHT }, 120, 'regular');
    const member = buildRegularCast(4207, NIGHT)[0]!;
    const adopted = adoptCastIdentity(rolled, member, 1, false);
    expect(adopted.dollSeed).toBe(member.dollSeed);
    expect(adopted.idCard.name).toBe(member.idCard.name);
    expect(adopted.outfit).toBe(rolled.outfit); // tonight's clothes
    expect(adopted.intoxication).toBe(rolled.intoxication); // tonight's drinks
    expect(adopted.flags.regularId).toBe(member.flags.regularId);
    expect(adopted.flags.timesDeniedBefore).toBe(1);
    expect(adopted.flags.disguised).toBeUndefined();
  });

  it('two denials on the run save → the third appearance is disguised', () => {
    const run = freshGameState(4207);
    recordDenialBy(run, 'reg2', 0);
    expect(disguisedFromMemory(run.regulars['reg2'])).toBe(false);
    recordDenialBy(run, 'reg2', 1);
    expect(disguisedFromMemory(run.regulars['reg2'])).toBe(true);
    expect(run.regulars['reg2']!.timesDenied).toBe(2);
  });

  it('the disguise is on the man, never on his card photo', () => {
    const member = buildRegularCast(4207, NIGHT)[3]!;
    const plain = { ...member, flags: { ...member.flags } };
    const disguised = { ...member, flags: { ...member.flags, disguised: true } };
    expect(dollPlan(disguised, 'queue').rects.length).toBeGreaterThan(
      dollPlan(plain, 'queue').rects.length,
    );
    // The idPhoto stays clean-shaven: the card shows Thursday's bloke.
    expect(dollPlan(disguised, 'idPhoto')).toEqual(dollPlan(plain, 'idPhoto'));
  });
});
