"use client";

import { SSE_EVENT_TYPES } from "@capakraken/shared";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useRef } from "react";
import {
  getTimelineSseInvalidationKeys,
  getTimelineSseResyncKeys,
  parseTimelineSseEvent,
} from "./timelineSsePolicy.js";

/**
 * Connects to the SSE timeline endpoint and invalidates React Query caches
 * when allocation/project change events arrive.
 */
export function useTimelineSSE() {
  const queryClient = useQueryClient();
  const reconnectTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    let es: EventSource | null = null;
    let reconnectAttempts = 0;
    let isDisposed = false;
    let shouldResyncOnOpen = false;
    const canObserveVisibility = typeof document !== "undefined" && "visibilityState" in document;

    function clearReconnectTimer() {
      if (!reconnectTimeout.current) return;
      clearTimeout(reconnectTimeout.current);
      reconnectTimeout.current = null;
    }

    function closeConnection() {
      es?.close();
      es = null;
    }

    function scheduleReconnect() {
      if (isDisposed || reconnectTimeout.current) return;
      reconnectAttempts++;
      const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
      reconnectTimeout.current = setTimeout(() => {
        reconnectTimeout.current = null;
        if (isDisposed) return;
        connect();
      }, delay);
    }

    function connect() {
      if (isDisposed) return;
      if (canObserveVisibility && document.visibilityState === "hidden") return;
      es = new EventSource("/api/sse/timeline");

      es.onopen = () => {
        reconnectAttempts = 0;
        if (!shouldResyncOnOpen) {
          return;
        }

        shouldResyncOnOpen = false;
        for (const queryKey of getTimelineSseResyncKeys()) {
          void queryClient.invalidateQueries({ queryKey });
        }
      };

      es.onmessage = (event) => {
        const eventType = parseTimelineSseEvent(String(event.data));
        if (!eventType) {
          return;
        }

        if (eventType === SSE_EVENT_TYPES.PING) {
          reconnectAttempts = 0;
          return;
        }

        for (const queryKey of getTimelineSseInvalidationKeys(eventType)) {
          void queryClient.invalidateQueries({ queryKey });
        }
      };

      es.onerror = () => {
        shouldResyncOnOpen = true;
        closeConnection();
        scheduleReconnect();
      };
    }

    function handleVisibilityChange() {
      if (!canObserveVisibility) return;
      if (document.visibilityState === "hidden") {
        if (es || reconnectTimeout.current) {
          shouldResyncOnOpen = true;
        }
        clearReconnectTimer();
        closeConnection();
        return;
      }

      reconnectAttempts = 0;
      if (!es) {
        connect();
      }
    }

    if (canObserveVisibility) {
      document.addEventListener("visibilitychange", handleVisibilityChange);
    }

    connect();

    return () => {
      isDisposed = true;
      if (canObserveVisibility) {
        document.removeEventListener("visibilitychange", handleVisibilityChange);
      }
      closeConnection();
      clearReconnectTimer();
    };
  }, [queryClient]);
}
