import { useOfficeStore } from "@/store/office-store";
import { ENTRANCE, MAX_MAIN_AGENTS, MAX_SUB_AGENTS } from "@/lib/constants";
import { DESK_SLOTS, HOT_DESK_SLOTS, LOUNGE_ANCHORS } from "@/lib/positions";
import {
  AGENT_MODELS,
  AGENT_NAMES,
  DONE_PHRASES,
  MEETING_PHRASES,
  SUB_AGENT_KINDS,
  SUB_DONE_PHRASES,
  SUB_TOOLS,
  TASK_NAMES,
  TOOLS,
  pick,
  rand,
} from "@/lib/phrases";

type Phase =
  | "arriving"
  | "idle"
  | "thinking"
  | "tooling"
  | "waiting_subs"
  | "speaking"
  | "error"
  | "lounge_go"
  | "lounge_stay"
  | "lounge_back"
  | "meeting"
  | "sub_arriving"
  | "sub_working"
  | "sub_reporting"
  | "sub_leaving";

interface Brain {
  phase: Phase;
  timer: number;
  toolsLeft: number;
  loungeAnchor: number;
  taskName: string | null;
}

interface MeetingSession {
  id: string;
  agentIds: string[];
  timer: number;
  speakTimer: number;
  speakerIdx: number;
  gathered: boolean;
}

let subSeq = 0;
let meetingSeq = 0;

/**
 * The simulation director: a per-agent finite-state machine plus a global
 * meeting scheduler. Everything mutates the world through store actions only,
 * so a real Claude Code event feed could replace this file wholesale.
 */
export class Director {
  private brains = new Map<string, Brain>();
  private meeting: MeetingSession | null = null;
  private meetingCooldown = 20;
  private arrivalQueue: string[] = [];
  private arrivalTimer = 0.5;

  constructor() {
    this.arrivalQueue = AGENT_NAMES.slice(0, 4);
  }

  private get store() {
    return useOfficeStore.getState();
  }

  update(dt: number) {
    this.handleArrivals(dt);
    this.updateMeetingScheduler(dt);

    const S = this.store;
    for (const [id, brain] of this.brains) {
      const agent = S.agents.get(id);
      if (!agent) {
        this.brains.delete(id);
        continue;
      }
      brain.timer -= dt;
      this.stepAgent(id, brain, dt);
    }
  }

  /* ── public controls ── */

  /** Manually assign a task from the UI panel. */
  assignTask(id: string): boolean {
    const brain = this.brains.get(id);
    const agent = this.store.agents.get(id);
    if (!brain || !agent || brain.phase !== "idle" || agent.movement) return false;
    this.beginTask(id, brain);
    return true;
  }

  /** Spawn one more main agent (header button). */
  hireAgent(): boolean {
    const S = this.store;
    const mains = [...S.agents.values()].filter((a) => a.role !== "subagent");
    if (mains.length + this.arrivalQueue.length >= MAX_MAIN_AGENTS) return false;
    const used = new Set(mains.map((a) => a.name));
    const name = AGENT_NAMES.find((n) => !used.has(n) && !this.arrivalQueue.includes(n));
    if (!name) return false;
    this.arrivalQueue.push(name);
    return true;
  }

  /* ── arrivals ── */

  private handleArrivals(dt: number) {
    if (this.arrivalQueue.length === 0) return;
    this.arrivalTimer -= dt;
    if (this.arrivalTimer > 0) return;
    this.arrivalTimer = rand(0.9, 1.6);

    const S = this.store;
    const name = this.arrivalQueue.shift()!;
    const occupied = new Set(
      [...S.agents.values()].filter((a) => a.homeZone === "desk").map((a) => a.homeSlot),
    );
    const slot = DESK_SLOTS.findIndex((_, i) => !occupied.has(i));
    if (slot === -1) return;

    const id = `main-${name.toLowerCase()}`;
    const role = S.agents.size === 0 ? "lead" : "agent";
    S.spawnAgent({ id, name, role, model: AGENT_MODELS[name] ?? "claude-sonnet-5", homeSlot: slot });
    S.addEvent("🚪", `${name} 進入辦公室`);
    S.startWalk(id, DESK_SLOTS[slot], "desk", { arriveStatus: "idle" });
    this.brains.set(id, {
      phase: "arriving",
      timer: 0,
      toolsLeft: 0,
      loungeAnchor: -1,
      taskName: null,
    });
  }

  /* ── main agent FSM ── */

  private beginTask(id: string, brain: Brain) {
    const S = this.store;
    const task = pick(TASK_NAMES);
    brain.taskName = task;
    brain.phase = "thinking";
    brain.timer = rand(2, 4);
    S.setStatus(id, "thinking");
    const agent = S.agents.get(id);
    if (agent) S.addEvent("📋", `${agent.name} 接下任務「${task}」`);
  }

