import { create } from "zustand";
import { DESK_SLOTS, HOT_DESK_SLOTS, meetingSeats } from "@/lib/positions";
import { interpolatePath, planWalkPath, walkDuration } from "@/lib/movement";
import { ENTRANCE, MEETING_CENTER } from "@/lib/constants";
import type {
  AgentStatus,
  AgentZone,
  CollaborationLink,
  Meeting,
  OfficeEvent,
  Point,
  ToolCall,
  VisualAgent,
} from "@/lib/types";

export interface ChatMessage {
  role: "user" | "assistant" | "system";
  text: string;
}

export interface ChatThread {
  messages: ChatMessage[];
  busy: boolean;
}

interface OfficeState {
  agents: Map<string, VisualAgent>;
  links: CollaborationLink[];
  meetings: Meeting[];
  events: OfficeEvent[];
  /** LIVE-mode chat threads, keyed by agent id */
  chats: Map<string, ChatThread>;
  selectedAgentId: string | null;
  theme: "light" | "dark";
  paused: boolean;
  speed: number;
  /** data source: scripted simulation or live Claude Code transcripts */
  mode: "sim" | "live";
  /** side panel visibility (collapse to maximise the office view) */
  panelOpen: boolean;
  /** LIVE-mode chat expanded to a full-screen overlay */
  chatFullscreen: boolean;
  /** simulation clock, seconds */
  clock: number;

  selectAgent: (id: string | null) => void;
  setTheme: (t: "light" | "dark") => void;
  setPaused: (p: boolean) => void;
  setSpeed: (s: number) => void;
  setMode: (m: "sim" | "live") => void;
  togglePanel: () => void;
  setChatFullscreen: (v: boolean) => void;
  clearWorld: () => void;

  chatAppend: (agentId: string, msg: ChatMessage) => void;
  chatSetBusy: (agentId: string, busy: boolean) => void;
  chatClear: (agentId: string) => void;

  addEvent: (icon: string, text: string) => void;

  spawnAgent: (a: {
    id: string;
    name: string;
    role: VisualAgent["role"];
    model: string;
    homeSlot: number;
    parentId?: string;
  }) => void;
  removeAgent: (id: string) => void;
  setStatus: (id: string, status: AgentStatus) => void;
  setTool: (id: string, tool: ToolCall | null) => void;
  setSpeech: (id: string, text: string | null) => void;
  startWalk: (
    id: string,
    to: Point,
    toZone: AgentZone,
    opts?: { arriveStatus?: AgentStatus; despawnOnArrive?: boolean },
  ) => void;
  walkHome: (id: string, arriveStatus?: AgentStatus) => void;

  addLink: (link: CollaborationLink) => void;
  removeLink: (sourceId: string, targetId: string) => void;

  startMeeting: (id: string, agentIds: string[]) => void;
  endMeeting: (id: string) => void;

  /** advance movements + clock; called once per frame with scaled dt */
  tick: (dt: number) => void;
}

let eventSeq = 0;

/* UI preferences persisted across reloads. */
function loadPref(key: string): string | null {
  try {
    return localStorage.getItem(key);
  } catch {
    return null;
  }
}

function savePref(key: string, value: string) {
  try {
    localStorage.setItem(key, value);
  } catch {
    /* ignore */
  }
}

const savedTheme = loadPref("office-theme");
const savedPanel = loadPref("office-panel");

/** Read the preferred data source ("live" | "sim"); the runtime applies it on boot. */
export function loadPreferredMode(): "sim" | "live" {
  return loadPref("office-mode") === "live" ? "live" : "sim";
}

