// CorruptionDirector.ts: single source of truth for all state + A/V params.
// Holds happiness/awareness/day/task flags/clue set/erosion stage.
// Emits events for scenes to react.
import { audio } from './AudioEngine';
import {
  HAPPY_START, AWARE_START, HAPPY_MAX, AWARE_MAX, OVERFLOW_THRESHOLD,
  EROSION_MATRIX, AWARE_CLUE_GAIN, AWARE_SAD_GAIN, HAPPY_SAD_LOSS,
  HAPPY_SMALL_DAILY_CAP, DEFAULT_NAME, LOW_HAPPY_THRESHOLD, LOW_HAPPY_DURATION_MS,
  FORCE_FESTIVAL_HAPPY,
} from '../data/gamedata';

export type Stage = 'S0' | 'S1' | 'S2' | 'S3' | 'S4';

type Handler = (payload?: any) => void;

class CorruptionDirector {
  happiness = HAPPY_START;
  awareness = AWARE_START;
  day = 1;
  stage: Stage = 'S1';
  playerName = DEFAULT_NAME;
  startTime = Date.now();

  // clue set
  clues = new Set<string>();
  // task completion
  tasksDone = new Set<string>();
  // sadness: small-interaction daily counters per kind
  smallInteractCounts: Record<string, number> = {}; // key: kind@day
  // npc talk counts per day (for 纠缠 detection)
  npcTalkCounts: Record<string, number> = {};
  // rejection records (Day 5)
  rejectedTasks = new Set<string>();
  // escaped forced festivals count (E4)
  escapedFestivals = 0;
  // happiness-low tracker
  private lowHappySince = 0;
  // overflow pool
  private overflowPool = 0;
  // last negative flash time
  private lastFlashAt = 0;

  // gaze tracking (well / fog) once per day
  gazedWell = false;
  gazedFog = false;

  // whether forced festival is active
  forcedFestivalActive = false;

  private listeners: Record<string, Handler[]> = {};

  on(ev: string, h: Handler) {
    (this.listeners[ev] ||= []).push(h);
    return () => this.off(ev, h);
  }
  off(ev: string, h: Handler) {
    const arr = this.listeners[ev]; if (!arr) return;
    const i = arr.indexOf(h); if (i >= 0) arr.splice(i, 1);
  }
  emit(ev: string, payload?: any) {
    (this.listeners[ev] || []).slice().forEach(h => { try { h(payload); } catch (e) { console.error(e); } });
  }

  reset() {
    this.happiness = HAPPY_START;
    this.awareness = AWARE_START;
    this.day = 1;
    this.stage = 'S1';
    this.playerName = DEFAULT_NAME;
    this.startTime = Date.now();
    this.clues.clear();
    this.tasksDone.clear();
    this.smallInteractCounts = {};
    this.npcTalkCounts = {};
    this.rejectedTasks.clear();
    this.escapedFestivals = 0;
    this.lowHappySince = 0;
    this.overflowPool = 0;
    this.gazedWell = false;
    this.gazedFog = false;
    this.forcedFestivalActive = false;
    this.recomputeStage();
    this.emit('change');
  }

  recomputeStage() {
    const h = this.happiness;
    let s: Stage = 'S0';
    for (const row of EROSION_MATRIX) {
      if (h >= row.happinessRange[0] && h <= row.happinessRange[1]) { s = row.stage; break; }
    }
    if (s !== this.stage) {
      this.stage = s;
      audio.setErosion(s);
      this.emit('stage', s);
    }
    this.emit('change');
  }

  setHappiness(v: number, opts: { silent?: boolean } = {}) {
    const prev = this.happiness;
    let nv = Math.max(0, Math.min(HAPPY_MAX, Math.round(v)));
    // forced maintenance at 100: small deductions accumulate in overflow pool
    if (prev >= HAPPY_MAX && nv < prev) {
      const diff = prev - nv;
      this.overflowPool += diff;
      nv = HAPPY_MAX; // stays at 100
      if (this.overflowPool >= OVERFLOW_THRESHOLD) {
        this.overflowPool = 0;
        this.emit('negativeFlash');
        this.stage = 'S4';
        audio.setErosion('S4');
        this.emit('stage', 'S4');
      }
    }
    this.happiness = nv;
    if (!opts.silent) this.recomputeStage();
    this.checkLowHappy();
  }

  addHappiness(delta: number) {
    if (delta > 0 && this.happiness >= HAPPY_MAX) {
      // overflow pool accumulation (PRD: 小额扣分不再生效而是累积进隐藏溢出池) — for increases beyond 100, ignore
      return;
    }
    this.setHappiness(this.happiness + delta);
  }

