import {
  useRef,
  RefObject,
  useCallback,
  MutableRefObject,
  useEffect,
} from 'react';
import {Animated} from 'react-native';
import {ScrollEvent} from '@interfaces/generic';

import {Mapping, ScrollableElement, TabViewRef} from '../interfaces';

export interface CollapsibleTabProps {
  scrollY?: Animated.Value;
  collapsibleHeaderListener: (event: ScrollEvent) => void;
  minimumScrollDistance: number;
  animatedArgumentMapping: Array<Nullable<Mapping>>;
  syncScrollOffset: () => void;
}

export interface FakeScrollableElement {
  getScrollResponder: () => {
    scrollTo: ({y}: {y: number}) => void;
  };
}

interface ScrollPositions {
  [key: string]: number;
}
interface ScrollReferences {
  [key: string]: RefObject<Nullable<ScrollableElement>>;
}
interface ScrollOffsetY {
  y: Animated.Value;
}
interface UseTabsHelperParams {
  keys: string[];
  scrollReferences: ScrollReferences;
  minimumScrollDistance: number;
  initialTab?: number;
  tabRef?: RefObject<Nullable<TabViewRef>>;
}

interface UseTabsHelperResult {
  onTabChange: (key: string, index: number) => void;
  onScrollEventListener: (event: ScrollEvent) => void;
  scrollRef: MutableRefObject<ScrollOffsetY>;
  animatedArgumentMapping: Array<Mapping | null>;
  positionY: MutableRefObject<number>;
  syncScrollOffset: () => void;
}

export const useCollapsibleHeader = ({
  keys,
  scrollReferences,
  minimumScrollDistance,
  tabRef,
}: UseTabsHelperParams): UseTabsHelperResult => {
  const currentPositionY = useRef<number>(0);
  const scrollRef = useRef<ScrollOffsetY>({y: new Animated.Value(0)});
  const listenerId = useRef<string | undefined>('');
  const positionY = useRef<number>(0);

  const removeListener = useCallback((): void => {
    if (listenerId.current && scrollRef.current.y) {
      scrollRef.current.y.removeListener(listenerId.current);
    }
  }, []);

  useEffect(() => {
    listenerId.current = scrollRef.current.y?.addListener(value => {
      positionY.current = value.value;
    });
    return (): void => removeListener();
  }, [removeListener]);

  const listOffset = useRef<ScrollPositions>(
    keys.reduce(
      (acc, currentValue) => ({
        ...acc,
        [currentValue]: 0,
      }),
      {},
    ),
  );

  const syncScrollOffset = useCallback((): void => {
    Object.keys(scrollReferences).forEach(key => {
      if (
        currentPositionY.current < minimumScrollDistance &&
        currentPositionY.current >= 0
      ) {
        const scrollReference = scrollReferences[key];
        if (scrollReference && scrollReference.current) {
          const scrollView = scrollReference.current.getScrollResponder();
          if (scrollView.scrollTo) {
            scrollView.scrollTo({
              animated: false,
              y: currentPositionY.current,
            });
          }
          listOffset.current[key] = currentPositionY.current;
        }
      } else if (currentPositionY.current >= minimumScrollDistance) {
        if (
          listOffset.current[key] < minimumScrollDistance ||
          listOffset.current[key] == null
        ) {
          const scrollReference = scrollReferences[key];
          if (scrollReference && scrollReference.current) {
            const scrollView = scrollReference.current.getScrollResponder();
            if (scrollView.scrollTo) {
              scrollView.scrollTo({
                animated: false,
                y: minimumScrollDistance,
              });
            }
            listOffset.current[key] = minimumScrollDistance;
          }
        }
      }
    });
  }, [minimumScrollDistance, scrollReferences]);

  const onScrollEventListener = useCallback(
    (event: ScrollEvent): void => {
      currentPositionY.current = event.nativeEvent.contentOffset.y;
      listOffset.current[tabRef?.current?.currentIndex || 0] =
        event.nativeEvent.contentOffset.y;
    },
    [tabRef],
  );

  const animatedArgumentMapping = [
    {
      nativeEvent: {
        contentOffset: {y: scrollRef.current.y},
      },
    },
  ];

  const onTabChange = useCallback((): void => {
    syncScrollOffset();
  }, [syncScrollOffset]);

  return {
    animatedArgumentMapping,
    onScrollEventListener,
    onTabChange,
    positionY,
    scrollRef,
    syncScrollOffset,
  };
};
