import { useCallback, useMemo, useRef } from "react";
import { dragHandle as dragHandleAttr } from "../data-attributes";
import useLayoutEffect from "../use-isomorphic-layout-effect";
import findDragHandle from "../get-elements/find-drag-handle";

export default function useFocusMarshal(contextId) {
  const entriesRef = useRef({});
  const recordRef = useRef(null);
  const restoreFocusFrameRef = useRef(null);
  const isMountedRef = useRef(false);
  const register = useCallback(function register(id, focus) {
    const entry = {
      id,
      focus
    };
    entriesRef.current[id] = entry;
    return function unregister() {
      const entries = entriesRef.current;
      const current = entries[id];
      // entry might have been overrided by another registration
      if (current !== entry) {
        delete entries[id];
      }
    };
  }, []);
  const tryGiveFocus = useCallback(
    function tryGiveFocus(tryGiveFocusTo) {
      const handle = findDragHandle(contextId, tryGiveFocusTo);
      if (handle && handle !== document.activeElement) {
        handle.focus();
      }
    },
    [contextId]
  );
  const tryShiftRecord = useCallback(function tryShiftRecord(previous, redirectTo) {
    if (recordRef.current === previous) {
      recordRef.current = redirectTo;
    }
  }, []);
  const tryRestoreFocusRecorded = useCallback(
    function tryRestoreFocusRecorded() {
      // frame already queued
      if (restoreFocusFrameRef.current) {
        return;
      }

      // cannot give focus if unmounted
      // this code path is generally not hit expect for some hot-reloading flows
      if (!isMountedRef.current) {
        return;
      }
      restoreFocusFrameRef.current = requestAnimationFrame(() => {
        restoreFocusFrameRef.current = null;
        const record = recordRef.current;
        if (record) {
          tryGiveFocus(record);
        }
      });
    },
    [tryGiveFocus]
  );
  const tryRecordFocus = useCallback(function tryRecordFocus(id) {
    // clear any existing record
    recordRef.current = null;
    const focused = document.activeElement;

    // no item focused so it cannot be our item
    if (!focused) {
      return;
    }

    // focused element is not a drag handle or does not have the right id
    if (focused.getAttribute(dragHandleAttr.draggableId) !== id) {
      return;
    }
    recordRef.current = id;
  }, []);
  useLayoutEffect(() => {
    isMountedRef.current = true;
    return function clearFrameOnUnmount() {
      isMountedRef.current = false;
      const frameId = restoreFocusFrameRef.current;
      if (frameId) {
        cancelAnimationFrame(frameId);
      }
    };
  }, []);
  const marshal = useMemo(
    () => ({
      register,
      tryRecordFocus,
      tryRestoreFocusRecorded,
      tryShiftRecord
    }),
    [register, tryRecordFocus, tryRestoreFocusRecorded, tryShiftRecord]
  );
  return marshal;
}
