import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  useCallback,
  ReactNode,
} from "react";
import { Socket } from "socket.io-client";
import socketService from "../services/socket";

/**
 * Socket Context Type
 */
interface SocketContextType {
  socket: Socket | null;
  isConnected: boolean;
  joinConversation: (otherUserId: string) => void;
  leaveConversation: (otherUserId: string) => void;
  emitTypingStart: (receiverId: string) => void;
  emitTypingStop: (receiverId: string) => void;
  emitMarkMessageRead: (messageId: string, senderId: string) => void;
  onNewMessage: (callback: (message: any) => void) => () => void;
  onMessageRead: (
    callback: (data: {
      messageId: string;
      readAt: string;
      readBy: string;
    }) => void
  ) => () => void;
  onUserTyping: (
    callback: (data: {
      userId: string;
      firstName: string;
      isTyping: boolean;
    }) => void
  ) => () => void;
}

/**
 * Create Socket Context
 */
const SocketContext = createContext<SocketContextType | undefined>(undefined);

/**
 * Socket Provider Props
 */
interface SocketProviderProps {
  children: ReactNode;
  isAuthenticated?: boolean;
}

/**
 * Socket Provider Component
 * Manages socket connection lifecycle and provides socket functionality to children
 */
export const SocketProvider: React.FC<SocketProviderProps> = ({
  children,
  isAuthenticated = false,
}) => {
  const [socket, setSocket] = useState<Socket | null>(null);
  const [isConnected, setIsConnected] = useState(false);

  /**
   * Initialize socket connection when user is authenticated
   */
  useEffect(() => {
    if (!isAuthenticated) {
      return;
    }

    const newSocket = socketService.connect();
    setSocket(newSocket);

    // Listen for connection status changes
    const removeListener = socketService.addConnectionListener((connected) => {
      setIsConnected(connected);
    });

    // Cleanup on unmount
    return () => {
      removeListener();
    };
  }, [isAuthenticated]);

  /**
   * Disconnect socket when user logs out
   */
  useEffect(() => {
    if (!isAuthenticated && socket) {
      socketService.disconnect();
      setSocket(null);
      setIsConnected(false);
    }
  }, [isAuthenticated, socket]);

  /**
   * Join a conversation room
   */
  const joinConversation = useCallback((otherUserId: string) => {
    socketService.joinConversation(otherUserId);
  }, []);

  /**
   * Leave a conversation room
   */
  const leaveConversation = useCallback((otherUserId: string) => {
    socketService.leaveConversation(otherUserId);
  }, []);

  /**
   * Emit typing start event
   */
  const emitTypingStart = useCallback((receiverId: string) => {
    socketService.emitTypingStart(receiverId);
  }, []);

  /**
   * Emit typing stop event
   */
  const emitTypingStop = useCallback((receiverId: string) => {
    socketService.emitTypingStop(receiverId);
  }, []);

  /**
   * Emit mark message as read event
   */
  const emitMarkMessageRead = useCallback(
    (messageId: string, senderId: string) => {
      socketService.emitMarkMessageRead(messageId, senderId);
    },
    []
  );

  /**
   * Listen for new messages
   */
  const onNewMessage = useCallback((callback: (message: any) => void) => {
    return socketService.onNewMessage(callback);
  }, []);

  /**
   * Listen for message read events
   */
  const onMessageRead = useCallback(
    (
      callback: (data: {
        messageId: string;
        readAt: string;
        readBy: string;
      }) => void
    ) => {
      return socketService.onMessageRead(callback);
    },
    []
  );

  /**
   * Listen for typing indicators
   */
  const onUserTyping = useCallback(
    (
      callback: (data: {
        userId: string;
        firstName: string;
        isTyping: boolean;
      }) => void
    ) => {
      return socketService.onUserTyping(callback);
    },
    []
  );

  const value: SocketContextType = {
    socket,
    isConnected,
    joinConversation,
    leaveConversation,
    emitTypingStart,
    emitTypingStop,
    emitMarkMessageRead,
    onNewMessage,
    onMessageRead,
    onUserTyping,
  };

  return (
    <SocketContext.Provider value={value}>{children}</SocketContext.Provider>
  );
};

/**
 * Custom hook to use Socket Context
 * @throws Error if used outside of SocketProvider
 */
export const useSocket = (): SocketContextType => {
  const context = useContext(SocketContext);

  if (context === undefined) {
    throw new Error("useSocket must be used within a SocketProvider");
  }

  return context;
};

export default SocketContext;