  addAwareness(delta: number) {
    this.awareness = Math.max(0, Math.min(AWARE_MAX, this.awareness + delta));
    this.emit('change');
  }

  // §6.2 clue discovery
  discoverClue(id: string) {
    if (this.clues.has(id)) return false;
    this.clues.add(id);
    this.addAwareness(AWARE_CLUE_GAIN);
    this.emit('clue', id);
    this.emit('change');
    return true;
  }

  // §6.1 small interaction (water/feed/pet) +1, daily cap 3 per kind
  smallInteraction(kind: string) {
    const key = `${kind}@${this.day}`;
    const count = this.smallInteractCounts[key] || 0;
    if (count >= HAPPY_SMALL_DAILY_CAP) return false;
    this.smallInteractCounts[key] = count + 1;
    this.addHappiness(1);
    return true;
  }

  // §6.3 sadness behavior: -5 happy, +3 aware, nearest NPC turns
  sadness(behavior: string, nearestNpc?: string) {
    this.addHappiness(-HAPPY_SAD_LOSS);
    this.addAwareness(AWARE_SAD_GAIN);
    this.emit('sadness', { behavior, nearestNpc });
    this.emit('change');
  }

  // gaze well/fog (§6.2)
  gaze(target: 'well' | 'fog') {
    if (target === 'well' && !this.gazedWell) { this.gazedWell = true; this.addAwareness(3); }
    if (target === 'fog' && !this.gazedFog) { this.gazedFog = true; this.addAwareness(3); }
  }

  // NPC talk counter for 纠缠 (>=5/day)
  noteNpcTalk(npcKey: string): boolean {
    const key = `${npcKey}@${this.day}`;
    this.npcTalkCounts[key] = (this.npcTalkCounts[key] || 0) + 1;
    return this.npcTalkCounts[key] >= 5;
  }

  private checkLowHappy() {
    if (this.happiness < LOW_HAPPY_THRESHOLD) {
      if (this.lowHappySince === 0) this.lowHappySince = Date.now();
      else if (Date.now() - this.lowHappySince >= LOW_HAPPY_DURATION_MS && !this.forcedFestivalActive) {
        this.triggerForcedFestival();
      }
    } else {
      this.lowHappySince = 0;
    }
  }

  // §6.1 forced festival
  triggerForcedFestival() {
    this.forcedFestivalActive = true;
    this.emit('forcedFestivalStart');
    // raise happiness to 50
    this.setHappiness(FORCE_FESTIVAL_HAPPY);
  }
  escapeFestival() {
    if (!this.forcedFestivalActive) return;
    this.forcedFestivalActive = false;
    this.escapedFestivals++;
    this.emit('forcedFestivalEnd');
    this.emit('change');
  }

  advanceDay() {
    this.day = Math.min(7, this.day + 1);
    this.gazedWell = false; this.gazedFog = false;
    this.emit('day', this.day);
    this.emit('change');
  }

  // ending determination per §9 priority
  determineEnding(): 'E4'|'E3'|'E2'|'E1' {
    if (this.day >= 5 && this.happiness <= 10 && this.escapedFestivals >= 3) return 'E4';
    if (this.awareness >= 70 && this.clues.size >= 6) return 'E3';
    if (this.awareness >= 30) return 'E2';
    if (this.awareness <= 29 && this.happiness >= 90) return 'E1';
    // fallback
    if (this.awareness <= 29) return 'E1';
    return 'E2';
  }

  playTimeSeconds(): number {
    return Math.floor((Date.now() - this.startTime) / 1000);
  }
  formatPlayTime(): string {
    const s = this.playTimeSeconds();
    const m = Math.floor(s / 60);
    const sec = s % 60;
    return `${m}分${sec}秒`;
  }

  // debug setters
  debugSetHappiness(v: number) { this.happiness = Math.max(0,Math.min(HAPPY_MAX,v)); this.recomputeStage(); }
  debugSetAwareness(v: number) { this.awareness = Math.max(0,Math.min(AWARE_MAX,v)); this.emit('change'); }
  debugSetDay(d: number) { this.day = Math.max(1,Math.min(7,d)); this.emit('day',this.day); this.emit('change'); }
  debugAllClues() { ['C1','C2','C3','C4','C5','C6','C7','C8','C9'].forEach(c=>this.clues.add(c)); this.addAwareness(0); this.emit('change'); }
}

export const director = new CorruptionDirector();
