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_id: number; sender: string; sender_avatar: string | null; reply_to_id?: number; media_files?: Array<{ id: number; file: string; media_type: string; uploaded_at: string }> }
  | { type: "new_reply_chat_message"; message_id: number; message: string; reply_to_id: number; sender_id: number; sender: string; sender_avatar: string | null; media_files?: Array<{ id: number; file: string; media_type: string; uploaded_at: 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; user_id: number; action: "added" | "removed" | "switched" }
  | { type: "typing"; user: string; is_typing: boolean }
  | { type: "stop_typing"; user: string }
  | { type: "read_status"; user: string; chat_id: number }
  | { 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;
    // Guards against React StrictMode double-invoke: events from the first
    // (torn-down) WebSocket are silently dropped after cleanup runs.
    let mounted = true;

    let reconnectTimer: number | undefined;

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

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

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

      ws.addEventListener("error", () => { if (mounted) setStatus("error"); });

      ws.addEventListener("close", () => {
        if (!mounted) return;
        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 () => {
      mounted = false;
      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],
  );

  const sendMarkRead = useCallback(() => send({ type: "mark_read" }), [send]);

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