export const useOfficeStore = create<OfficeState>()((set, get) => ({
  agents: new Map(),
  links: [],
  meetings: [],
  events: [],
  chats: new Map(),
  selectedAgentId: null,
  theme: savedTheme === "dark" ? "dark" : "light",
  paused: false,
  speed: 1,
  mode: "sim",
  panelOpen: savedPanel !== "0",
  chatFullscreen: false,
  clock: 0,

  selectAgent: (id) => set({ selectedAgentId: id, chatFullscreen: id === null ? false : get().chatFullscreen }),
  setTheme: (t) => {
    savePref("office-theme", t);
    set({ theme: t });
  },
  setPaused: (p) => set({ paused: p }),
  setSpeed: (s) => set({ speed: s }),
  setMode: (m) => {
    savePref("office-mode", m);
    set({ mode: m });
  },
  togglePanel: () =>
    set((state) => {
      savePref("office-panel", state.panelOpen ? "0" : "1");
      return { panelOpen: !state.panelOpen };
    }),
  setChatFullscreen: (v) => set({ chatFullscreen: v }),
  clearWorld: () =>
    set({
      agents: new Map(),
      links: [],
      meetings: [],
      events: [],
      chats: new Map(),
      selectedAgentId: null,
      chatFullscreen: false,
    }),

  chatAppend: (agentId, msg) =>
    set((state) => {
      const chats = new Map(state.chats);
      const thread = chats.get(agentId) ?? { messages: [], busy: false };
      chats.set(agentId, { ...thread, messages: [...thread.messages, msg].slice(-100) });
      return { chats };
    }),

  chatSetBusy: (agentId, busy) =>
    set((state) => {
      const chats = new Map(state.chats);
      const thread = chats.get(agentId) ?? { messages: [], busy: false };
      chats.set(agentId, { ...thread, busy });
      return { chats };
    }),

  chatClear: (agentId) =>
    set((state) => {
      const chats = new Map(state.chats);
      chats.delete(agentId);
      return { chats };
    }),

  addEvent: (icon, text) =>
    set((state) => ({
      events: [{ at: eventSeq++, text, icon }, ...state.events].slice(0, 60),
    })),

  spawnAgent: ({ id, name, role, model, homeSlot, parentId }) =>
    set((state) => {
      const isSub = role === "subagent";
      const slot = isSub ? HOT_DESK_SLOTS[homeSlot] : DESK_SLOTS[homeSlot];
      const agent: VisualAgent = {
        id,
        name,
        role,
        model,
        status: "spawning",
        position: { ...ENTRANCE },
        zone: "corridor",
        homePosition: { x: slot.x, y: slot.y },
        homeZone: isSub ? "hotDesk" : "desk",
        homeSlot,
        currentTool: null,
        speech: null,
        movement: null,
        parentId: parentId ?? null,
        childIds: [],
        toolCallCount: 0,
        toolHistory: [],
        spawnedAt: state.clock,
      };
      const agents = new Map(state.agents);
      agents.set(id, agent);
      if (parentId) {
        const parent = agents.get(parentId);
        if (parent) agents.set(parentId, { ...parent, childIds: [...parent.childIds, id] });
      }
      return { agents };
    }),

  removeAgent: (id) =>
    set((state) => {
      const agents = new Map(state.agents);
      const agent = agents.get(id);
      agents.delete(id);
      if (agent?.parentId) {
        const parent = agents.get(agent.parentId);
        if (parent) {
          agents.set(agent.parentId, {
            ...parent,
            childIds: parent.childIds.filter((c) => c !== id),
          });
        }
      }
      return {
        agents,
        links: state.links.filter((l) => l.sourceId !== id && l.targetId !== id),
        selectedAgentId: state.selectedAgentId === id ? null : state.selectedAgentId,
      };
    }),

  setStatus: (id, status) =>
    set((state) => {
      const agent = state.agents.get(id);
      if (!agent || agent.status === status) return {};
      const agents = new Map(state.agents);
      agents.set(id, {
        ...agent,
        status,
        speech: status === "speaking" ? agent.speech : null,
        currentTool: status === "tool_calling" ? agent.currentTool : null,
      });
      return { agents };
    }),

  setTool: (id, tool) =>
    set((state) => {
      const agent = state.agents.get(id);
      if (!agent) return {};
      const agents = new Map(state.agents);
      agents.set(id, {
        ...agent,
        currentTool: tool,
        toolCallCount: tool ? agent.toolCallCount + 1 : agent.toolCallCount,
        toolHistory: tool ? [tool.name, ...agent.toolHistory].slice(0, 12) : agent.toolHistory,
      });
      return { agents };
    }),

  setSpeech: (id, text) =>
    set((state) => {
      const agent = state.agents.get(id);
      if (!agent) return {};
      const agents = new Map(state.agents);
      agents.set(id, { ...agent, speech: text });
      return { agents };
    }),

  startWalk: (id, to, toZone, opts) =>
    set((state) => {
      const agent = state.agents.get(id);
      if (!agent) return {};
      const from = agent.movement
        ? interpolatePath(agent.movement.path, agent.movement.elapsed / agent.movement.duration)
        : agent.position;
      const path = planWalkPath(from, to, agent.zone, toZone);
      const agents = new Map(state.agents);
      agents.set(id, {
        ...agent,
        position: { ...from },
        movement: {
          path,
          duration: walkDuration(path),
          elapsed: 0,
          toZone,
          arriveStatus: opts?.arriveStatus,
          despawnOnArrive: opts?.despawnOnArrive,
        },
      });
      return { agents };
    }),

  walkHome: (id, arriveStatus = "idle") => {
    const agent = get().agents.get(id);
    if (!agent) return;
    get().startWalk(id, agent.homePosition, agent.homeZone, { arriveStatus });
  },

  addLink: (link) =>
    set((state) => {
      const others = state.links.filter(
        (l) => !(l.sourceId === link.sourceId && l.targetId === link.targetId),
      );
      return { links: [...others, link] };
    }),

  removeLink: (sourceId, targetId) =>
    set((state) => ({
      links: state.links.filter((l) => !(l.sourceId === sourceId && l.targetId === targetId)),
    })),

  startMeeting: (id, agentIds) => {
    set((state) => ({
      meetings: [...state.meetings, { id, agentIds, center: { ...MEETING_CENTER } }],
    }));
    const seats = meetingSeats(agentIds.length);
    agentIds.forEach((agentId, i) => {
      get().startWalk(agentId, seats[i], "meeting", { arriveStatus: "idle" });
    });
    // pairwise meeting links
    for (let i = 0; i < agentIds.length; i++) {
      for (let j = i + 1; j < agentIds.length; j++) {
        get().addLink({ sourceId: agentIds[i], targetId: agentIds[j], strength: 0.85, kind: "meeting" });
      }
    }
  },

  endMeeting: (id) => {
    const meeting = get().meetings.find((m) => m.id === id);
    if (!meeting) return;
    set((state) => ({ meetings: state.meetings.filter((m) => m.id !== id) }));
    const ids = meeting.agentIds;
    for (let i = 0; i < ids.length; i++) {
      for (let j = i + 1; j < ids.length; j++) {
        get().removeLink(ids[i], ids[j]);
      }
    }
    ids.forEach((agentId) => {
      const agent = get().agents.get(agentId);
      if (agent) get().walkHome(agentId);
    });
  },

  tick: (dt) =>
    set((state) => {
      let agents: Map<string, VisualAgent> | null = null;
      const toRemove: string[] = [];

      for (const [id, agent] of state.agents) {
        if (!agent.movement) continue;
        if (!agents) agents = new Map(state.agents);

        const elapsed = agent.movement.elapsed + dt;
        if (elapsed >= agent.movement.duration) {
          const end = agent.movement.path[agent.movement.path.length - 1];
          if (agent.movement.despawnOnArrive) {
            toRemove.push(id);
            continue;
          }
          agents.set(id, {
            ...agent,
            position: { ...end },
            zone: agent.movement.toZone,
            status: agent.movement.arriveStatus ?? agent.status,
            movement: null,
          });
        } else {
          const pos = interpolatePath(agent.movement.path, elapsed / agent.movement.duration);
          agents.set(id, {
            ...agent,
            position: pos,
            movement: { ...agent.movement, elapsed },
          });
        }
      }

      if (toRemove.length > 0 && agents) {
        for (const id of toRemove) agents.delete(id);
      }

      return {
        clock: state.clock + dt,
        ...(agents ? { agents } : {}),
        ...(toRemove.length > 0
          ? {
              links: state.links.filter(
                (l) => !toRemove.includes(l.sourceId) && !toRemove.includes(l.targetId),
              ),
              selectedAgentId:
                state.selectedAgentId && toRemove.includes(state.selectedAgentId)
                  ? null
                  : state.selectedAgentId,
            }
          : {}),
      };
    }),
}));
