import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import type { CardMessageMetadata, ClientMessage, SessionSummary } from "../types";
import {
  createSessionInBackend,
  deleteSessionInBackend,
  fetchSessionDetailFromBackend,
  fetchSessionsFromBackend,
  renameSessionInBackend,
  useBackendActions,
} from "./webrtc/backend";
import { useSessionSurfaceEvents } from "./webrtc/cards";
import { useMessageState } from "./webrtc/messages";
import {
  usePeerConnectionControls,
  useRemoteAudioBindings,
  useRtcRefs,
} from "./webrtc/rtcTransport";
import type { WebRTCState } from "./webrtc/types";

// biome-ignore lint/complexity/noExcessiveLinesPerFunction: this hook owns the app's transport/session state wiring
export function useWebRTC(): WebRTCState {
  const [connected, setConnected] = useState(false);
  const [connecting, setConnecting] = useState(false);
  const [textOnly, setTextOnlyState] = useState(true);
  const [sessions, setSessions] = useState<SessionSummary[]>([]);
  const [activeSessionId, setActiveSessionId] = useState("web");
  const [sessionLoading, setSessionLoading] = useState(false);
  const [textStreaming, setTextStreaming] = useState(false);
  const [voiceStatus, setVoiceStatus] = useState("");
  const [statusVisible, setStatusVisible] = useState(false);
  const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
  const refs = useRtcRefs();
  const statusTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const textOnlyRef = useRef(true);
  const activeSessionIdRef = useRef("web");
  const {
    agentState,
    setAgentState,
    logLines,
    cards,
    workbenchItems,
    appendLine,
    replaceLines,
    clearLines,
    clearCards,
    clearWorkbench,
    dismissCard,
    dismissWorkbenchItem,
    promoteWorkbenchItem,
    loadPersistedCards,
    loadWorkbench,
    onDcMessage,
    onIncomingMessage,
    suppressRtcTextRef,
  } = useMessageState(activeSessionIdRef);
  const { sendTextMessage: sendTextMessageRaw } = useBackendActions({
    onMessage: onIncomingMessage,
    suppressRtcTextRef,
    getActiveSessionId: () => activeSessionIdRef.current,
    setTextStreaming,
  });
  const activeSession = sessions.find((session) => session.chat_id === activeSessionId) ?? null;

  useEffect(() => {
    activeSessionIdRef.current = activeSessionId;
  }, [activeSessionId]);

  const refreshSessions = useCallback(async () => {
    const nextSessions = await fetchSessionsFromBackend();
    if (nextSessions) setSessions(nextSessions);
    return nextSessions;
  }, []);

  const setTextOnly = useCallback((enabled: boolean) => {
    textOnlyRef.current = enabled;
    setTextOnlyState(enabled);
  }, []);

  const showStatus = useCallback((text: string, persistMs = 0) => {
    setVoiceStatus(text);
    setStatusVisible(true);
    if (statusTimerRef.current) clearTimeout(statusTimerRef.current);
    if (persistMs > 0) {
      statusTimerRef.current = setTimeout(() => setStatusVisible(false), persistMs);
    }
  }, []);

  const sendJson = useCallback(
    (msg: ClientMessage) => {
      const dc = refs.dcRef.current;
      if (dc?.readyState !== "open") return;
      if (msg.type === "command" || msg.type === "voice-ptt") {
        dc.send(JSON.stringify({ ...msg, chat_id: activeSessionIdRef.current }));
        return;
      }
      dc.send(JSON.stringify(msg));
    },
    [refs.dcRef],
  );

  const sendTextMessage = useCallback(
    async (text: string, metadata?: CardMessageMetadata) => {
      await sendTextMessageRaw(text, metadata);
      await refreshSessions();
    },
    [refreshSessions, sendTextMessageRaw],
  );

  const switchSession = useCallback(async (chatId: string) => {
    if (!chatId || chatId === activeSessionIdRef.current) return;
    activeSessionIdRef.current = chatId;
    setActiveSessionId(chatId);
  }, []);

  const createSession = useCallback(
    async (title = "") => {
      setSessionLoading(true);
      try {
        const created = await createSessionInBackend(title);
        const nextSessions = await refreshSessions();
        if (!created) return;
        const nextSessionId = created.chat_id;
        activeSessionIdRef.current = nextSessionId;
        setActiveSessionId(nextSessionId);
        if (nextSessions && !nextSessions.some((session) => session.chat_id === nextSessionId)) {
          setSessions((prev) => [created, ...prev]);
        }
      } finally {
        setSessionLoading(false);
      }
    },
    [refreshSessions],
  );

  const renameSession = useCallback(
    async (chatId: string, title: string) => {
      if (!chatId) return;
      setSessionLoading(true);
      try {
        const updated = await renameSessionInBackend(chatId, title);
        const nextSessions = await refreshSessions();
        if (!updated) return;
        if (nextSessions && !nextSessions.some((session) => session.chat_id === updated.chat_id)) {
          setSessions((prev) => [
            updated,
            ...prev.filter((session) => session.chat_id !== updated.chat_id),
          ]);
        }
      } finally {
        setSessionLoading(false);
      }
    },
    [refreshSessions],
  );

  const deleteSession = useCallback(
    async (chatId: string) => {
      if (!chatId) return;
      setSessionLoading(true);
      try {
        const deleted = await deleteSessionInBackend(chatId);
        const nextSessions = await refreshSessions();
        if (!deleted) return;

        if (chatId !== activeSessionIdRef.current) return;

        const fallbackSessionId =
          nextSessions?.find((session) => session.chat_id !== chatId)?.chat_id || "web";
        activeSessionIdRef.current = fallbackSessionId;
        setActiveSessionId(fallbackSessionId);
      } finally {
        setSessionLoading(false);
      }
    },
    [refreshSessions],
  );

  useEffect(() => {
    refreshSessions().then((nextSessions) => {
      if (
        !nextSessions ||
        nextSessions.some((session) => session.chat_id === activeSessionIdRef.current)
      ) {
        return;
      }
      if (nextSessions[0]?.chat_id) {
        activeSessionIdRef.current = nextSessions[0].chat_id;
        setActiveSessionId(nextSessions[0].chat_id);
      }
    });
  }, [refreshSessions]);

  useEffect(() => {
    let cancelled = false;

    const loadSession = async () => {
      setSessionLoading(true);
      setAgentState(() => "idle");
      clearLines();
      clearCards();
      clearWorkbench();

      try {
        const detail = await fetchSessionDetailFromBackend(activeSessionId);
        if (cancelled || activeSessionIdRef.current !== activeSessionId) return;
        if (detail) {
          replaceLines(detail.messages);
          setSessions((prev) => {
            const filtered = prev.filter((session) => session.chat_id !== detail.session.chat_id);
            return [detail.session, ...filtered].sort((a, b) =>
              b.updated_at.localeCompare(a.updated_at),
            );
          });
        }
        await loadPersistedCards();
        await loadWorkbench();
      } finally {
        if (!cancelled && activeSessionIdRef.current === activeSessionId) {
          setSessionLoading(false);
        }
      }
    };

    loadSession().catch((err) => {
      console.warn("[sessions] failed to load session", err);
      if (!cancelled && activeSessionIdRef.current === activeSessionId) {
        setSessionLoading(false);
      }
    });

    return () => {
      cancelled = true;
    };
  }, [
    activeSessionId,
    clearCards,
    clearLines,
    clearWorkbench,
    loadPersistedCards,
    loadWorkbench,
    replaceLines,
    setAgentState,
  ]);

  useSessionSurfaceEvents({
    activeSessionId,
    loadPersistedCards,
    loadWorkbench,
    refreshSessions,
    setSessions,
  });
  useRemoteAudioBindings({
    textOnly,
    connected,
    showStatus,
    remoteAudioRef: refs.remoteAudioRef,
    micSendersRef: refs.micSendersRef,
    dcRef: refs.dcRef,
    textOnlyRef,
  });
  const { connect } = usePeerConnectionControls({
    textOnly,
    connected,
    appendLine,
    onDcMessage,
    loadPersistedCards,
    loadWorkbench,
    showStatus,
    refs,
    setConnected,
    setConnecting,
    setRemoteStream,
    textOnlyRef,
  });

  return {
    connected,
    connecting,
    textOnly,
    agentState,
    logLines,
    cards,
    workbenchItems,
    sessions,
    activeSessionId,
    activeSession,
    sessionLoading,
    textStreaming,
    voiceStatus,
    statusVisible,
    remoteAudioEl: refs.remoteAudioRef.current,
    remoteStream,
    sendJson,
    sendTextMessage,
    dismissCard,
    dismissWorkbenchItem,
    promoteWorkbenchItem,
    setTextOnly,
    switchSession,
    createSession,
    renameSession,
    deleteSession,
    connect,
  };
}
