import { useCallback, useMemo, useRef } from "react";
import memoizeOne from "memoize-one";
import { invariant } from "../../invariant";
import getStyles from "./get-styles";
import { prefix } from "../data-attributes";
import useLayoutEffect from "../use-isomorphic-layout-effect";

const getHead = () => {
  const head = document.querySelector("head");
  invariant(head, "Cannot find the head to append a style to");
  return head;
};
const createStyleEl = (nonce) => {
  const el = document.createElement("style");
  if (nonce) {
    el.setAttribute("nonce", nonce);
  }
  el.type = "text/css";
  return el;
};
export default function useStyleMarshal(contextId, nonce) {
  const styles = useMemo(() => getStyles(contextId), [contextId]);
  const alwaysRef = useRef(null);
  const dynamicRef = useRef(null);

  // TODO: Changed to accommodate ESLINT
  const setDynamicStyle = useCallback((proposed) => {
    // Using memoizeOne to prevent frequent updates to textContext
    const memoizedFunction = memoizeOne((proposed) => {
      const el = dynamicRef.current;
      invariant(el, "Cannot set dynamic style element if it is not set");
      el.textContent = proposed;
    });

    return memoizedFunction(proposed);
  }, []);

  const setAlwaysStyle = useCallback((proposed) => {
    const el = alwaysRef.current;
    invariant(el, "Cannot set dynamic style element if it is not set");
    el.textContent = proposed;
  }, []);

  // using layout effect as programatic dragging might start straight away (such as for cypress)
  useLayoutEffect(() => {
    invariant(!alwaysRef.current && !dynamicRef.current, "style elements already mounted");
    const always = createStyleEl(nonce);
    const dynamic = createStyleEl(nonce);

    // store their refs
    alwaysRef.current = always;
    dynamicRef.current = dynamic;

    // for easy identification
    always.setAttribute(`${prefix}-always`, contextId);
    dynamic.setAttribute(`${prefix}-dynamic`, contextId);

    // add style tags to head
    getHead().appendChild(always);
    getHead().appendChild(dynamic);

    // set initial style
    setAlwaysStyle(styles.always);
    setDynamicStyle(styles.resting);
    return () => {
      const remove = (ref) => {
        const current = ref.current;
        invariant(current, "Cannot unmount ref as it is not set");
        if (getHead().contains(current)) {
          getHead().removeChild(current);
        }
        ref.current = null;
      };
      remove(alwaysRef);
      remove(dynamicRef);
    };
  }, [nonce, setAlwaysStyle, setDynamicStyle, styles.always, styles.resting, contextId]);
  const dragging = useCallback(() => setDynamicStyle(styles.dragging), [setDynamicStyle, styles.dragging]);
  const dropping = useCallback(
    (reason) => {
      if (reason === "DROP") {
        setDynamicStyle(styles.dropAnimating);
        return;
      }
      setDynamicStyle(styles.userCancel);
    },
    [setDynamicStyle, styles.dropAnimating, styles.userCancel]
  );
  const resting = useCallback(() => {
    // Can be called defensively
    if (!dynamicRef.current) {
      return;
    }
    setDynamicStyle(styles.resting);
  }, [setDynamicStyle, styles.resting]);
  const marshal = useMemo(
    () => ({
      dragging,
      dropping,
      resting
    }),
    [dragging, dropping, resting]
  );
  return marshal;
}
