import FocusTrap from 'focus-trap-react';
import {
  Children,
  FocusEventHandler,
  KeyboardEventHandler,
  ReactElement,
  ReactNode,
  useCallback,
  useMemo,
  useRef,
  useState,
} from 'react';
import { CSSTransition } from 'react-transition-group';

import { useId } from '../../../../hooks/useId';
import { useTestIdAttribute } from '../../../../hooks/useTestIdAttribute';
import { CommonProps } from '../../../../types';
import { assertEmptyObject } from '../../../../utils/assertEmptyObject';
import { useNavigationBarContext } from '../../contexts/NavigationBarContext';
import { NavigationBarListItemProps } from '../NavigationBarListItem/NavigationBarListItem';
import { NavigationBarPortal } from '../NavigationBarPortal/NavigationBarPortal';

import {
  StyledIconWrapper,
  StyledSubMenu,
  StyledSubMenuHeader,
  StyledSubNav,
  StyledTrigger,
  animationDuration,
} from './styled';

/** Props for {@link NavigationBarList} */
export interface NavigationBarListProps extends Omit<CommonProps, 'ariaDescribedBy'> {
  /** Navigation title */
  title: ReactNode;
  /** Label in the First Level navigation */
  label: ReactNode | ((props: { active: boolean; hover: boolean }) => ReactNode);
  /**
   * Array of {@link NavigationBarListItem} and custom React nodes.
   * Each will be wrapped into `li` tag with `presentation` role
   */
  children: ReactElement<NavigationBarListItemProps>[] | ReactElement<NavigationBarListItemProps>;
  /** If `true`, the item will be designed as active */
  active?: boolean;
}

/**
 * {@link NavigationBar} item that represents the Second Level of navigation.
 *
 * It appears as a sub-level of the first level ({@link NavigationBar}) and can't exist without it. It overlaps the screen's content.
 */
export function NavigationBarList(props: NavigationBarListProps) {
  /* istanbul ignore next */
  const { title, label, children, active = false, testId, className, ...restProps } = props;
  assertEmptyObject(restProps);

  const triggerId = useId();
  const menuId = useId();
  const headerId = useId();
  const menuRef = useRef<HTMLUListElement>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const transitionRef = useRef<HTMLUListElement>(null);
  const [isFocusTrapEnabled, setFocusTrapEnabled] = useState<boolean>(false);
  const [isFocusTrapActive, setFocusTrapActive] = useState(false);
  const { currentList, setCurrentList } = useNavigationBarContext();

  const hover = triggerId === currentList;

  const handleMouseEnter = useCallback(() => {
    setCurrentList(triggerId);
  }, [triggerId, setCurrentList]);

  const handleMouseLeave = useCallback(() => {
    setCurrentList(null);
  }, [setCurrentList]);

  const handleTriggerKeyDown = useCallback<KeyboardEventHandler>(
    (event) => {
      switch (event.code) {
        case 'ArrowRight':
          setFocusTrapEnabled(true);
          break;
        case 'Escape':
          setCurrentList(null);
          setFocusTrapEnabled(false);
          break;
        case 'Enter':
        case 'Space':
          setCurrentList(triggerId);
          setFocusTrapEnabled(false);
          break;
        default:
      }
    },
    [setFocusTrapEnabled, setCurrentList, triggerId],
  );

  const handleMenuKeyDown = useCallback<KeyboardEventHandler>(
    (event) => {
      if (event.key === 'ArrowLeft') {
        setFocusTrapEnabled(false);
      }
    },
    [setFocusTrapEnabled],
  );

  const handleFocus = useCallback<FocusEventHandler>(() => {
    setCurrentList(triggerId);
  }, [setCurrentList, triggerId]);

  const handleBlur = useCallback<FocusEventHandler>(() => {
    setCurrentList(null);
  }, [setCurrentList]);

  const handleSubMenuKeyDown = useCallback<KeyboardEventHandler>((event) => {
    const menu = menuRef.current!;
    const eventTarget = event.target as HTMLElement;
    const items = Array.from(menu.querySelectorAll<HTMLElement>('[role=menuitem]'));
    const currentIndex = items.indexOf(eventTarget);

    /* istanbul ignore next */
    if (currentIndex === -1) {
      return;
    }

    const nextIndex = currentIndex + 1 >= items.length ? 0 : currentIndex + 1;
    const prevIndex = currentIndex - 1 < 0 ? items.length - 1 : currentIndex - 1;

    switch (event.key) {
      case 'ArrowUp':
        items[prevIndex]?.focus();
        break;
      case 'ArrowDown':
        items[nextIndex]?.focus();
        break;
      /* istanbul ignore next */
      default:
    }
  }, []);

  const focusTrapOptions = useMemo<FocusTrap.Props['focusTrapOptions']>(() => {
    return {
      allowOutsideClick: true,
      setReturnFocus() {
        return triggerRef.current ?? /* istanbul ignore next */ false;
      },
      fallbackFocus() {
        return transitionRef.current!;
      },
    };
  }, []);

  const handleOnEntered = useCallback(() => setFocusTrapActive(true), []);
  const handleOnExit = useCallback(() => setFocusTrapActive(false), []);

  const testIdAttribute = useTestIdAttribute();

  return (
    <StyledTrigger
      ref={triggerRef}
      aria-controls={menuId}
      aria-haspopup="true"
      aria-labelledby={headerId}
      className={className}
      id={triggerId}
      onBlur={handleBlur}
      onFocus={handleFocus}
      onKeyDown={handleTriggerKeyDown}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      role="menuitem"
      {...{ [testIdAttribute]: testId }}
    >
      <StyledIconWrapper $active={active} $hover={hover}>
        {typeof label === 'function' ? label({ hover, active }) : /* istanbul ignore next */ label}
      </StyledIconWrapper>
      <NavigationBarPortal>
        <CSSTransition
          in={hover}
          nodeRef={transitionRef}
          onEntered={handleOnEntered}
          onExit={handleOnExit}
          timeout={animationDuration * 2}
        >
          <FocusTrap active={isFocusTrapEnabled && isFocusTrapActive} focusTrapOptions={focusTrapOptions}>
            <StyledSubNav ref={transitionRef} onKeyDown={handleMenuKeyDown}>
              <StyledSubMenuHeader id={headerId}>{title}</StyledSubMenuHeader>
              <StyledSubMenu
                ref={menuRef}
                aria-labelledby={triggerId}
                id={menuId}
                onKeyDown={handleSubMenuKeyDown}
                role="menu"
              >
                {Children.map(children, (child) => (
                  <li role="presentation">{child}</li>
                ))}
              </StyledSubMenu>
            </StyledSubNav>
          </FocusTrap>
        </CSSTransition>
      </NavigationBarPortal>
    </StyledTrigger>
  );
}
