import Phaser from 'phaser';
import { CLUES, CORRUPTIONS, NPCS, TASKS, type EndingId, type NpcId, type Stage } from './data';

interface RuntimeState {
  playerName: string;
  startTime: number;
  happiness: number;
  awareness: number;
  day: number;
  dayStartedAt: number;
  boardRead: boolean;
  sunset: boolean;
  clues: Set<string>;
  completed: Set<string>;
  refused: Set<string>;
  flags: Set<string>;
  counters: Map<string, number>;
  dialogueCounts: Map<string, number>;
  overflow: number;
  cheerEscapes: number;
  cheerActive: boolean;
  lowSince: number | null;
  lowRouteValid: boolean;
  dialogueOpen: boolean;
  t14Active: boolean;
  festivalStarted: boolean;
}

const createState = (): RuntimeState => ({
  playerName: '小满', startTime: performance.now(), happiness: 30, awareness: 0, day: 1, dayStartedAt: performance.now(),
  boardRead: false, sunset: false, clues: new Set(), completed: new Set(), refused: new Set(),
  flags: new Set(), counters: new Map(), dialogueCounts: new Map(), overflow: 0,
  cheerEscapes: 0, cheerActive: false, lowSince: null, lowRouteValid: true,
  dialogueOpen: false, t14Active: false, festivalStarted: false,
});

export class CorruptionDirector extends Phaser.Events.EventEmitter {
  private state = createState();
  private renderCount = 0;

  get snapshot(): Readonly<RuntimeState> { return this.state; }
  get stage(): Stage { return this.stageFor(this.state.happiness); }

  reset(playerName = '小满'): void {
    this.state = createState();
    this.renderCount = 0;
    this.state.playerName = playerName.trim() || '小满';
    this.emit('reset');
    this.emitChange();
  }

  stageFor(value: number): Stage {
    if (value <= 20) return 'S0';
    if (value <= 50) return 'S1';
    if (value <= 80) return 'S2';
    if (value <= 99) return 'S3';
    return 'S4';
  }

  npcExpression(id: NpcId): 'smile' | 'flat' | 'off' {
    if (id === 'doctor' || id === 'turtle') return 'smile';
    const effective = this.state.happiness - NPCS[id].offset;
    const npcStage = this.stageFor(Math.max(0, effective));
    if (npcStage === 'S4') return 'flat';
    if (npcStage === 'S3') return 'flat';
    return 'smile';
  }

  setHappiness(value: number): void {
    const wasFull = this.state.happiness === 100;
    this.state.happiness = Phaser.Math.Clamp(Math.round(value), 0, 100);
    if (!wasFull && this.state.happiness === 100 && !this.state.flags.has('first-full')) {
      this.state.flags.add('first-full');
      this.emit('first-full');
    }
    this.checkLowTimer();
    this.emitChange();
  }

  adjustHappiness(delta: number): void {
    if (this.state.happiness === 100 && delta < 0 && Math.abs(delta) <= 5) {
      this.state.overflow += Math.abs(delta);
      if (this.state.overflow >= 15) {
        this.state.overflow -= 15;
        this.emit('negative-flash');
      }
      this.emitChange();
      return;
    }
    this.setHappiness(this.state.happiness + delta);
  }

  setAwareness(value: number): void {
    this.state.awareness = Phaser.Math.Clamp(Math.round(value), 0, 100);
    this.emitChange();
  }

  adjustAwareness(delta: number): void { this.setAwareness(this.state.awareness + Math.max(0, delta)); }

  setDay(day: number): void {
    this.state.day = Phaser.Math.Clamp(Math.round(day), 1, 7);
    this.state.boardRead = false;
    this.state.sunset = false;
    this.state.dayStartedAt = performance.now();
    this.state.dialogueCounts.clear();
    if (this.state.day >= 5 && this.state.happiness > 10) this.state.lowRouteValid = false;
    this.emitChange();
  }

  acceptDay(): void {
    this.state.boardRead = true;
    this.emitChange();
  }

  tasksForDay(day = this.state.day) { return TASKS.filter((task) => task.day === day); }
  isTaskActive(id: string): boolean {
    const task = TASKS.find((item) => item.id === id);
    return Boolean(task && task.day === this.state.day && this.state.boardRead && !this.state.completed.has(id) && !this.state.refused.has(id));
  }

  completeTask(id: string): boolean {
    const task = TASKS.find((item) => item.id === id);
    if (!task || this.state.completed.has(id) || this.state.refused.has(id)) return false;
    this.state.completed.add(id);
    this.adjustHappiness(task.reward);
    this.emit('task-complete', task);
    this.checkDayComplete();
    return true;
  }

  refuseTask(id: string): void {
    const task = TASKS.find((item) => item.id === id && item.optional);
    if (!task || this.state.completed.has(id) || this.state.refused.has(id)) return;
    this.state.refused.add(id);
    this.sadBehavior(`refuse-${id}`);
    this.emit('task-refused', task);
    this.checkDayComplete();
  }

  private checkDayComplete(): void {
    if (this.state.day === 7) return;
    const done = this.tasksForDay().every((task) => this.state.completed.has(task.id) || this.state.refused.has(task.id));
    if (done && !this.state.sunset && performance.now() - this.state.dayStartedAt >= 240_000) {
      this.state.sunset = true;
      this.emit('sunset');
      this.emitChange();
    }
  }

