import { useRef, useState, useEffect, useCallback } from 'react';
import { useRecoilCallback } from 'recoil';
import { fetchDataFiles, updateFetchedDataFiles } from "../../../atoms/election/index.js";

/**
 * Hook to kick off fetching election data.
 *
 * Input:
 *
 * ```
 * // flatpackConfig
 * {
 *   isPolling: boolean,
 * }
 *
 * // dataList
 * [
 *   {
 *     url: string,
 *     type?: string, // if not provided, treated as raw data
 *     key?: string, // if not provided, `url` is used as `key`
 *     refreshRate?: number, // if not provided, won't poll
 *   },
 * ]
 * ```
 */
const useActivateElections = function useActivateElections({
  flatpackConfig = {},
  dataList = []
}) {
  const {
    isPolling = true
  } = flatpackConfig;
  const getKey = file => file.config.key || file.config.url;
  const hasExpired = useRef(true);
  const fetchId = useRef();
  const oldDataList = useRef(dataList);
  const [isVisible, setIsVisible] = useState(true);
  const [fetchedDataFiles, setFetchedDataFiles] = useState({});
  const updateSourceBatch = useCallback(async ({
    files
  }) => {
    const values = files.map(file => ({
      config: file.config
    }));
    const fetchedValues = await fetchDataFiles({
      values
    });
    const newFiles = fetchedValues.filter(Boolean);
    const filesByKey = Object.fromEntries(newFiles.map(file => [getKey(file), file]));
    setFetchedDataFiles(old => ({
      ...old,
      ...filesByKey
    }));
  }, []);
  const recoilCallback = useRecoilCallback(({
    snapshot,
    gotoSnapshot
  }) => () => {
    const snap = snapshot.map(mutableSnap => {
      const values = dataList.map(d => fetchedDataFiles[getKey(d)]).filter(Boolean);
      updateFetchedDataFiles({
        snapshot: mutableSnap,
        values
      });
      return undefined;
    });
    gotoSnapshot(snap);
  }, [dataList, fetchedDataFiles]);
  useEffect(() => {
    recoilCallback();
  }, [recoilCallback]);
  const getIsVisible = useCallback(() => {
    const state = window.document.visibilityState;
    return state === 'visible';
  }, []);
  useEffect(() => {
    const handleVisibilityChange = () => {
      setIsVisible(getIsVisible());
    };
    handleVisibilityChange();
    window.document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => {
      window.document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, []);
  useEffect(() => {
    const timers = new Map();

    // Function that fetches all endpoints in a batch and then updates the
    // store with the entire batch at once. It will then continue fetching the
    // batch on an interval if one is specified.
    const fetchAllEndpointsInBatch = (configs, interval, key) => {
      if (timers.has(interval)) {
        clearTimeout(timers.get(interval));
      }
      const fetcher = async () => {
        if (key !== fetchId.current) return;
        timers.delete(interval);
        const isCurrentlyVisible = getIsVisible();
        const refresh = !interval || interval > 0;
        const poll = interval && interval > 0 && isCurrentlyVisible;
        if (isCurrentlyVisible) {
          await updateSourceBatch({
            files: configs,
            refresh
          });
        } else {
          hasExpired.current = true;
        }
        if (poll) {
          const timer = setTimeout(fetcher, interval);
          timers.set(interval, timer);
        }
      };
      fetcher();
    };
    const start = key => {
      hasExpired.current = false;

      // Group endpoints into batches with the same refresh rate
      const batchesByRefreshRate = dataList.reduce((batches, data) => {
        const {
          refreshRate
        } = data.config;

        // ignore negative refresh rates because those are not client-side
        if (refreshRate < 0) return batches;
        const batch = batches.get(refreshRate) || [];
        batches.set(refreshRate, [...batch, data]);
        return batches;
      }, new Map());

      // Start fetching all the batches
      [...batchesByRefreshRate].map(([refreshRate, batch]) => fetchAllEndpointsInBatch(batch, refreshRate, key));
    };
    const startBecauseVisibilityChange = hasExpired.current;
    const startBecauseDataChange = oldDataList.current !== dataList;
    if (isPolling && isVisible && (startBecauseVisibilityChange || startBecauseDataChange)) {
      oldDataList.current = dataList;
      const key = Date.now();
      fetchId.current = key;
      [...timers].forEach(([interval, timer]) => {
        timers.delete(interval);
        clearTimeout(timer);
      });
      start(key);
    }
  }, [isPolling, dataList, isVisible, updateSourceBatch]);
};
export default useActivateElections;