import { useId } from '@radix-ui/react-id';
import { Link, useLocation } from 'app';
import cn from 'clsx';
import React, { useEffect, useReducer, useRef, useState } from 'react';

import { Badge } from '../';
import { useApp } from '../../app';
import { McolGrid, Box } from '../../system';
import * as css from './nav-dropdown.module.scss';

const { dropMenuListItem, dropSubmenuLabel, dropSubmenuList, dropMenuLink } = css;

// TODO: abstract this, and figure out how to handle focus confusion on iOS
function useHover2({ onHoverStart, onHoverEnd, onHoverChange, isDisabled }) {
  const [isHovered, setIsHovered] = useState(false);
  const onEnter = (e) => {
    if (isDisabled || e.pointerType == 'touch') return;
    if (onHoverStart) onHoverStart();
    if (onHoverChange) onHoverChange();
    setIsHovered(true);
  };
  const onLeave = (e) => {
    if (isDisabled || e.pointerType == 'touch') return;
    if (onHoverEnd) onHoverEnd();
    if (onHoverChange) onHoverChange();
    setIsHovered(false);
  };
  const hasPointers = typeof PointerEvent !== undefined;
  return {
    hoverProps: hasPointers
      ? {
          onPointerEnter: onEnter,
          onPointerLeave: onLeave,
        }
      : {
          onMouseEnter: onEnter,
          onMouseLeave: onLeave,
        },
    isHovered,
  };
}

export function NavDropdown({
  text = 'items',
  items = [],
  forceOpen = false,
  btnClassName,
  className,
  alignment,
  ...restProps
}) {
  const triggerRef = useRef(null);
  const contentRef = useRef(null);
  const firstLinkRef = useRef(null);
  const { appNode } = useApp();
  const [dropdownState, sendDropdownEvent] = useReducer(
    (state, event) =>
      ({
        open: {
          hoverEnd: 'closed',
          escapeKey: 'closed',
          focusOut: 'closed',
          clickOutside: 'closed',
          locationChange: 'closed',
        },
        closed: {
          hoverStart: 'open',
          triggerClick: 'open',
        },
      }[state][event] || state),
    'closed',
  );
  const { hoverProps, isHovered } = useHover2({
    onHoverStart: () => sendDropdownEvent('hoverStart'),
    onHoverEnd: () => sendDropdownEvent('hoverEnd'),
  });

  // track the location, and reset focus to top
  const location = useLocation();
  // const focusWrapper = useQuerySelector('#site');
  useEffect(() => {
    if (dropdownState == 'open') {
      document.activeElement.blur();
      setTimeout(() => {
        sendDropdownEvent('locationChange');
        appNode.focus();
      }, 0);
    }
  }, [location]);

  // handle blurring out of content area
  const onBlurCapture = ({ relatedTarget }) => {
    if (relatedTarget && !contentRef.current.contains(relatedTarget)) {
      setTimeout(() => {
        sendDropdownEvent('focusOut');
      }, 0);
    }
  };

  const onKeyUpCapture = (event) => {
    if (event.key !== 'Escape') return;
    setTimeout(() => {
      sendDropdownEvent('escapeKey');
      if (triggerRef.current) triggerRef.current.focus();
    }, 0);
  };

  const onClickOutside = (event) => {
    if (
      event.target &&
      [contentRef, triggerRef].some((ref) => ref.current && ref.current.contains(event.target))
    )
      return;
    event.preventDefault();
    setTimeout(() => {
      sendDropdownEvent('clickOutside');
      if (triggerRef.current) triggerRef.current.focus();
    }, 0);
  };

  useEffect(() => {
    if (dropdownState == 'open') {
      document.addEventListener('mousedown', onClickOutside);
    } else {
      document.removeEventListener('mousedown', onClickOutside);
    }
    return () => document.removeEventListener('mousedown', onClickOutside);
  }, [dropdownState]);

  const triggerID = useId();
  const contentID = useId();
  const isMultiList = items.some((item) => 'items' in item && item.items != null);

  const show = dropdownState === 'open' || forceOpen;

  return (
    <li
      role="listitem"
      className={cn('nav-dropdown pos-rel fw-500', className)}
      onBlurCapture={onBlurCapture}
      onKeyUpCapture={onKeyUpCapture}
      {...hoverProps}
      {...restProps}
    >
      <Box
        fz={16}
        as="button"
        type="button"
        id={triggerID}
        ref={triggerRef}
        className={cn(`drop-menu-trigger btn ${isHovered ? 'hover' : ''}`, css.dropMenuTrigger, btnClassName)}
        aria-haspopup="true"
        aria-controls={contentID}
        aria-expanded={show}
        onClick={() => sendDropdownEvent('triggerClick')}
      >
        {text}
      </Box>

      <div
        className={cn({ alignment: `align-${alignment}` }, css.dropMenuContent)}
        id={contentID}
        ref={contentRef}
        aria-hidden={!show}
      >
        <div className={cn('bg bd', css.dropMenuPanel)}>
          {isMultiList ? (
            <McolGrid as="ul" cols={2} role="list" aria-labelledby={triggerID} className={css.dropMenuList}>
              {items.map(({ text, items }, i) => {
                const labelID = useId();
                return (
                  <li role="listitem" key={text}>
                    <p
                      id={labelID}
                      className={cn('drop-submenu-name f-mono fg-tone-10', dropSubmenuLabel)}
                      style={{ '--font-size': '12px' }}
                    >
                      {text}
                    </p>
                    {items && (
                      <ul
                        role="list"
                        aria-labelledby={labelID}
                        className={[css.dropMenuList, dropSubmenuList]}
                      >
                        {items.map(({ text, href, xtra }, j) => (
                          <li role="listitem" key={text} className={dropMenuListItem}>
                            <Link
                              rootRelative
                              className={cn('link', dropMenuLink)}
                              ref={i === 0 && j === 0 ? firstLinkRef : undefined}
                              href={href}
                            >
                              {text}
                              {xtra && <Badge ml="10">{xtra}</Badge>}
                            </Link>
                          </li>
                        ))}
                      </ul>
                    )}
                  </li>
                );
              })}
            </McolGrid>
          ) : (
            <ul role="list" className={css.dropMenuList}>
              {items.map(({ text, href, xtra, indent }, i) => (
                <li
                  role="listitem"
                  key={text}
                  className={dropMenuListItem}
                  style={{ marginLeft: indent ? '1rem' : 'unset' }}
                >
                  <Link
                    rootRelative
                    className={cn('link', dropMenuLink)}
                    ref={i === 0 ? firstLinkRef : undefined}
                    href={href}
                  >
                    {text}
                    {xtra && <Badge ml="10">{xtra}</Badge>}
                  </Link>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>
    </li>
  );
}
