import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import type {
  AgentState,
  CardItem,
  LogLine,
  ServerMessage,
  SessionHistoryLine,
  WorkbenchItem,
} from "../../types";
import { useCardsState, useWorkbenchState } from "./cards";
import type { AppendLine, IdleFallbackControls, SetAgentState, UpsertCard } from "./types";

let logIdCounter = 0;

function hydrateLogLines(entries: SessionHistoryLine[]): LogLine[] {
  return entries.map((entry) => ({
    id: logIdCounter++,
    role: entry.role,
    text: entry.text,
    timestamp: entry.timestamp || new Date().toISOString(),
    contextLabel: undefined,
  }));
}

function appendLogLineEntry(
  prev: LogLine[],
  role: string,
  text: string,
  timestamp: string,
  contextLabel?: string,
): LogLine[] {
  const next = [
    ...prev,
    {
      id: logIdCounter++,
      role,
      text,
      timestamp: timestamp || new Date().toISOString(),
      contextLabel: contextLabel?.trim() || undefined,
    },
  ];
  return next.length > 250 ? next.slice(next.length - 250) : next;
}

function appendStreamingAssistantEntry(
  prev: LogLine[],
  text: string,
  timestamp: string,
): LogLine[] {
  const nextTimestamp = timestamp || new Date().toISOString();
  const last = prev[prev.length - 1];
  if (last?.role === "nanobot-progress") {
    const next = [...prev];
    next[next.length - 1] = {
      ...last,
      text: `${last.text}${text}`,
      timestamp: nextTimestamp,
    };
    return next;
  }
  return appendLogLineEntry(prev, "nanobot-progress", text, nextTimestamp);
}

function finalizeStreamingAssistantEntry(
  prev: LogLine[],
  text: string,
  timestamp: string,
): LogLine[] {
  const nextTimestamp = timestamp || new Date().toISOString();
  const last = prev[prev.length - 1];
  if (last?.role === "nanobot-progress") {
    const next = [...prev];
    next[next.length - 1] = {
      ...last,
      role: "nanobot",
      text,
      timestamp: nextTimestamp,
    };
    return next;
  }
  return appendLogLineEntry(prev, "nanobot", text, nextTimestamp);
}

interface MessageHandlers {
  setAgentState: SetAgentState;
  appendLine: AppendLine;
  appendAssistantDelta: AppendLine;
  finalizeAssistantLine: AppendLine;
  upsertCard: UpsertCard;
  upsertWorkbench: (item: WorkbenchItem) => void;
  idleFallback: IdleFallbackControls;
}

function handleAgentStateMessage(
  msg: Extract<ServerMessage, { type: "agent_state" }>,
  handlers: Pick<MessageHandlers, "idleFallback" | "setAgentState">,
): void {
  handlers.idleFallback.clear();
  handlers.setAgentState(() => msg.state);
}

function handleTextMessage(
  msg: Extract<ServerMessage, { type: "message" }>,
  handlers: Pick<
    MessageHandlers,
    "appendAssistantDelta" | "appendLine" | "finalizeAssistantLine" | "idleFallback"
  >,
): void {
  if (msg.is_tool_hint) {
    handlers.appendLine("tool", msg.content, msg.timestamp);
    return;
  }
  if (msg.is_progress) {
    handlers.appendAssistantDelta("nanobot-progress", msg.content, msg.timestamp);
    return;
  }
  handlers.finalizeAssistantLine(msg.role, msg.content, msg.timestamp, msg.context_label);
  handlers.idleFallback.schedule();
}

function toIncomingCard(msg: Extract<ServerMessage, { type: "card" }>): Omit<CardItem, "id"> {
  return {
    serverId: msg.id,
    kind: msg.kind,
    content: msg.content,
    title: msg.title,
    question: msg.question || undefined,
    choices: msg.choices.length > 0 ? msg.choices : undefined,
    responseValue: msg.response_value || undefined,
    slot: msg.slot || undefined,
    lane: msg.lane,
    priority: msg.priority,
    state: msg.state,
    templateKey: msg.template_key || undefined,
    templateState:
      msg.template_state && typeof msg.template_state === "object" ? msg.template_state : undefined,
    contextSummary: msg.context_summary || undefined,
    createdAt: msg.created_at || new Date().toISOString(),
    updatedAt: msg.updated_at || new Date().toISOString(),
  };
}

function toIncomingWorkbench(msg: Extract<ServerMessage, { type: "workbench" }>): WorkbenchItem {
  return {
    id: msg.id,
    chatId: msg.chat_id,
    kind: msg.kind,
    title: msg.title,
    content: msg.content,
    question: msg.question || undefined,
    choices: msg.choices.length > 0 ? msg.choices : undefined,
    responseValue: msg.response_value || undefined,
    slot: msg.slot || undefined,
    templateKey: msg.template_key || undefined,
    templateState:
      msg.template_state && typeof msg.template_state === "object" ? msg.template_state : undefined,
    contextSummary: msg.context_summary || undefined,
    promotable: msg.promotable !== false,
    sourceCardId: msg.source_card_id || undefined,
    createdAt: msg.created_at || new Date().toISOString(),
    updatedAt: msg.updated_at || new Date().toISOString(),
  };
}

function handleTypedMessage(
  msg: Extract<ServerMessage, { type: string }>,
  handlers: MessageHandlers,
): void {
  switch (msg.type) {
    case "agent_state":
      handleAgentStateMessage(msg, handlers);
      return;
    case "message":
      handleTextMessage(msg, handlers);
      return;
    case "card":
      handlers.upsertCard(toIncomingCard(msg));
      handlers.idleFallback.schedule();
      return;
    case "workbench":
      handlers.upsertWorkbench(toIncomingWorkbench(msg));
      handlers.idleFallback.schedule();
      return;
    case "error":
      handlers.appendLine("system", msg.error, "");
      handlers.idleFallback.schedule();
      return;
    default:
      return;
  }
}

