import useSize from '@react-hook/size';
import * as React from 'react';
import { IPoint } from '../../Interfaces/IPoint';
import { MenuItem } from './MenuItem';

interface IMenuProps {
  getListener: () => HTMLElement | null
  actions: Map<string, IMenuAction[]>
  className?: string
}

export interface IMenuAction {
  /** displayed  */
  text: string

  /** title to show on hover */
  title?: string

  /** description of the shortcut */
  shortcut?: string

  /** function to be called on button click */
  action: (target: HTMLElement) => void

}

function UseMouseEvents(
  getListener: () => HTMLElement | null,
  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
  setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>,
  setTarget: React.Dispatch<React.SetStateAction<HTMLElement | undefined>>
): void {
  React.useEffect(() => {
    function OnContextMenu(event: MouseEvent): void {
      event.preventDefault();
      const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };

      setIsOpen(true);
      setContextMenuPosition(contextMenuPosition);
      setTarget(event.target as HTMLElement); // this is wrong since target can be null and not undefined
    }

    function OnLeftClick(): void {
      setIsOpen(false);
    }

    getListener()?.addEventListener(
      'contextmenu',
      OnContextMenu
    );

    window.addEventListener(
      'click',
      OnLeftClick
    );

    return () => {
      getListener()?.removeEventListener(
        'contextmenu',
        OnContextMenu
      );

      window.removeEventListener(
        'click',
        OnLeftClick
      );
    };
  });
}

const MENU_WIDTH_CLASS = 'w-52';
const MENU_VERTICAL_PADDING_CLASS = 'py-1';

export function Menu(props: IMenuProps): JSX.Element {
  const [isOpen, setIsOpen] = React.useState(false);
  const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
    x: 0,
    y: 0
  });
  const [target, setTarget] = React.useState<HTMLElement>();
  const menuRef = React.useRef<HTMLDivElement>(null);
  const [menuWidth, menuHeight] = useSize(menuRef);

  UseMouseEvents(
    props.getListener,
    setIsOpen,
    setContextMenuPosition,
    setTarget
  );

  const children: JSX.Element[] = [];

  if (target !== undefined) {
    let count = 0;
    count = AddClassSpecificActions(target, count, props, children);
    // Add universal actions
    AddUniversalActions(props, children, count, target);
  }

  const visible = isOpen && children.length > 0 ? 'visible opacity-1' : 'invisible opacity-0';
  const isOutOfBoundHorizontally = contextMenuPosition.x + menuWidth > window.innerWidth;
  const isOutOfBoundVertically = contextMenuPosition.y + menuHeight > window.innerHeight;
  const finalHorizontalPosition = isOutOfBoundHorizontally ? contextMenuPosition.x - menuWidth : contextMenuPosition.x;
  const finalVerticalPosition = isOutOfBoundVertically ? contextMenuPosition.y - menuWidth : contextMenuPosition.y;
  return (
    <div
      ref={menuRef}
      className={`fixed context-menu ${MENU_VERTICAL_PADDING_CLASS} ${MENU_WIDTH_CLASS} ${props.className ?? ''} ${visible}`}
      style={{
        left: finalHorizontalPosition,
        top: finalVerticalPosition
      }}>
      { children }
    </div>
  );
}

function AddClassSpecificActions(
  target: HTMLElement,
  count: number,
  props: IMenuProps,
  children: JSX.Element[]
): number {
  for (const className of target.classList) {
    count++;
    const actions = props.actions.get(className);

    // Only select action where classname matches
    if (actions === undefined) {
      continue;
    }

    actions.forEach((action, index) => {
      children.push(<MenuItem
        key={`contextmenu-item-${count}-${index}`}
        className={'contextmenu-item'}
        text={action.text}
        title={action.title}
        shortcut={action.shortcut}
        onClick={() => action.action(target)} />);
    });
    children.push(<hr key={`contextmenu-hr-${count}`} className='border-slate-400' />);
  }
  return count;
}

function AddUniversalActions(props: IMenuProps, children: JSX.Element[], count: number, target: HTMLElement): number {
  const actions = props.actions.get('');

  if (actions !== undefined) {
    count++;
    actions.forEach((action, index) => {
      children.push(<MenuItem
        key={`contextmenu-item-${count}-${index}`}
        className={'contextmenu-item'}
        text={action.text}
        title={action.title}
        shortcut={action.shortcut}
        onClick={() => action.action(target)} />);
    });
  }

  return count;
}
