import { useEffect, useRef } from 'react';
import { useIsomorphicLayoutEffect } from 'usehooks-ts';

type Callback = () => void;

const isNode = typeof process !== 'undefined' && process.env.NODE_ENV === 'test';

let sharedIntervalId: ReturnType<typeof setInterval> | null = null;
const callbacks = new Set<Callback>();

const startSharedInterval = () => {
  if (isNode || sharedIntervalId !== null) {
    return;
  }
  sharedIntervalId = setInterval(() => {
    callbacks.forEach((cb) => {
      try {
        cb();
      } catch (e) {
        console.error(e);
      }
    });
  }, 100);
}

const stopSharedInterval = () => {
  if (sharedIntervalId === null) {
    return;
  }
  clearInterval(sharedIntervalId);
  sharedIntervalId = null;
}

/**
 * Like useInterval, but all callers share a single 100ms interval.
 * Each callback is isolated in a try/catch so a throwing callback won't prevent others from running.
 */
export const useSharedInterval = (callback: () => void) => {
  const savedCallback = useRef(callback);

  useIsomorphicLayoutEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    const cb = () => savedCallback.current();
    callbacks.add(cb);
    startSharedInterval();

    return () => {
      callbacks.delete(cb);
      if (callbacks.size === 0) {
        stopSharedInterval();
      }
    };
  }, []);
}
