/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

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

import styles from './ContextMenu.css';

import type {RegistryContextType} from './Contexts';

function repositionToFit(element: HTMLElement, pageX: number, pageY: number) {
  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: (data: Object) => React$Node,
  id: string,
};

export default function ContextMenu({children, id}: Props): React.Node {
  const {hideMenu, registerMenu} =
    useContext<RegistryContextType>(RegistryContext);

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

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

  useEffect(() => {
    const element = bodyAccessorRef.current;
    if (element !== null) {
      const ownerDocument = element.ownerDocument;
      containerRef.current = ownerDocument.querySelector(
        '[data-react-devtools-portal-root]',
      );

      if (containerRef.current == null) {
        console.warn(
          'DevTools tooltip root node not found; context menus will be disabled.',
        );
      }
    }
  }, []);

  useEffect(() => {
    const showMenuFn = ({
      data,
      pageX,
      pageY,
    }: {
      data: any,
      pageX: number,
      pageY: number,
    }) => {
      setState({data, isVisible: true, pageX, pageY});
    };
    const hideMenuFn = () => setState(HIDDEN_STATE);
    return registerMenu(id, showMenuFn, hideMenuFn);
  }, [id]);

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

    const menu = ((menuRef.current: any): HTMLElement);
    const container = containerRef.current;
    if (container !== null) {
      // $FlowFixMe[missing-local-annot]
      const hideUnlessContains = event => {
        if (!menu.contains(event.target)) {
          hideMenu();
        }
      };

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

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

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

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

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

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