/* eslint jsx-a11y/click-events-have-key-events: warn */
import { useOutsideClick } from '@chakra-ui/react';
import clsx from 'clsx';
import React, {
  DetailedHTMLProps,
  HTMLAttributes,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import ReactDOM from 'react-dom';
import { twMerge } from 'tailwind-merge';

import Button, {
  Props as ButtonProps,
  ButtonShape,
  ButtonSize,
  ButtonVariant,
} from '../button/Button';

export type SelectorV2Option = {
  value: string;
  title: string;
  ListComponent: React.FC;
  isDefault?: boolean;
  disabled?: boolean;
};

const BUTTON_MENU_SPACING = 4;
const SCREEN_EDGE_SPACING = 8;

const getScrollParent = (node: HTMLElement | null): HTMLElement | null => {
  if (node == null) {
    return null;
  }

  if (node.scrollHeight > node.clientHeight) {
    return node;
  } else {
    return getScrollParent(node.parentNode as HTMLElement | null);
  }
};

export const SelectorOption = (
  props: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
) => {
  return (
    <div
      {...props}
      className={twMerge(
        'rounded-md px-2 py-1 hover:bg-background-tertiary',
        props.className
      )}
    />
  );
};

const SelectorV2 = ({
  value,
  onSetValue,
  options,
  menuClassName,
  onClick,
  ...restProps
}: {
  value: string;
  onSetValue: (newValue: string) => void;
  options: SelectorV2Option[];
  menuClassName?: string;
} & Omit<ButtonProps, 'children'>) => {
  const selectedOption = useMemo(() => {
    return (
      options.find((o) => o.value === value) ||
      options.find((o) => o.isDefault) ||
      options[0]
    );
  }, [options, value]);

  const [open, setOpen] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(0);
  const optionRefs = useRef<(HTMLDivElement | null)[]>([]);

  const handleSelectOption = useCallback(
    (option: SelectorV2Option) => {
      onSetValue(option.value);
      setOpen(false);
    },
    [onSetValue]
  );

  const handleClick = useCallback(
    (
      e: React.MouseEvent<HTMLAnchorElement, MouseEvent> &
        React.MouseEvent<HTMLButtonElement, MouseEvent>
    ) => {
      if (onClick) {
        onClick(e);
      }

      setOpen((prev) => !prev);
    },
    [onClick]
  );

  const handleButtonKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
      e.preventDefault();
      e.stopPropagation();
      setOpen(true);
    }
  }, []);

  // Focus first option when menu opens
  useEffect(() => {
    if (open) {
      const selectedIndex = options.findIndex((o) => o === selectedOption);
      setFocusedIndex(selectedIndex >= 0 ? selectedIndex : 0);
      // Focus the first option after a brief delay to ensure menu is rendered
      setTimeout(() => {
        optionRefs.current[selectedIndex >= 0 ? selectedIndex : 0]?.focus();
      }, 0);
    }
  }, [open, options, selectedOption]);

  // Handle keyboard navigation in the menu
  const handleMenuKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (!open) return;

      const enabledOptions = options.filter((o) => !o.disabled);
      const currentEnabledIndex = enabledOptions.findIndex(
        (o) => o === options[focusedIndex]
      );

      switch (e.key) {
        case 'ArrowDown':
          e.preventDefault();
          e.stopPropagation();
          const nextEnabledIndex =
            (currentEnabledIndex + 1) % enabledOptions.length;
          const nextIndex = options.indexOf(enabledOptions[nextEnabledIndex]);
          setFocusedIndex(nextIndex);
          optionRefs.current[nextIndex]?.focus();
          break;

        case 'ArrowUp':
          e.preventDefault();
          e.stopPropagation();
          const prevEnabledIndex =
            (currentEnabledIndex - 1 + enabledOptions.length) %
            enabledOptions.length;
          const prevIndex = options.indexOf(enabledOptions[prevEnabledIndex]);
          setFocusedIndex(prevIndex);
          optionRefs.current[prevIndex]?.focus();
          break;

        case 'Home':
          e.preventDefault();
          e.stopPropagation();
          const firstIndex = options.indexOf(enabledOptions[0]);
          setFocusedIndex(firstIndex);
          optionRefs.current[firstIndex]?.focus();
          break;

        case 'End':
          e.preventDefault();
          e.stopPropagation();
          const lastIndex = options.indexOf(
            enabledOptions[enabledOptions.length - 1]
          );
          setFocusedIndex(lastIndex);
          optionRefs.current[lastIndex]?.focus();
          break;

        case 'Escape':
          e.preventDefault();
          e.stopPropagation();
          setOpen(false);
          buttonRef.current?.focus();
          break;
      }
    },
    [open, options, focusedIndex]
  );

  const buttonRef = useRef<HTMLButtonElement>(null);
  const menuRef = useRef<HTMLDivElement | null>(null);
  useOutsideClick({
    ref: menuRef as React.RefObject<HTMLDivElement>,
    handler: (e) => {
      if (e.target && !buttonRef.current?.contains(e.target as Node) && open) {
        setOpen(false);
      }
    },
  });

  const repositionMenu = useCallback(
    (menu: HTMLDivElement, button: HTMLButtonElement) => {
      const buttonRect = button.getBoundingClientRect();

      let top =
        buttonRect.top +
        buttonRect.height +
        BUTTON_MENU_SPACING +
        document.body.scrollTop;
      let left = buttonRect.left + document.body.scrollLeft;
      const menuRect = menu.getBoundingClientRect();
      const bottom = top + menuRect.height + SCREEN_EDGE_SPACING;
      const right = left + menuRect.width + SCREEN_EDGE_SPACING;
      if (bottom > window.innerHeight) {
        top = buttonRect.top - menuRect.height - BUTTON_MENU_SPACING;
      }
      if (right > window.innerWidth) {
        left -= right - window.innerWidth;
      }

      menu.setAttribute(
        'style',
        `top: ${top}px; left: ${left}px; min-width: ${buttonRect.width}px;`
      );
    },
    []
  );

  const receiveMenuRef = useCallback(
    (menu: HTMLDivElement | null) => {
      const button = buttonRef.current;
      setTimeout(() => {
        menuRef.current = menu;
        if (menu && button) {
          repositionMenu(menu, button);
        }
      }, 0);
    },
    [repositionMenu]
  );

  useEffect(() => {
    const scrollParent = getScrollParent(buttonRef.current);
    if (!scrollParent) return;
    const handleScroll = () => {
      if (menuRef.current && buttonRef.current) {
        repositionMenu(menuRef.current, buttonRef.current);
      }
    };
    scrollParent.addEventListener('scroll', handleScroll);
    return () => scrollParent.removeEventListener('scroll', handleScroll);
  });

  if (!selectedOption) {
    // no options to select. do nothing, i guess.
    return null;
  }

  return (
    <>
      <Button
        ref={buttonRef}
        shape={ButtonShape.Pill}
        size={ButtonSize.Small}
        variant={ButtonVariant.Secondary}
        {...(restProps as any)}
        onClick={handleClick}
        onKeyDown={handleButtonKeyDown}
        aria-haspopup='listbox'
        aria-expanded={open}
        aria-label={`Select ${selectedOption.title}`}
      >
        {selectedOption.title}
        <svg
          className={clsx('inline-block h-4 w-4 transition-transform', {
            ['rotate-0']: !open,
            ['rotate-180']: open,
          })}
          xmlns='http://www.w3.org/2000/svg'
          viewBox='0 0 20 20'
          fill='currentColor'
        >
          <path
            fillRule='evenodd'
            d='M5.23 7.21a.75.75 0 011.06 0L10 10.93l3.71-3.72a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.23 8.27a.75.75 0 010-1.06z'
            clipRule='evenodd'
          />
        </svg>
      </Button>

      {open &&
        ReactDOM.createPortal(
          <div
            role='listbox'
            aria-label='Select an option'
            tabIndex={-1}
            className={twMerge(
              'prevent-modal-close absolute z-1500 rounded-lg bg-background-secondary p-1 text-foreground-primary shadow-lg',
              menuClassName
            )}
            ref={receiveMenuRef}
            onKeyDown={handleMenuKeyDown}
          >
            {options.map((o, index) => (
              <div
                key={o.value}
                ref={(el) => {
                  optionRefs.current[index] = el;
                }}
                role='option'
                aria-selected={o === selectedOption}
                tabIndex={o.disabled ? -1 : 0}
                className={clsx('relative mt-2 cursor-pointer first:mt-0', {
                  'pointer-events-none': o.disabled,
                  'opacity-50': o.disabled,
                })}
                onClick={
                  !o.disabled
                    ? (e) => {
                        handleSelectOption(o);
                        e.stopPropagation();
                      }
                    : undefined
                }
                onKeyDown={
                  !o.disabled
                    ? (e) => {
                        if (e.key === 'Enter' || e.key === ' ') {
                          e.preventDefault();
                          e.stopPropagation();
                          handleSelectOption(o);
                        }
                      }
                    : undefined
                }
              >
                <o.ListComponent />
                {o === selectedOption && (
                  <svg
                    className='absolute top-3 right-2'
                    width='16'
                    height='12'
                    viewBox='0 0 16 12'
                    fill='none'
                    xmlns='http://www.w3.org/2000/svg'
                  >
                    <path
                      d='M16 1.66109L15.1712 2.44697L6.46573 10.6917L5.67984 11.4347L4.89395 10.6917L0.828756 6.84439L0 6.0585L1.57178 4.39741L2.40054 5.1833L5.67984 8.28756L13.5995 0.78589L14.4282 0L16 1.66109Z'
                      fill='#BAB4B1'
                    />
                  </svg>
                )}
              </div>
            ))}
          </div>,
          document.body
        )}
    </>
  );
};
export default SelectorV2;