  counter(key: string): number { return this.state.counters.get(`${this.state.day}:${key}`) ?? 0; }
  increment(key: string, cap = Number.POSITIVE_INFINITY): number {
    const id = `${this.state.day}:${key}`;
    const value = Math.min(cap, (this.state.counters.get(id) ?? 0) + 1);
    this.state.counters.set(id, value);
    this.emitChange();
    return value;
  }

  smallInteraction(kind: 'water' | 'feed' | 'pet'): boolean {
    const key = `small-${kind}`;
    if (this.counter(key) >= 3) return false;
    this.increment(key, 3);
    this.adjustHappiness(1);
    this.emit('small-interaction', kind);
    return true;
  }

  flag(key: string): boolean { return this.state.flags.has(`${this.state.day}:${key}`) || this.state.flags.has(key); }
  setFlag(key: string, global = false): void {
    this.state.flags.add(global ? key : `${this.state.day}:${key}`);
    this.emitChange();
  }

  discoverClue(id: string): boolean {
    if (!CLUES[id] || this.state.clues.has(id)) return false;
    this.state.clues.add(id);
    this.adjustAwareness(8);
    this.emit('clue', id, CLUES[id]);
    return true;
  }

  discoverAllClues(): void {
    Object.keys(CLUES).forEach((id) => this.state.clues.add(id));
    this.setAwareness(Math.max(this.state.awareness, 72));
  }

  sadBehavior(key: string): boolean {
    const scoped = `${this.state.day}:sad:${key}`;
    if (this.state.flags.has(scoped)) return false;
    this.state.flags.add(scoped);
    this.adjustHappiness(-5);
    this.adjustAwareness(3);
    this.emit('sad-behavior', key);
    return true;
  }

  recordDialogue(npc: string): number {
    const count = (this.state.dialogueCounts.get(npc) ?? 0) + 1;
    this.state.dialogueCounts.set(npc, count);
    if (count === 5) this.sadBehavior(`cling-${npc}`);
    return count;
  }

  corruptText(text: string, speaker: string): string {
    this.renderCount += 1;
    const stage = this.stage;
    if (stage === 'S0' || stage === 'S1') return text;
    if (stage === 'S2') return this.renderCount % 80 === 0 ? text.replace(/[。！？]/, '$&$&') : text;
    const interval = stage === 'S4' ? 18 : 50;
    const prioritized = speaker === '咪咪';
    let result = text;
    if (prioritized || this.renderCount % interval === 0) {
      const pair = CORRUPTIONS.find(([clean]) => result.includes(clean));
      if (pair) result = result.replace(pair[0], pair[1]);
    }
    if (stage === 'S4' && speaker === '咪咪') result = result.replace(/([。！？])$/, '$1$1$1');
    return result;
  }

  setDialogueOpen(open: boolean): void { this.state.dialogueOpen = open; }
  setT14(active: boolean): void { this.state.t14Active = active; this.emitChange(); }
  setFestivalStarted(): void { this.state.festivalStarted = true; this.emitChange(); }

  advanceDay(): void {
    if (!this.state.sunset || this.state.day >= 7) return;
    this.setDay(this.state.day + 1);
  }

  elapsedLabel(): string {
    const total = Math.max(0, Math.floor((performance.now() - this.state.startTime) / 1000));
    const minutes = Math.floor(total / 60).toString().padStart(2, '0');
    const seconds = (total % 60).toString().padStart(2, '0');
    return `${minutes}:${seconds}`;
  }

  ending(): EndingId | undefined {
    if (this.state.day >= 5 && this.state.happiness <= 10 && this.state.cheerEscapes >= 3 && this.state.lowRouteValid) return 'E4';
    if (this.state.awareness >= 70 && this.state.clues.size >= 6) return 'E3';
    if (this.state.awareness >= 30 && this.state.awareness <= 69) return 'E2';
    if (this.state.awareness <= 29 && this.state.happiness >= 90) return 'E1';
    return undefined;
  }

  tick(): void {
    this.checkLowTimer();
    this.checkDayComplete();
    if (this.stage === 'S4' && Math.random() < 0.4 / 3600) this.emit('negative-flash');
  }

  private checkLowTimer(): void {
    const now = performance.now();
    if (this.state.day >= 5 && this.state.happiness > 10) this.state.lowRouteValid = false;
    if (this.state.happiness < 30 && !this.state.cheerActive) {
      this.state.lowSince ??= now;
      if (now - this.state.lowSince >= 120_000) {
        this.state.cheerActive = true;
        this.emit('forced-cheer');
      }
    } else if (this.state.happiness >= 30) this.state.lowSince = null;
  }

  escapeCheer(): void {
    if (!this.state.cheerActive) return;
    this.state.cheerActive = false;
    if (this.state.day >= 5) this.state.cheerEscapes += 1;
    this.state.lowSince = performance.now();
    this.emitChange();
  }

  finishCheer(): void {
    if (!this.state.cheerActive) return;
    this.state.cheerActive = false;
    this.setHappiness(50);
  }

  private emitChange(): void { this.emit('change', this.state); }
}

export const director = new CorruptionDirector();