  private stepAgent(id: string, brain: Brain, _dt: number) {
    const S = this.store;
    const agent = S.agents.get(id)!;

    switch (brain.phase) {
      case "arriving": {
        if (!agent.movement && agent.zone === agent.homeZone) {
          brain.phase = "idle";
          brain.timer = rand(1.5, 5);
        }
        break;
      }

      case "idle": {
        if (this.meeting?.agentIds.includes(id)) break;
        if (brain.timer > 0 || agent.movement) break;
        const roll = Math.random();
        if (roll < 0.62) {
          this.beginTask(id, brain);
        } else if (roll < 0.78 && agent.zone === "desk") {
          // coffee break
          const usedAnchors = new Set(
            [...this.brains.values()].map((b) => b.loungeAnchor).filter((i) => i >= 0),
          );
          const anchor = LOUNGE_ANCHORS.findIndex((_, i) => !usedAnchors.has(i));
          if (anchor >= 0) {
            brain.loungeAnchor = anchor;
            brain.phase = "lounge_go";
            S.startWalk(id, LOUNGE_ANCHORS[anchor], "lounge", { arriveStatus: "idle" });
            S.addEvent("☕", `${agent.name} 去休息區倒咖啡`);
          } else {
            brain.timer = rand(2, 5);
          }
        } else if (roll < 0.83) {
          brain.phase = "error";
          brain.timer = rand(2.5, 3.5);
          S.setStatus(id, "error");
          S.addEvent("⚠️", `${agent.name} 遇到錯誤,重試中`);
        } else {
          brain.timer = rand(2, 6);
        }
        break;
      }

      case "thinking": {
        if (brain.timer > 0) break;
        brain.phase = "tooling";
        brain.toolsLeft = 2 + Math.floor(Math.random() * 3);
        brain.timer = 0;
        S.setStatus(id, "tool_calling");
        break;
      }

      case "tooling": {
        if (brain.timer > 0) break;
        if (brain.toolsLeft > 0) {
          brain.toolsLeft -= 1;
          brain.timer = rand(1.4, 2.6);
          S.setTool(id, { name: pick(TOOLS), startedAt: S.clock });
          break;
        }
        S.setTool(id, null);
        // decide whether to delegate to subagents
        const subCount = [...S.agents.values()].filter((a) => a.role === "subagent").length;
        const wantSubs = Math.random() < 0.55 ? 1 + Math.floor(Math.random() * 2) : 0;
        const canSpawn = Math.min(wantSubs, MAX_SUB_AGENTS - subCount);
        if (canSpawn > 0) {
          for (let i = 0; i < canSpawn; i++) this.spawnSubagent(id);
          brain.phase = "waiting_subs";
          S.setStatus(id, "thinking");
        } else {
          this.finishTask(id, brain);
        }
        break;
      }

      case "waiting_subs": {
        if (agent.childIds.length === 0) {
          this.finishTask(id, brain);
        }
        break;
      }

      case "speaking": {
        if (brain.timer > 0) break;
        S.setSpeech(id, null);
        S.setStatus(id, "idle");
        brain.phase = "idle";
        brain.timer = rand(3, 8);
        break;
      }

      case "error": {
        if (brain.timer > 0) break;
        S.setStatus(id, "idle");
        brain.phase = "idle";
        brain.timer = rand(2, 5);
        break;
      }

      case "lounge_go": {
        if (!agent.movement && agent.zone === "lounge") {
          brain.phase = "lounge_stay";
          brain.timer = rand(5, 10);
        }
        break;
      }

      case "lounge_stay": {
        if (brain.timer > 0) break;
        brain.phase = "lounge_back";
        brain.loungeAnchor = -1;
        S.walkHome(id);
        break;
      }

      case "lounge_back": {
        if (!agent.movement && agent.zone === agent.homeZone) {
          brain.phase = "idle";
          brain.timer = rand(2, 6);
        }
        break;
      }

      case "meeting":
        // handled by the meeting scheduler
        break;

      /* ── subagent FSM ── */

      case "sub_arriving": {
        if (!agent.movement && agent.zone === "hotDesk") {
          brain.phase = "sub_working";
          brain.toolsLeft = 3 + Math.floor(Math.random() * 4);
          brain.timer = 0;
          S.setStatus(id, "tool_calling");
        }
        break;
      }

      case "sub_working": {
        if (brain.timer > 0) break;
        if (brain.toolsLeft > 0) {
          brain.toolsLeft -= 1;
          brain.timer = rand(1.2, 2.2);
          S.setTool(id, { name: pick(SUB_TOOLS), startedAt: S.clock });
          break;
        }
        S.setTool(id, null);
        brain.phase = "sub_reporting";
        brain.timer = rand(1.8, 2.6);
        S.setStatus(id, "speaking");
        S.setSpeech(id, pick(SUB_DONE_PHRASES));
        break;
      }

      case "sub_reporting": {
        if (brain.timer > 0) break;
        S.setSpeech(id, null);
        S.addEvent("↩️", `${agent.name} 回報完畢,離開辦公室`);
        if (agent.parentId) S.removeLink(agent.parentId, id);
        brain.phase = "sub_leaving";
        S.startWalk(id, { ...ENTRANCE }, "corridor", { despawnOnArrive: true });
        break;
      }

      case "sub_leaving":
        break;
    }
  }

