import React, {
  useContext,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
} from 'react';
import {createPortal} from 'react-dom';
import {RegistryContext} from './Contexts';

import styles from './ContextMenu.css';

function respositionToFit(element, pageX, pageY) {
  const ownerWindow = element.ownerDocument.defaultView;
  if (element !== null) {
    if (pageY + element.offsetHeight >= ownerWindow.innerHeight) {
      if (pageY - element.offsetHeight > 0) {
        element.style.top = `${pageY - element.offsetHeight}px`;
      } else {
        element.style.top = '0px';
      }
    } else {
      element.style.top = `${pageY}px`;
    }

    if (pageX + element.offsetWidth >= ownerWindow.innerWidth) {
      if (pageX - element.offsetWidth > 0) {
        element.style.left = `${pageX - element.offsetWidth}px`;
      } else {
        element.style.left = '0px';
      }
    } else {
      element.style.left = `${pageX}px`;
    }
  }
}

const HIDDEN_STATE = {
  data: null,
  isVisible: false,
  pageX: 0,
  pageY: 0,
};

type Props = {|
  children: React$Node,
  id: string,
|};

export default function ContextMenu({children, id}: Props) {
  const {registerMenu} = useContext(RegistryContext);

  const [state, setState] = useState(HIDDEN_STATE);

  const bodyAccessorRef = useRef(null);
  const containerRef = useRef(null);
  const menuRef = useRef(null);

  useEffect(() => {
    const ownerDocument = bodyAccessorRef.current.ownerDocument;
    containerRef.current = ownerDocument.createElement('div');
    ownerDocument.body.appendChild(containerRef.current);
    return () => {
      ownerDocument.body.removeChild(containerRef.current);
    };
  }, []);

  useEffect(
    () => {
      const showMenu = ({data, pageX, pageY}) => {
        setState({data, isVisible: true, pageX, pageY});
      };
      const hideMenu = () => setState(HIDDEN_STATE);
      return registerMenu(id, showMenu, hideMenu);
    },
    [id],
  );

  useLayoutEffect(
    () => {
      if (!state.isVisible) {
        return;
      }

      const menu = menuRef.current;

      const hideUnlessContains = event => {
        if (!menu.contains(event.target)) {
          setState(HIDDEN_STATE);
        }
      };

      const hide = event => {
        setState(HIDDEN_STATE);
      };

      const ownerDocument = containerRef.current.ownerDocument;
      ownerDocument.addEventListener('mousedown', hideUnlessContains);
      ownerDocument.addEventListener('touchstart', hideUnlessContains);
      ownerDocument.addEventListener('keydown', hideUnlessContains);

      const ownerWindow = ownerDocument.defaultView;
      ownerWindow.addEventListener('resize', hide);

      respositionToFit(menu, state.pageX, state.pageY);

      return () => {
        ownerDocument.removeEventListener('mousedown', hideUnlessContains);
        ownerDocument.removeEventListener('touchstart', hideUnlessContains);
        ownerDocument.removeEventListener('keydown', hideUnlessContains);

        ownerWindow.removeEventListener('resize', hide);
      };
    },
    [state],
  );

  if (!state.isVisible) {
    return <div ref={bodyAccessorRef} />;
  } else {
    return createPortal(
      <div ref={menuRef} className={styles.ContextMenu}>
        {children(state.data)}
      </div>,
      containerRef.current,
    );
  }
}
