// @flow
import React, {
  useState,
  useMemo,
  useEffect,
  Children,
  cloneElement,
  type ComponentType,
} from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
import type { MenuItemListProps } from './menu-item-list.proptypes';
import { MenuItem } from '../menu-item';
import { Box, type BoxProps } from '../../../../primitives';
import { getPaletteColor } from '../../../../mixins/branding';
import { ClickOutside } from '../../../behaviors/click-outside';
import {
  getTopBorderRadius,
  getBottomBorderRadius,
  getItemBorderRadiusTopLeft,
  getItemBorderRadiusTopRight,
  getItemBorderRadiusBottomLeft,
  getItemBorderRadiusBottomRight,
  getFullWidthMediaQuery,
} from '../menu.css-rules';
import { type BreakPoint } from '../../../../breakpoints';
import { useBreakPoint } from '../../../../hooks';

const MenuItemListWrapper: ComponentType<{|
  ...BoxProps,
  flatTopCorners: boolean,
  currentBreakPoint: BreakPoint,
  direction: 'left' | 'right',
  hasTargetElement: boolean,
|}> = styled(Box)`
  background: ${getPaletteColor('white')};
  border: solid 1px ${getPaletteColor('greyscale-light')};
  border-radius: ${getTopBorderRadius} ${getTopBorderRadius}
    ${getBottomBorderRadius} ${getBottomBorderRadius};
  box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.15);
  min-width: 210px;
  position: absolute;
  top: 100%;
  z-index: 2;
  right: ${({ direction }) => (direction === 'left' ? '0px' : undefined)};

  ${MenuItem} {
    &:first-child {
      border-radius: ${getItemBorderRadiusTopLeft}
        ${getItemBorderRadiusTopRight} 0 0;
    }

    &:last-child {
      border-radius: 0 0 ${getItemBorderRadiusBottomLeft}
        ${getItemBorderRadiusBottomRight};
    }
  }

  ${getFullWidthMediaQuery}
`;

export const MenuItemList = ({
  children,
  onToggle,
  flatTopCorners,
  spacing,
  direction,
  targetElement,
}: MenuItemListProps) => {
  const [focusIndex, setFocusIndex] = useState(0);
  const [childRefs, setChildRefs] = useState(
    Array.isArray(children) ? new Array(children.length) : [undefined],
  );
  const breakPoint = useBreakPoint();

  // Focus the first menu item when the popup appears.
  useEffect(() => {
    childRefs[focusIndex]?.focus();
  }, [childRefs, focusIndex]);

  // Inject some extra behaviour into the children:
  // * keep track of a ref so this parent can update the focused item
  //   from arrow key events
  // * override each item's onClick to close the menu when it is selected.
  const childrenWithRefs = useMemo(
    () =>
      Children.map(children, (child, i) =>
        cloneElement(child, {
          ref: (r) =>
            setChildRefs((last) => {
              const next = [...last];
              next[i] = r;
              return next;
            }),
          onClick: (e) => {
            child?.props?.onClick(e);
            onToggle();
          },
        }),
      ),
    [children, onToggle],
  );

  // Update which menu item has the focus when the up or down arrow keys are
  // pressed. Close the menu when the escape key is pressed.
  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowDown':
        setFocusIndex((last) => (last + 1) % childrenWithRefs.length);
        e.stopPropagation();
        e.preventDefault();
        break;
      case 'ArrowUp':
        setFocusIndex((last) => {
          const next = (last - 1) % childrenWithRefs.length;
          if (next < 0) return childrenWithRefs.length - 1;
          return next;
        });
        e.stopPropagation();
        e.preventDefault();
        break;
      case 'Escape':
        onToggle();
        e.stopPropagation();
        e.preventDefault();
        break;
      case 'Tab':
        onToggle();
        // Allow event to bubble so we can tab out of the menu.
        break;
      default:
    }
  };

  const component = (
    <ClickOutside
      onClickOutside={(e) => {
        onToggle();
        e.stopPropagation();
      }}
    >
      <MenuItemListWrapper
        flatTopCorners={flatTopCorners}
        onKeyDown={handleKeyDown}
        stacked="column"
        spacing={spacing}
        currentBreakPoint={breakPoint}
        direction={direction}
        hasTargetElement={!!targetElement}
      >
        {childrenWithRefs}
      </MenuItemListWrapper>
    </ClickOutside>
  );

  if (targetElement && breakPoint === 'sm') {
    return ReactDOM.createPortal(component, targetElement);
  }

  return component;
};
