import {useCallback, useEffect, useRef} from 'react';
import {InteractionManager} from 'react-native';
import {ExtendedMessage} from '@modules/chats/interfaces';
import {useMessagesListRef} from '@modules/chats/contexts';
import {useScrollToMessageActions} from '@modules/chats/store';

import {useViewPort} from './components/ViewPortProvider';

export interface ScrollToMessageData {
  data: {
    ts: string;
    client_msg_id: string;
  };
  id: string;
}

type ScrollState =
  | {status: 'idle'}
  | {status: 'loading'; ts: string}
  | {status: 'scrolling'; ts: string}
  | {status: 'awaiting_visibility'; ts: string};

interface MessagesEdges {
  first: Nullable<string>;
  last: Nullable<string>;
}

const NOOP = () => {};

const getMessagesEdges = (messages: ExtendedMessage[]): MessagesEdges => ({
  first: messages[0]?.ts ?? null,
  last: messages[messages.length - 1]?.ts ?? null,
});

const findMessageIndex = (messages: ExtendedMessage[], ts: string): number =>
  messages.findIndex(message => message.ts === ts);

const isMessageVisible = (
  viewPort: ReturnType<ReturnType<typeof useViewPort>['getViewPort']>,
  ts: string,
): boolean => viewPort.some(item => item.item.ts === ts && item.isViewable);

export function useScrollToMessage({
  channel,
  extendedMessages,
}: {
  channel: string;
  extendedMessages: ExtendedMessage[];
}) {
  const {startScrollToMessage, setTargetMessageId} =
    useScrollToMessageActions();

  const legendRef = useMessagesListRef();
  const {getViewPort, subscribe} = useViewPort();

  const extendedMessagesRef = useRef(extendedMessages);
  extendedMessagesRef.current = extendedMessages;

  const stateRef = useRef<ScrollState>({status: 'idle'});
  const viewPositionRef = useRef<number>(0);
  const prevEdgesRef = useRef<MessagesEdges>({first: null, last: null});
  const finishCallbackRef = useRef<() => void>(NOOP);
  const interactionHandleRef = useRef<ReturnType<
    typeof InteractionManager.runAfterInteractions
  > | null>(null);
  const unsubscribeRef = useRef<(() => void) | null>(null);

  const cleanup = useCallback(() => {
    interactionHandleRef.current?.cancel();
    interactionHandleRef.current = null;
    unsubscribeRef.current?.();
    unsubscribeRef.current = null;
    finishCallbackRef.current();
    finishCallbackRef.current = NOOP;
  }, []);

  const resetState = useCallback(() => {
    if (stateRef.current.status !== 'idle') {
      cleanup();
    }
    stateRef.current = {status: 'idle'};
  }, [cleanup]);

  const triggerHighlight = useCallback(
    (ts: string) => {
      setTargetMessageId(ts);
      resetState();
    },
    [resetState, setTargetMessageId],
  );

  const checkScrollProgress = useCallback(
    (viewPort: ReturnType<typeof getViewPort>) => {
      const state = stateRef.current;

      if (state.status !== 'awaiting_visibility') {
        return;
      }

      if (isMessageVisible(viewPort, state.ts)) {
        triggerHighlight(state.ts);
      }
    },
    [triggerHighlight],
  );

  const subscribeToViewPort = useCallback(() => {
    if (unsubscribeRef.current) {
      return;
    }

    unsubscribeRef.current = subscribe(checkScrollProgress);
  }, [subscribe, checkScrollProgress]);

  const performScroll = useCallback(
    (ts: string) => {
      const messages = extendedMessagesRef.current;
      const targetIndex = findMessageIndex(messages, ts);

      if (targetIndex === -1) {
        resetState();
        return;
      }

      stateRef.current = {status: 'scrolling', ts};
      const executeScroll = () => {
        if (stateRef.current.status !== 'scrolling') {
          return;
        }

        interactionHandleRef.current = InteractionManager.runAfterInteractions(
          () => {
            if (stateRef.current.status !== 'scrolling') {
              return;
            }

            stateRef.current = {status: 'awaiting_visibility', ts};
            subscribeToViewPort();

            // Check immediately in case message is already visible
            checkScrollProgress(getViewPort());
          },
        );

        requestAnimationFrame(() => {
          legendRef.current?.scrollToIndex({
            index: targetIndex,
            animated: true,
            viewPosition: viewPositionRef.current,
          });
        });
      };
      executeScroll();
    },
    [
      legendRef,
      resetState,
      subscribeToViewPort,
      checkScrollProgress,
      getViewPort,
    ],
  );

  useEffect(() => {
    return () => {
      cleanup();
    };
  }, [cleanup]);

  const scrollToMessage = useCallback(
    async (ts: string, viewPosition = 1) => {
      if (!startScrollToMessage || !ts) {
        return;
      }

      const currentState = stateRef.current;
      if (currentState.status !== 'idle' && currentState.ts === ts) {
        return;
      }

      resetState();

      prevEdgesRef.current = getMessagesEdges(extendedMessagesRef.current);
      viewPositionRef.current = viewPosition;

      stateRef.current = {status: 'loading', ts};

      finishCallbackRef.current = await startScrollToMessage({
        groupId: channel,
        ts,
        onScrollShouldStart: () => {
          requestAnimationFrame(() => {
            if (stateRef.current.status === 'loading') {
              performScroll(ts);
            }
          });
        },
      });
    },
    [channel, startScrollToMessage, resetState, performScroll],
  );

  return {
    scrollToMessage,
  } as const;
}