function parseServerMessage(raw: string): Extract<ServerMessage, { type: string }> | null {
  let msg: ServerMessage;
  try {
    msg = JSON.parse(raw);
  } catch {
    return null;
  }
  if (typeof msg !== "object" || msg === null || !("type" in msg)) return null;
  return msg as Extract<ServerMessage, { type: string }>;
}

function useIdleFallback(setAgentState: SetAgentState): IdleFallbackControls {
  const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const clear = useCallback(() => {
    if (!idleTimerRef.current) return;
    clearTimeout(idleTimerRef.current);
    idleTimerRef.current = null;
  }, []);

  const schedule = useCallback(
    (delayMs = 450) => {
      clear();
      idleTimerRef.current = setTimeout(() => {
        idleTimerRef.current = null;
        setAgentState((prev) => {
          if (prev === "listening" || prev === "speaking") return prev;
          return "idle";
        });
      }, delayMs);
    },
    [clear, setAgentState],
  );

  useEffect(() => clear, [clear]);
  return { clear, schedule };
}

function useLogState() {
  const [logLines, setLogLines] = useState<LogLine[]>([]);

  const appendLine = useCallback(
    (role: string, text: string, timestamp: string, contextLabel?: string) => {
      setLogLines((prev) => appendLogLineEntry(prev, role, text, timestamp, contextLabel));
    },
    [],
  );

  const appendAssistantDelta = useCallback((_: string, text: string, timestamp: string) => {
    setLogLines((prev) => appendStreamingAssistantEntry(prev, text, timestamp));
  }, []);

  const finalizeAssistantLine = useCallback(
    (role: string, text: string, timestamp: string, contextLabel?: string) => {
      if (role !== "nanobot") {
        setLogLines((prev) => appendLogLineEntry(prev, role, text, timestamp, contextLabel));
        return;
      }
      setLogLines((prev) => finalizeStreamingAssistantEntry(prev, text, timestamp));
    },
    [],
  );

  const replaceLines = useCallback((entries: SessionHistoryLine[]) => {
    setLogLines(hydrateLogLines(entries));
  }, []);

  const clearLines = useCallback(() => {
    setLogLines([]);
  }, []);

  return {
    logLines,
    appendLine,
    appendAssistantDelta,
    finalizeAssistantLine,
    replaceLines,
    clearLines,
  };
}

function useIncomingMessages({
  setAgentState,
  appendLine,
  appendAssistantDelta,
  finalizeAssistantLine,
  upsertCard,
  upsertWorkbench,
  idleFallback,
}: {
  setAgentState: SetAgentState;
  appendLine: AppendLine;
  appendAssistantDelta: AppendLine;
  finalizeAssistantLine: AppendLine;
  upsertCard: UpsertCard;
  upsertWorkbench: (item: WorkbenchItem) => void;
  idleFallback: IdleFallbackControls;
}) {
  return useCallback(
    (raw: string) => {
      const msg = parseServerMessage(raw);
      if (!msg) return;
      handleTypedMessage(msg, {
        setAgentState,
        appendLine,
        appendAssistantDelta,
        finalizeAssistantLine,
        upsertCard,
        upsertWorkbench,
        idleFallback,
      });
    },
    [
      appendAssistantDelta,
      appendLine,
      finalizeAssistantLine,
      idleFallback,
      setAgentState,
      upsertCard,
      upsertWorkbench,
    ],
  );
}

export function useMessageState(activeSessionIdRef: { current: string }) {
  const [agentState, setAgentState] = useState<AgentState>("idle");
  const {
    logLines,
    appendLine,
    appendAssistantDelta,
    finalizeAssistantLine,
    replaceLines,
    clearLines,
  } = useLogState();
  const { cards, upsertCard, dismissCard, loadPersistedCards, clearCards } =
    useCardsState(activeSessionIdRef);
  const {
    workbenchItems,
    upsertWorkbench,
    dismissWorkbenchItem,
    promoteWorkbenchItem,
    loadWorkbench,
    clearWorkbench,
  } = useWorkbenchState(activeSessionIdRef);
  const idleFallback = useIdleFallback(setAgentState);
  const suppressRtcTextRef = useRef(0);
  const onIncomingMessage = useIncomingMessages({
    setAgentState,
    appendLine,
    appendAssistantDelta,
    finalizeAssistantLine,
    upsertCard,
    upsertWorkbench,
    idleFallback,
  });
  const onDcMessage = useCallback(
    (raw: string) => {
      const msg = parseServerMessage(raw);
      if (!msg) return;
      if (
        suppressRtcTextRef.current > 0 &&
        (msg.type === "message" || msg.type === "agent_state" || msg.type === "card")
      ) {
        return;
      }
      handleTypedMessage(msg, {
        setAgentState,
        appendLine,
        appendAssistantDelta,
        finalizeAssistantLine,
        upsertCard,
        upsertWorkbench,
        idleFallback,
      });
    },
    [
      appendAssistantDelta,
      appendLine,
      finalizeAssistantLine,
      idleFallback,
      setAgentState,
      upsertCard,
      upsertWorkbench,
    ],
  );

  return {
    agentState,
    setAgentState,
    logLines,
    cards,
    workbenchItems,
    appendLine,
    replaceLines,
    clearLines,
    clearCards,
    clearWorkbench,
    dismissCard,
    dismissWorkbenchItem,
    promoteWorkbenchItem,
    loadPersistedCards,
    loadWorkbench,
    onDcMessage,
    onIncomingMessage,
    suppressRtcTextRef,
  };
}
