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

type UseIntervalTypes = {
  callback: () => void;
  delay: number;
};

/**
 * React lifecycle-aware implementation of setInterval.
 * This allows for a quick implementation of setInterval in React without having to worry about cleanup.
 * @see https://overreacted.io/making-setinterval-declarative-with-react-hooks/ by Dan Abramov for more details.
 */
function useInterval(
  callback: UseIntervalTypes["callback"],
  delay: UseIntervalTypes["delay"]
) {
  const runOnMount = useRef<boolean>(true);
  const savedCallback = useRef<UseIntervalTypes["callback"]>();
  const intervalRef = useRef<NodeJS.Timeout>();

  useEffect(() => {
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, []);

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Run the interval if this hook mounts and runOnMount is true
  // eslint-disable-next-line consistent-return
  useEffect(() => {
    function tick() {
      savedCallback.current?.();
    }
    if (delay !== null && runOnMount.current) {
      intervalRef.current = setInterval(tick, delay);
    }
  }, [delay]);

  // Run the interval when this function is called
  const beginPolling = () => {
    function tick() {
      savedCallback.current?.();
    }
    intervalRef.current = setInterval(tick, delay);
  };

  // Intercept how this function is called
  const interceptedReturn = useMemo(() => {
    return new Proxy([beginPolling], {
      get(target, prop, receiver) {
        // If beginPolling is extracted on the call (ie. const [poll] = useInterval(_, 5000)),
        // do not start the interval on mount.
        if (!Number.isNaN(prop) && prop === "0") {
          runOnMount.current = false;
        }
        return Reflect.get(target, prop, receiver);
      },
    });
  }, [beginPolling]);

  return interceptedReturn;
}

export default useInterval;