  private finishTask(id: string, brain: Brain) {
    const S = this.store;
    const agent = S.agents.get(id);
    if (!agent) return;
    brain.phase = "speaking";
    brain.timer = rand(2.5, 4);
    S.setStatus(id, "speaking");
    S.setSpeech(id, pick(DONE_PHRASES));
    S.addEvent("✅", `${agent.name} 完成「${brain.taskName ?? "任務"}」`);
    brain.taskName = null;
  }

  private spawnSubagent(parentId: string) {
    const S = this.store;
    const parent = S.agents.get(parentId);
    if (!parent) return;
    const occupied = new Set(
      [...S.agents.values()].filter((a) => a.homeZone === "hotDesk").map((a) => a.homeSlot),
    );
    const slot = HOT_DESK_SLOTS.findIndex((_, i) => !occupied.has(i));
    if (slot === -1) return;

    const kind = pick(SUB_AGENT_KINDS);
    const id = `sub-${kind.toLowerCase()}-${subSeq++}`;
    const name = `${kind}-${subSeq}`;
    S.spawnAgent({ id, name, role: "subagent", model: "claude-haiku-4-5", homeSlot: slot, parentId });
    S.addEvent("✨", `${parent.name} 派出 subagent ${name}`);
    S.startWalk(id, HOT_DESK_SLOTS[slot], "hotDesk", { arriveStatus: "tool_calling" });
    S.addLink({ sourceId: parentId, targetId: id, strength: 0.6, kind: "spawn" });
    this.brains.set(id, {
      phase: "sub_arriving",
      timer: 0,
      toolsLeft: 0,
      loungeAnchor: -1,
      taskName: null,
    });
  }

  /* ── meetings ── */

  private updateMeetingScheduler(dt: number) {
    const S = this.store;

    if (this.meeting) {
      const m = this.meeting;
      m.timer -= dt;

      if (!m.gathered) {
        const allSeated = m.agentIds.every((id) => {
          const a = S.agents.get(id);
          return a && !a.movement && a.zone === "meeting";
        });
        if (allSeated) {
          m.gathered = true;
          m.speakTimer = 0;
        }
      } else {
        m.speakTimer -= dt;
        if (m.speakTimer <= 0) {
          // rotate speaker
          const prev = m.agentIds[m.speakerIdx % m.agentIds.length];
          S.setSpeech(prev, null);
          S.setStatus(prev, "idle");
          m.speakerIdx += 1;
          const next = m.agentIds[m.speakerIdx % m.agentIds.length];
          S.setStatus(next, "speaking");
          S.setSpeech(next, pick(MEETING_PHRASES));
          m.speakTimer = rand(2, 3.4);
        }
      }

      if (m.timer <= 0 && m.gathered) {
        for (const id of m.agentIds) {
          S.setSpeech(id, null);
          const brain = this.brains.get(id);
          if (brain) {
            brain.phase = "arriving"; // reuse: wait until seated home, then idle
            brain.timer = 0;
          }
        }
        S.addEvent("🏁", "會議結束,各自回工位");
        S.endMeeting(m.id);
        this.meeting = null;
        this.meetingCooldown = rand(30, 55);
      }
      return;
    }

    this.meetingCooldown -= dt;
    if (this.meetingCooldown > 0) return;

    // candidates: settled main agents that are idle at their desk
    const candidates = [...S.agents.values()].filter((a) => {
      const brain = this.brains.get(a.id);
      return (
        a.role !== "subagent" &&
        brain?.phase === "idle" &&
        !a.movement &&
        a.zone === "desk"
      );
    });
    if (candidates.length < 2) {
      this.meetingCooldown = 6;
      return;
    }

    const count = Math.min(candidates.length, 2 + Math.floor(Math.random() * 2));
    const chosen = candidates.slice(0, count).map((a) => a.id);
    const id = `meeting-${meetingSeq++}`;
    for (const agentId of chosen) {
      const brain = this.brains.get(agentId)!;
      brain.phase = "meeting";
    }
    S.addEvent("🤝", `${chosen.length} 位 agent 前往會議區討論`);
    S.startMeeting(id, chosen);
    this.meeting = { id, agentIds: chosen, timer: rand(12, 18), speakTimer: 0, speakerIdx: -1, gathered: false };
  }
}
