import { useCallback, useEffect, useRef, useState } from "react";
import { getChatSocketUrl } from "@/api/social/ws";

export type ChatSocketStatus = "idle" | "connecting" | "open" | "closed" | "error";

export type ChatSocketEvent =
  | { type: "new_chat_message"; message_id: number; message: string; sender: string }
  | { type: "new_reply_chat_message"; message_id: number; message: string; reply_to_id: number; sender: string }
  | { type: "edit_chat_message"; message_id: number; content: string; is_edited: boolean }
  | { type: "delete_chat_message"; message_id: number }
  | { type: "reaction"; message_id: number; emoji: string; user: string; action: "added" | "removed" | "switched" }
  | { type: "typing"; user: string; is_typing: boolean }
  | { type: "stop_typing"; user: string }
  | { type: "error"; error: string };

interface Opts {
  chatId: number | null;
  onEvent?: (event: ChatSocketEvent) => void;
}

const MAX_BACKOFF_MS = 15_000;

export function useChatSocket({ chatId, onEvent }: Opts) {
  const [status, setStatus] = useState<ChatSocketStatus>("idle");
  const [lastEvent, setLastEvent] = useState<ChatSocketEvent | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectAttemptRef = useRef(0);
  const closedByUserRef = useRef(false);
  const onEventRef = useRef(onEvent);

  useEffect(() => {
    onEventRef.current = onEvent;
  }, [onEvent]);

  useEffect(() => {
    if (chatId == null || !Number.isFinite(chatId)) return;
    closedByUserRef.current = false;

    let reconnectTimer: number | undefined;

    function connect() {
      setStatus("connecting");
      const ws = new WebSocket(getChatSocketUrl(chatId!));
      wsRef.current = ws;

      ws.addEventListener("open", () => {
        setStatus("open");
        reconnectAttemptRef.current = 0;
      });

      ws.addEventListener("message", (e) => {
        try {
          const data = JSON.parse(e.data) as ChatSocketEvent;
          setLastEvent(data);
          onEventRef.current?.(data);
        } catch {
          // swallow malformed payloads
        }
      });

      ws.addEventListener("error", () => setStatus("error"));

      ws.addEventListener("close", () => {
        setStatus("closed");
        if (closedByUserRef.current) return;
        const attempt = reconnectAttemptRef.current++;
        const backoff = Math.min(500 * 2 ** attempt, MAX_BACKOFF_MS);
        reconnectTimer = window.setTimeout(connect, backoff);
      });
    }

    connect();

    return () => {
      closedByUserRef.current = true;
      if (reconnectTimer) window.clearTimeout(reconnectTimer);
      wsRef.current?.close();
      wsRef.current = null;
    };
  }, [chatId]);

  const send = useCallback((payload: object) => {
    const ws = wsRef.current;
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(payload));
      return true;
    }
    return false;
  }, []);

  const sendMessage = useCallback(
    (message: string) => send({ type: "new_chat_message", message }),
    [send],
  );

  const sendReply = useCallback(
    (message: string, replyToId: number) =>
      send({ type: "new_reply_chat_message", message, reply_to_id: replyToId }),
    [send],
  );

  const sendReaction = useCallback(
    (messageId: number, emoji: string) =>
      send({ type: "reaction", message_id: messageId, emoji }),
    [send],
  );

  const sendTyping = useCallback(
    (isTyping: boolean) =>
      send({ type: isTyping ? "typing" : "stop_typing", is_typing: isTyping }),
    [send],
  );

  return {
    status,
    lastEvent,
    sendMessage,
    sendReply,
    sendReaction,
    sendTyping,
  };
}
