import { useCallback, createContext, useEffect, useRef } from "react";

import { channelSocket, type WsOutEvent } from "@/socket";
import { useAuth } from "@/context/auth-context";

type Handler = (event: WsOutEvent) => void;

const ChannelSocketContext = createContext<null>(null);

/**
 * Global channel socket lifecycle.
 * Connects once when the user is authenticated and keeps the connection alive
 * for the entire app lifetime. Unmounting the provider disconnects.
 */
export function ChannelSocketProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const { isAuthenticated, isLoading } = useAuth();
  const connectedRef = useRef(false);

  useEffect(() => {
    if (isLoading) return;
    if (isAuthenticated && !connectedRef.current) {
      connectedRef.current = true;
      channelSocket.connect();
    }
  }, [isAuthenticated, isLoading]);

  // Disconnect only on page unload
  useEffect(() => {
    const handleUnload = () => channelSocket.disconnect();
    window.addEventListener("beforeunload", handleUnload);
    return () => {
      window.removeEventListener("beforeunload", handleUnload);
      channelSocket.disconnect();
      connectedRef.current = false;
    };
  }, []);

  return <ChannelSocketContext value={null}>{children}</ChannelSocketContext>;
}

/**
 * Subscribe to channel socket events.
 * Does NOT manage connection lifecycle — that's the provider's job.
 */
export function useChannelSocket() {
  const handlersRef = useRef(new Set<Handler>());

  // Register the single relay handler once (lazy ref init)
  const relayRef = useRef<(() => void) | null>(null);
  // eslint-disable-next-line react-hooks/refs
  if (!relayRef.current) {
    relayRef.current = channelSocket.on((event) => {
      for (const handler of handlersRef.current) {
        handler(event);
      }
    });
  }

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      relayRef.current?.();
      relayRef.current = null;
    };
  }, []);

  const subscribe = useCallback(async (roomId: string) => {
    await channelSocket.subscribe(roomId);
  }, []);

  const unsubscribe = useCallback(async (roomId: string) => {
    await channelSocket.unsubscribe(roomId);
  }, []);

  const send = useCallback(async <T = unknown>(message: {
    type: string;
    [key: string]: unknown;
  }) => {
    return channelSocket.request<T>(message);
  }, []);

  const onEvent = useCallback((handler: Handler) => {
    handlersRef.current.add(handler);
    return () => {
      handlersRef.current.delete(handler);
    };
  }, []);

  return { subscribe, unsubscribe, send, onEvent };
}

export type { WsOutEvent };
