import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react';

export const useScroll = <T extends HTMLElement>(): {
  scrollLeft: () => void;
  scrollRight: () => void;
  scrollRef: React.RefObject<T>;
  showScroll: boolean;
  enableScrollLeft: boolean;
  enableScrollRight: boolean;
} => {
  const scrollRef = useRef<T>(null);

  const scrollLeft = useCallback(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollBy({
        left: -220,
        behavior: 'smooth',
      });
    }
  }, []);

  const scrollRight = useCallback(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollBy({
        left: 220,
        behavior: 'smooth',
      });
    }
  }, []);

  const [showScroll, setShowScroll] = React.useState<boolean>(false);
  const [enableScrollLeft, setEnableScrollLeft] =
    React.useState<boolean>(false);
  const [enableScrollRight, setEnableScrollRight] =
    React.useState<boolean>(false);

  const updateScroll = useCallback(() => {
    if (scrollRef.current) {
      const { scrollWidth, clientWidth } = scrollRef.current;

      if (showScroll) {
        // take into account the width of the scroll buttons
        setShowScroll(scrollWidth > clientWidth + 100);
      } else {
        setShowScroll(scrollWidth > clientWidth);
      }
    }
  }, [showScroll]);

  const updateScrollButtons = useCallback(() => {
    if (scrollRef.current) {
      const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;

      setEnableScrollLeft(scrollLeft > 0);
      setEnableScrollRight(scrollLeft + clientWidth < scrollWidth);
    }
  }, []);

  // Omitting the dependency array here since this effect needs to run at every render
  // to handle the case where the components are dynamically added/removed
  useEffect(updateScroll);
  useEffect(updateScrollButtons);

  useLayoutEffect(() => {
    // debounce is needed to avoid re-rendering everything on every resize event
    const debouncedUpdateScroll = debounce(updateScroll, 50);

    // add window resize event listener on mount
    window.addEventListener('resize', debouncedUpdateScroll);

    debouncedUpdateScroll();

    const scrollContainer = scrollRef.current;
    const debouncedUpdateScrollButtons = debounce(updateScrollButtons, 50);

    scrollContainer?.addEventListener('scroll', debouncedUpdateScrollButtons);

    updateScrollButtons();

    const transformScroll = (event: WheelEvent): void => {
      if (event.deltaY !== 0 && scrollContainer) {
        scrollContainer.scrollLeft += event.deltaY;
      }
    };

    scrollContainer?.addEventListener('wheel', transformScroll, {
      passive: false, // added for scrolling on Safari
    });

    // clear all event listeners on unmount
    return () => {
      debouncedUpdateScroll.cancel();
      window.removeEventListener('resize', debouncedUpdateScroll);

      debouncedUpdateScrollButtons.cancel();
      scrollContainer?.removeEventListener(
        'scroll',
        debouncedUpdateScrollButtons,
      );

      scrollContainer?.removeEventListener('wheel', transformScroll);
    };
  }, [showScroll, updateScroll, updateScrollButtons]);

  return {
    scrollLeft,
    scrollRight,
    scrollRef,
    showScroll,
    enableScrollLeft,
    enableScrollRight,
  };
};
