/**
 * Integration-style tests: drive shipped Director through T1–T15, clues, endings.
 * Cross-scene delivery must use Director inventory (not scene fields).
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { Director } from '../systems/CorruptionDirector';
import { TASKS } from './tasks';
import type { ClueId } from './clues';
import { evaluateEnding } from './endings';
import { stageParams, stageFromHappiness } from './corruption';
import { DEBUG_TELEPORT_SCENES, SCENE_KEYS } from './constants';

describe('full content path via Director', () => {
  beforeEach(() => {
    Director.resetSession('小满');
  });

  it('T1: take bread → inventory survives "travel" → deliver 3 NPCs → task complete', () => {
    Director.setDay(1);
    const h0 = Director.happiness;
    expect(Director.takeBread()).toBe(true);
    expect(Director.hasBread).toBe(true);
    // Simulate leaving bakery / re-entering plaza & other scenes:
    // only Director state remains (WorldScene.create would wipe scene fields).
    const snapAfterTravel = Director.snapshot();
    expect(snapAfterTravel.hasBread).toBe(true);
    expect(Director.deliverBread('dabao')).toBe(true);
    expect(Director.deliverBread('mimi')).toBe(true);
    expect(Director.completedTasks.has('T1')).toBe(false);
    expect(Director.deliverBread('gui')).toBe(true);
    expect(Director.completedTasks.has('T1')).toBe(true);
    expect(Director.hasBread).toBe(false);
    expect(Director.happiness).toBe(h0 + 15);
    expect(Director.breadDelivered).toEqual(['dabao', 'mimi', 'gui']);
  });

  it('T4: take letters → deliver 3 → C4 + task complete', () => {
    Director.setDay(2);
    expect(Director.takeLetters()).toBe(true);
    expect(Director.hasLetters).toBe(true);
    // "travel" does not clear inventory
    expect(Director.snapshot().hasLetters).toBe(true);
    Director.deliverLetter('yuanyuan');
    Director.deliverLetter('mimi');
    expect(Director.completedTasks.has('T4')).toBe(false);
    Director.deliverLetter('fu');
    expect(Director.completedTasks.has('T4')).toBe(true);
    expect(Director.clues.has('C4')).toBe(true);
    expect(Director.hasLetters).toBe(false);
  });

  it('cannot deliver bread without takeBread first', () => {
    Director.setDay(1);
    expect(Director.deliverBread('dabao')).toBe(false);
    expect(Director.breadDelivered).toHaveLength(0);
  });

  it('D4 T8 same delivery path as T1', () => {
    Director.setDay(4);
    expect(Director.takeBread()).toBe(true);
    Director.deliverBread('dabao');
    Director.deliverBread('mimi');
    Director.deliverBread('gui');
    expect(Director.completedTasks.has('T8')).toBe(true);
    expect(Director.completedTasks.has('T1')).toBe(false);
  });

  it('can complete every T1–T15 with listed rewards', () => {
    const rewards: Record<string, number> = {};
    for (const t of TASKS) {
      Director.setDay(t.day);
      const before = Director.happiness;
      const ok = Director.completeTask(t.id);
      expect(ok).toBe(true);
      if (t.happinessReward > 0) {
        const gained = Director.happiness - before;
        expect(gained).toBeGreaterThanOrEqual(0);
        if (before + t.happinessReward <= 100) {
          expect(gained).toBe(t.happinessReward);
        }
      }
      rewards[t.id] = t.happinessReward;
    }
    expect(Object.keys(rewards)).toHaveLength(15);
    expect(Director.completedTasks.size).toBe(15);
  });

  it('debug teleport target list covers all 10 map scenes', () => {
    expect(DEBUG_TELEPORT_SCENES).toHaveLength(10);
    expect(new Set(DEBUG_TELEPORT_SCENES).size).toBe(10);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.HOUSE);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.PLAZA);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.BAKERY);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.SHOP);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.CLINIC);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.LAKE);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.FIELD);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.WELL);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.FOG);
    expect(DEBUG_TELEPORT_SCENES).toContain(SCENE_KEYS.WHITE);
  });

  it('marks all 9 clues and raises awareness by 8 each', () => {
    Director.setDay(5);
    const ids: ClueId[] = ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'];
    for (const id of ids) {
      // force allow by day already 5; C4 etc ok
      if (id === 'C9') {
        Director.c9Step = 2;
      }
      Director.discoverClue(id);
    }
    expect(Director.clues.size).toBe(9);
    expect(Director.awareness).toBe(9 * 8);
  });

  it('D5 refuse records sadness', () => {
    Director.setDay(5);
    const h = Director.happiness;
    const a = Director.awareness;
    expect(Director.refuseTask('T11')).toBe(true);
    expect(Director.refusedTasks.has('T11')).toBe(true);
    expect(Director.happiness).toBe(h - 5);
    expect(Director.awareness).toBe(a + 3);
  });

  it('ending markers for forced E1–E4', () => {
    for (const id of ['E1', 'E2', 'E3', 'E4'] as const) {
      Director.resetSession('验收');
      Director.forceEnding(id);
      expect(Director.endingId).toBe(id);
      expect(Director.ended).toBe(true);
      expect(Director.playerName).toBe('验收');
    }
  });

  it('E3 eval needs fog + awareness + clues', () => {
    expect(
      evaluateEnding({
        awareness: 80,
        happiness: 40,
        clueCount: 7,
        lowHappinessStreakD5to7: false,
        celebrationEscapes: 0,
        choseFogWall: true,
      }),
    ).toBe('E3');
  });

  it('debug happiness bands map to stage API params', () => {
    const cases = [
      [10, 'S0', 0.9],
      [40, 'S1', 1.0],
      [70, 'S2', 1.15],
      [90, 'S3', 1.3],
      [100, 'S4', 1.4],
    ] as const;
    for (const [h, stage, sat] of cases) {
      Director.setHappiness(h);
      expect(Director.stage).toBe(stage);
      expect(stageFromHappiness(h)).toBe(stage);
      expect(Director.getStageParams().saturation).toBe(sat);
      expect(stageParams(stage).saturation).toBe(sat);
    }
  });

  it('session duration label non-empty for E3 record field', () => {
    Director.resetSession('病历名');
    const label = Director.sessionDurationLabel();
    expect(label).toMatch(/\d+分\d{2}秒/);
    expect(Director.playerName).toBe('病历名');
  });
});
