import { useEffect, useRef, useState, useCallback } from "react";
import { io, Socket } from "socket.io-client";

interface UseSocketOptions {
  reconnection?: boolean;
  reconnectionAttempts?: number;
  reconnectionDelay?: number;
  query?: Record<string, string>;
}

interface UseSocketReturn {
  socket: Socket | null;
  isConnected: boolean;
  error: Error | null;
  emit: (event: string, data?: unknown) => void;
  on: (event: string, callback: (...args: unknown[]) => void) => void;
  off: (event: string, callback?: (...args: unknown[]) => void) => void;
  connect: () => void;
  disconnect: () => void;
}

export const useSocket = (
  url: string,
  token?: string,
  options: UseSocketOptions = {}
): UseSocketReturn => {
  const socketRef = useRef<Socket | null>(null);
  const coreListenersAttachedRef = useRef(false);
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const listenersRef = useRef<Map<string, Set<(...args: unknown[]) => void>>>(
    new Map()
  );

  const {
    reconnection = true,
    reconnectionAttempts = 5,
    reconnectionDelay = 1000,
    query,
  } = options;

  const handleConnect = useCallback(() => {
    setIsConnected(true);
    setError(null);
  }, []);

  const handleDisconnect = useCallback(() => {
    setIsConnected(false);
  }, []);

  const handleConnectError = useCallback((err: Error) => {
    setError(err);
    setIsConnected(false);
  }, []);

  const attachCoreListeners = useCallback(
    (socket: Socket) => {
      if (coreListenersAttachedRef.current) return;
      socket.on("connect", handleConnect);
      socket.on("disconnect", handleDisconnect);
      socket.on("connect_error", handleConnectError);
      coreListenersAttachedRef.current = true;
    },
    [handleConnect, handleDisconnect, handleConnectError]
  );

  const connect = useCallback(() => {
    if (socketRef.current?.connected) return;

    try {
      if (!socketRef.current) {
        const socketOptions: {
          autoConnect: boolean;
          reconnection: boolean;
          reconnectionAttempts: number;
          reconnectionDelay: number;
          transports: string[];
          auth?: { token: string };
          query?: Record<string, string>;
        } = {
          autoConnect: false,
          reconnection,
          reconnectionAttempts,
          reconnectionDelay,
          transports: ["websocket", "polling"], // مثل کد HTML
        };

        if (token) {
          socketOptions.auth = { token };
        }

        if (query) {
          socketOptions.query = query;
        }

        socketRef.current = io(url, socketOptions);
        attachCoreListeners(socketRef.current);
      } else {
        attachCoreListeners(socketRef.current);
      }

      socketRef.current.connect();
    } catch (err) {
      setError(err instanceof Error ? err : new Error("خطا در اتصال به سوکت"));
    }
  }, [
    url,
    token,
    reconnection,
    reconnectionAttempts,
    reconnectionDelay,
    query,
    attachCoreListeners,
  ]);

  const disconnect = useCallback(() => {
    if (socketRef.current) {
      socketRef.current.disconnect();
    }
  }, []);

  const emit = useCallback((event: string, data?: unknown) => {
    if (socketRef.current?.connected) {
      socketRef.current.emit(event, data);
    } else {
      console.warn("سوکت متصل نیست. امکان ارسال داده وجود ندارد.");
    }
  }, []);

  const on = useCallback(
    (event: string, callback: (...args: unknown[]) => void) => {
      if (!socketRef.current) return;

      socketRef.current.on(event, callback);

      if (!listenersRef.current.has(event)) {
        listenersRef.current.set(event, new Set());
      }
      listenersRef.current.get(event)!.add(callback);
    },
    []
  );

  const off = useCallback(
    (event: string, callback?: (...args: unknown[]) => void) => {
      if (!socketRef.current) return;

      if (callback) {
        socketRef.current.off(event, callback);
        listenersRef.current.get(event)?.delete(callback);
      } else {
        socketRef.current.off(event);
        listenersRef.current.delete(event);
      }
    },
    []
  );

  useEffect(() => {
    connect();

    return () => {
      listenersRef.current.forEach((callbacks, event) => {
        callbacks.forEach((callback) => {
          socketRef.current?.off(event, callback);
        });
      });
      listenersRef.current.clear();

      if (socketRef.current) {
        socketRef.current.off("connect", handleConnect);
        socketRef.current.off("disconnect", handleDisconnect);
        socketRef.current.off("connect_error", handleConnectError);
        coreListenersAttachedRef.current = false;
        socketRef.current.disconnect();
        socketRef.current = null;
      }
    };
  }, [connect, handleConnect, handleDisconnect, handleConnectError]);

  return {
    socket: socketRef.current,
    isConnected,
    error,
    emit,
    on,
    off,
    connect,
    disconnect,
  };
};
