import { useCallback, useEffect, useRef } from "react";

function isElementInViewport(el) {
  var rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <=
      (window.innerHeight ||
        document.documentElement.clientHeight) /* or $(window).height() */ &&
    rect.right <=
      (window.innerWidth ||
        document.documentElement.clientWidth) /* or $(window).width() */
  );
}

export const useInViewEffect = (
  onEnter: () => void,
  onLeave: () => void,
  {
    root,
    rootMargin,
    threshold,
    once,
  }: IntersectionObserverInit & { once?: boolean } = {},
) => {
  const inView = useRef<boolean>(false);
  const target = useRef<Element | null>(null);
  const observer = useRef<IntersectionObserver | null>(null);
  useEffect(() => {
    if (!target.current) return;
    if (isElementInViewport(target.current)) {
      onEnter();
      if (once) {
        observer.current?.unobserve(target.current);
        observer.current?.disconnect();
        observer.current = null;
      }
      inView.current = true;
    } else {
      onLeave();
      inView.current = false;
    }
  }, []);

  const callback: IntersectionObserverCallback = useCallback(
    ([entry]) => {
      if (entry.isIntersecting && !inView.current) {
        onEnter();
        if (once) {
          observer.current?.unobserve(target.current!);
          observer.current?.disconnect();
          observer.current = null;
        }
      } else if (!entry.isIntersecting && inView.current) {
        onLeave();
      }
      inView.current = entry.isIntersecting;
    },
    // all the props are static
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [],
  );

  const setTarget = useCallback(
    (node) => {
      if (target.current && observer.current) {
        observer.current.unobserve(target.current);
        observer.current.disconnect();
        observer.current = null;
      }

      if (node) {
        observer.current = new IntersectionObserver(callback, {
          root,
          rootMargin,
          threshold,
        });
        observer.current.observe(node);
        target.current = node;
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [target, root, rootMargin, JSON.stringify(threshold), callback],
  );

  return [setTarget, inView.current] as const;
};
