// @flow
import React, {
  useState,
  useMemo,
  useEffect,
  Children,
  cloneElement,
  type ComponentType,
} from 'react';
import styled from 'styled-components';
import type { MenuItemListProps } from './menu-item-list.proptypes';
import { Box, type BoxProps } from '../../../../primitives';
import { getPaletteColor } from '../../../../mixins/branding';
import { ClickOutside } from '../../../behaviors/click-outside';
import { getTopBorderRadius, getBottomBorderRadius } from '../menu.css-rules';
import { bpMedium } from '../../../../breakpoints';

const MenuItemListWrapper: ComponentType<{|
  ...BoxProps,
  flatTopCorners: boolean,
|}> = styled(Box)`
  position: absolute;
  min-width: 210px;
  top: 100%;
  z-index: 2;

  box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.05);
  border: 1px solid ${getPaletteColor('greyscale-light')};
  border-radius: ${getTopBorderRadius} ${getTopBorderRadius}
    ${getBottomBorderRadius} ${getBottomBorderRadius};
  background: ${getPaletteColor('white')};

  @media (max-width: ${bpMedium}) {
    width: 100vw;
    min-width: auto;
  }
`;

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

  // 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();
        break;
      case 'ArrowUp':
        setFocusIndex((last) => {
          const next = (last - 1) % childrenWithRefs.length;
          if (next < 0) return childrenWithRefs.length - 1;
          return next;
        });
        e.stopPropagation();
        break;
      case 'Escape':
        onToggle();
        e.stopPropagation();
        break;
      case 'Tab':
        onToggle();
        // Allow event to bubble so we can tab out of the menu.
        break;
      default:
    }
  };

  return (
    <ClickOutside
      onClickOutside={(e) => {
        onToggle();
        e.stopPropagation();
      }}
    >
      <MenuItemListWrapper
        flatTopCorners={flatTopCorners}
        onKeyDown={handleKeyDown}
        stacked="column"
      >
        {childrenWithRefs}
      </MenuItemListWrapper>
    </ClickOutside>
  );
};
