import {
  createContext,
  type FC,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { type LegendListRef } from '@legendapp/list/react';
import { useTheme } from '@material-hu/mui/styles';

import { useStableCallback } from 'src/hooks/useStableCallback';
import { type ConversationMessage } from 'src/pages/dashboard/Conversations/types';

import { getConversationListMessages } from '../queries';

export type ScrollToMessageConversationContext = {
  scrollToMessage: (messageId: string) => void;
  isScrollingToMessage: boolean;
};

export const ScrollToMessageConversationContext =
  createContext<ScrollToMessageConversationContext>({
    scrollToMessage: () => null,
    isScrollingToMessage: false,
  });

type ScrollToMessageProviderProps = {
  messages: ConversationMessage[];
  conversationId: string;
  fetchNextPage: () => Promise<unknown>;
  userId: number | undefined;
  hasNextPage: boolean;
  listRef: React.RefObject<LegendListRef>;
};

export const ScrollToMessageConversationProvider: FC<
  React.PropsWithChildren<ScrollToMessageProviderProps>
> = props => {
  const {
    children,
    messages,
    fetchNextPage,
    conversationId,
    userId,
    hasNextPage,
    listRef,
  } = props;
  const theme = useTheme();
  const isFetchingRef = useRef(false);
  const scrollTokenRef = useRef(0);
  const [isScrollingToMessage, setIsScrollingToMessage] = useState(false);
  const highlightedElementRef = useRef<{
    element: HTMLElement;
    originalBackground: string;
  } | null>(null);

  // Reset state when conversation changes
  useEffect(() => {
    const resetState = () => {
      scrollTokenRef.current++;
      isFetchingRef.current = false;
      setIsScrollingToMessage(false);
      if (highlightedElementRef.current) {
        highlightedElementRef.current.element.style.backgroundColor =
          highlightedElementRef.current.originalBackground;
        highlightedElementRef.current = null;
      }
    };
    resetState();
    return resetState;
  }, [conversationId]);

  const isRequestActive = (requestToken: number) =>
    scrollTokenRef.current === requestToken;

  const tryScrollToMessage = useCallback(
    async (
      messageId: string,
      messagesList: ConversationMessage[],
      requestToken: number,
    ) => {
      // First verify message exists in the provided list
      const foundMessage = messagesList.find(
        message => message.hu_data.message_ts === messageId,
      );
      if (!foundMessage || !userId) return false;

      const listRefCurrent = listRef.current;
      if (!listRefCurrent) return false;

      const listState = listRefCurrent.getState();
      if (!listState?.data) return false;

      // Find index in virtualized list and verify it's the correct message
      const messageIndex = listState.data.findIndex(
        message => message.hu_data.message_ts === messageId,
      );
      const messageAtIndex = listState.data[messageIndex];

      // Only scroll if the message at that index matches
      if (
        messageIndex >= 0 &&
        messageAtIndex?.hu_data?.message_ts === messageId
      ) {
        const scrollPromise = listRefCurrent.scrollToIndex({
          index: messageIndex,
          animated: true,
          viewPosition: 0.5,
        });

        // Poll for DOM element (list renders asynchronously after scroll)
        const intervalId = setInterval(() => {
          if (!isRequestActive(requestToken)) {
            clearInterval(intervalId);
            return;
          }

          const messageElement = (
            listState?.elementAtIndex(messageIndex) as HTMLElement
          )?.querySelector('.message-bubble') as HTMLElement;

          if (!messageElement) return;

          clearInterval(intervalId);

          // Clear previous background highlight
          if (highlightedElementRef.current) {
            highlightedElementRef.current.element.style.backgroundColor =
              highlightedElementRef.current.originalBackground;
          }

          const originalBackground = messageElement.style.backgroundColor;
          const isLoggedUser = userId === Number(foundMessage.user);
          const highlightColor = isLoggedUser
            ? theme.palette.new.border.neutral.brand
            : theme.palette.new.background.layout.default;

          highlightedElementRef.current = {
            element: messageElement,
            originalBackground,
          };
          messageElement.style.backgroundColor = highlightColor || '';

          // Auto-clear background highlight after 1s
          setTimeout(() => {
            if (highlightedElementRef.current?.element === messageElement) {
              messageElement.style.backgroundColor = originalBackground;
              highlightedElementRef.current = null;
            }
          }, 1000);
        }, 100);

        // Wait for scroll to finish only if the message is outside the visible window
        // otherwise it would flash when scrolling to close messages
        let distanceFromVisibleWindow = 0;

        if (messageIndex < listState.start) {
          distanceFromVisibleWindow = listState.start - messageIndex;
        } else if (messageIndex > listState.end) {
          distanceFromVisibleWindow = messageIndex - listState.end;
        }

        // Number of messages outside the visible window to be considered as significant
        const significantDistanceThreshold = 4;
        const shouldSetScrollingState =
          distanceFromVisibleWindow > significantDistanceThreshold;

        if (shouldSetScrollingState && isRequestActive(requestToken)) {
          // Wait for the scroll to complete
          await scrollPromise;
        }

        return true;
      }

      return false;
    },
    [listRef, userId],
  );

  const scrollToMessage = useStableCallback(async (messageId: string) => {
    const requestToken = ++scrollTokenRef.current;
    setIsScrollingToMessage(true);

    try {
      if (!(await tryScrollToMessage(messageId, messages, requestToken))) {
        while (hasNextPage && isRequestActive(requestToken)) {
          if (isFetchingRef.current) {
            await new Promise(resolve => setTimeout(resolve, 16));
            continue;
          }

          isFetchingRef.current = true;
          try {
            await fetchNextPage();
          } finally {
            isFetchingRef.current = false;
          }

          if (!isRequestActive(requestToken)) {
            break;
          }

          // Wait for the list to be updated with the new messages
          await new Promise(resolve => setTimeout(resolve, 16));

          const messagesCache = getConversationListMessages(conversationId);

          if (
            !isRequestActive(requestToken) ||
            (await tryScrollToMessage(messageId, messagesCache, requestToken))
          ) {
            break;
          }
        }
      }
    } finally {
      if (isRequestActive(requestToken)) {
        setIsScrollingToMessage(false);
      }
    }
  });

  const contextValue = useMemo(
    () => ({
      scrollToMessage,
      isScrollingToMessage,
    }),
    [isScrollingToMessage, scrollToMessage],
  );

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

export const ScrollToMessageConversationConsumer =
  ScrollToMessageConversationContext.Consumer;

export const useScrollToMessageConversation = () =>
  useContext(ScrollToMessageConversationContext);

export default ScrollToMessageConversationContext;
