import { ForwardedRef, ReactNode, useCallback, useMemo, useRef, useState } from 'react';

import { useId } from '../../hooks/useId';
import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { CommonProps } from '../../types';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { makeTestId } from '../../utils/makeTestId';
import { setForwarderRef } from '../../utils/setForwarderRef';
import { Dropdown, DropdownProps } from '../Dropdown/Dropdown';
import { TriggerButtonContextProvider } from '../TriggerButton/contexts/TriggerButtonContext/TriggerButtonContext';

import { MenuList } from './components/MenuList/MenuList';
import { MenuContextProvider } from './contexts/MenuContext';
import { StyledMenuContainer } from './styled';

/** Props for {@link Menu} component */
export interface MenuProps extends CommonProps {
  /** The element to be rendered as the trigger button */
  trigger: ReactNode;
  /** The ref of the trigger button */
  triggerRef?: ForwardedRef<HTMLElement>;
  /** If `true`, the menu will be disabled */
  disabled?: boolean;
  children: ReactNode;
}

/**
 * Menu component
 *
 * ```tsx
 * import { Menu, MenuTrigger } from '@ui-kit';
 *
 * <Menu trigger={<MenuTrigger>Menu</MenuTrigger>}>
 *   <MenuItem>Item 1</MenuItem>
 *   <MenuItem>Item 2</MenuItem>
 *   <MenuItem>Item 3</MenuItem>
 * </Menu>
 * ```
 */
export function Menu(props: MenuProps) {
  const {
    trigger,
    children,
    disabled,
    triggerRef: forwardedTriggerRef,
    className,
    testId,
    ariaDescribedBy,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const testIdAttribute = useTestIdAttribute();
  const dropdownId = useId();
  const [isMenuOpen, setMenuOpen] = useState(false);

  const triggerRef = useRef<HTMLButtonElement | null>(null);
  const setTriggerRef = (ref: HTMLButtonElement) => {
    triggerRef.current = ref;
    setForwarderRef(forwardedTriggerRef, ref);
  };

  const closeMenu = useCallback(
    (skipTriggerFocus?: boolean) => {
      setMenuOpen(false);
      if (!skipTriggerFocus) {
        triggerRef.current?.focus();
      }
    },
    [triggerRef],
  );

  const handleTriggerButtonClick = () => {
    setMenuOpen((open) => !open);
  };

  const focusTrapOptions = useMemo<DropdownProps['focusTrapOptions']>(() => {
    return {
      allowOutsideClick: true,
      fallbackFocus() {
        return triggerRef.current!;
      },
    };
  }, []);

  return (
    <StyledMenuContainer
      aria-describedby={ariaDescribedBy}
      className={className}
      {...{ [testIdAttribute]: testId }}
    >
      <TriggerButtonContextProvider
        ariaControls={dropdownId}
        ariaDescribedBy={undefined}
        ariaErrorMessage={undefined}
        ariaExpanded={undefined}
        ariaHasPopup="menu"
        ariaInvalid={undefined}
        ariaLabel={undefined}
        disabled={disabled}
        id={undefined}
        onBlur={undefined}
        onClick={handleTriggerButtonClick}
        role="button"
        triggerRef={setTriggerRef}
      >
        {trigger}
      </TriggerButtonContextProvider>

      {children && (
        <Dropdown
          focusTrapOptions={focusTrapOptions}
          id={dropdownId}
          onEscButtonPress={() => closeMenu()}
          onOutsideClick={() => closeMenu()}
          trigger={triggerRef.current}
          visible={isMenuOpen}
          popperOptions={{
            placement: 'bottom-end',
          }}
        >
          <MenuContextProvider closeMenu={closeMenu}>
            <MenuList testId={makeTestId(testId, 'menu')}>{children}</MenuList>
          </MenuContextProvider>
        </Dropdown>
      )}
    </StyledMenuContainer>
  );
}
