import React, {
  useState,
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
  ReactNode,
  useCallback
} from "react";
import ReactDOM from "react-dom";
import cn from "classnames";
import { Manager, Reference, Popper } from "react-popper";
import { getPressedKey, KEYS } from "../utils/keyboard";
import { Button } from "../";

type ButtonVariants =
  | "primary"
  | "secondary"
  | "success"
  | "danger"
  | "warning"
  | "info"
  | "dark"
  | "light"
  | "link"
  | "white"
  | "outline-primary"
  | "outline-secondary"
  | "outline-success"
  | "outline-danger"
  | "outline-warning"
  | "outline-info"
  | "outline-dark"
  | "outline-light"
  | "outline-white";

export interface IDropdownProps {
  ["data-testid"]?: string;
  arrow?: boolean;
  autoClose?: boolean;
  btnClassName?: string;
  btnRef?: React.RefObject<HTMLButtonElement>;
  btnSize?: "lg" | "sm";
  btnVariant?: ButtonVariants;
  children?: React.ReactNode;
  className?: string;
  disabled?: boolean;
  dropdownMenuClassName?: string;
  header: ReactNode;
  id?: string;
  maxHeight?: number | string;
  maxWidth?: number | string;
  minHeight?: number | string;
  minWidth?: number | string;
  onClose?: () => void;
  onToggle?: (isOpen: boolean | null) => void;
  position?: "right" | "left";
  useCapture?: boolean;
  useFormControl?: boolean;
  width?: number;
}

export interface InputHandlers {
  closeDropdown(): void;
}

export interface IPopoverWrapperProps {
  children: React.ReactNode;
  scheduleUpdate: () => void;
}

function useKeyboard(
  itemsContainerRef: React.RefObject<HTMLDivElement>,
  isOpen: boolean | null,
  setOpen: React.Dispatch<React.SetStateAction<boolean | null>>,
  autoClose?: boolean,
  useCapture?: boolean
) {
  // ESC should close dropdown
  // Arrows down/up should navigate to next/prev (or first) element
  // by WAI-ARIA Authoring Practices
  useEffect(() => {
    // Avoid adding global event listener if dropdown is not open
    if (!isOpen) {
      return;
    }

    const handler = (e: KeyboardEvent) => {
      const key = getPressedKey(e);

      if (key === KEYS.Escape) {
        setOpen(false);
        return;
      }

      if (!itemsContainerRef.current || !e.target) {
        return;
      }

      if (key !== "ArrowDown" && key !== "ArrowUp") {
        return;
      }

      const items: HTMLElement[] = [].slice.call(
        itemsContainerRef.current.querySelectorAll(
          ".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)"
        )
      );

      if (!items.length) {
        return;
      }

      let currentFocusedIndex = items.indexOf(e.target as HTMLElement);

      if (key === "ArrowUp" && currentFocusedIndex > 0) {
        currentFocusedIndex--;
      }

      if (key === "ArrowDown" && currentFocusedIndex < items.length - 1) {
        currentFocusedIndex++;
      }

      if (currentFocusedIndex < 0) {
        currentFocusedIndex = 0;
      }

      items[currentFocusedIndex].focus();
      e.preventDefault();
    };
    document.addEventListener("keydown", handler, useCapture);
    return () => {
      document.removeEventListener("keydown", handler, useCapture);
    };
  }, [isOpen]);

  // Automatically close on container if Enter pressed
  const onContainerKeyPress = useCallback(
    (e: React.KeyboardEvent) => {
      if (!autoClose) {
        return;
      }
      const key = getPressedKey(e);
      if (key === KEYS.Enter) {
        setOpen(false);
      }
    },
    [autoClose]
  );

  return onContainerKeyPress;
}

function useDropdownContainer() {
  const portalContainerRef = useRef<HTMLDivElement>();

  // Creating container for dropdown due to React problems with appending to body
  // https://stackoverflow.com/questions/49504546/is-it-safe-to-use-reactdom-createportal-with-document-body
  useEffect(() => {
    let portalContainer = document.querySelector("#honeyui-dropdown-container") as HTMLDivElement;
    // Trying to reuse container to avoid creation multiple DOM Nodes, each for every dropdown
    if (!portalContainer) {
      portalContainer = document.createElement("div");
      portalContainer.setAttribute("id", "honeyui-dropdown-container");
      document.body.appendChild(portalContainer);
    }
    portalContainerRef.current = portalContainer;
  }, []);

  return portalContainerRef;
}

const PopoverWrapper: React.FC<IPopoverWrapperProps> = props => {
  const { scheduleUpdate, children } = props;

  const mounted = useRef(false);

  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true;
    } else {
      scheduleUpdate();
    }
  }, [children]);

  return <>{children}</>;
};

const Dropdown = forwardRef<InputHandlers, IDropdownProps>((props, ref) => {
  const {
    arrow,
    autoClose,
    btnClassName,
    btnRef,
    btnSize,
    btnVariant,
    children,
    className,
    disabled,
    dropdownMenuClassName,
    header,
    id,
    maxHeight,
    maxWidth,
    minHeight,
    minWidth,
    onClose,
    onToggle,
    position,
    useCapture,
    useFormControl
  } = props;
  const testId = props["data-testid"] || id || "honeyui-dropdown";

  const [isOpen, setOpen] = useState<boolean | null>(null);
  const defaultButtonRef = useRef<HTMLButtonElement>(null);
  const itemsContainerRef = useRef<HTMLDivElement>(null);
  const portalContainerRef = useDropdownContainer();
  const onContainerKeyPress = useKeyboard(
    itemsContainerRef,
    isOpen,
    setOpen,
    autoClose,
    useCapture
  );

  useImperativeHandle(ref, () => ({
    closeDropdown() {
      setOpen(false);
    }
  }));

  // Global click should close dropdown
  useEffect(() => {
    // Avoid adding global event listener if dropdown is not open
    if (!isOpen) {
      return;
    }
    const handler = (e: MouseEvent) => {
      if (!itemsContainerRef.current) {
        return;
      }
      if (!(e.target instanceof Element)) {
        return;
      }
      if (itemsContainerRef.current.contains(e.target)) {
        return;
      }
      if ((btnRef || defaultButtonRef).current?.contains(e.target)) {
        return;
      }
      setOpen(false);
    };

    document.addEventListener("click", handler, useCapture);
    return () => {
      document.removeEventListener("click", handler, useCapture);
    };
  }, [isOpen]);

  useEffect(() => {
    if (isOpen === false && onClose) {
      onClose();
    }

    if (onToggle) {
      onToggle(isOpen);
    }
  }, [isOpen]);

  return (
    <Manager>
      <Reference>
        {({ ref }) => (
          <div className={cn("dropdown", className)} ref={ref}>
            <Button
              aria-expanded={isOpen ? "true" : "false"}
              aria-haspopup="true"
              className={cn(btnClassName, {
                "dropdown-toggle": arrow,
                "form-control": useFormControl
              })}
              data-testid={testId}
              data-toggle="dropdown"
              disabled={disabled}
              onClick={() => setOpen(!isOpen)}
              ref={btnRef || defaultButtonRef}
              size={btnSize}
              variant={btnVariant}
            >
              {header}
            </Button>
          </div>
        )}
      </Reference>
      {/* eslint-disable @typescript-eslint/indent */}
      {isOpen && portalContainerRef.current
        ? ReactDOM.createPortal(
            <Popper placement={(position === "left" && "bottom-start") || "bottom-end"}>
              {({ ref, style, placement, scheduleUpdate }) => (
                // zIndex: 1050 is modal zIndex so 1051 is next one
                <div
                  ref={ref}
                  style={{ ...style, zIndex: 1051, maxWidth, maxHeight, minHeight, minWidth }}
                  data-placement={placement}
                  className={cn("dropdown-menu", "overflow-auto", dropdownMenuClassName, {
                    "dropdown-menu-right": position === "right",
                    "dropdown-menu-left": position === "left",
                    show: isOpen
                  })}
                >
                  <div
                    role="presentation"
                    ref={itemsContainerRef}
                    onClick={() => autoClose && setOpen(false)}
                    onKeyPress={onContainerKeyPress}
                    data-testid={`${testId}-dropdown-body`}
                    aria-labelledby="dropdownMenuButton"
                  >
                    <PopoverWrapper {...{ scheduleUpdate }}>{children}</PopoverWrapper>
                  </div>
                </div>
              )}
            </Popper>,
            portalContainerRef.current
          )
        : null}
      {/* eslint-enable */}
    </Manager>
  );
});

Dropdown.displayName = "Dropdown";
Dropdown.defaultProps = {
  arrow: false,
  btnVariant: "primary",
  id: "honeyui-dropdown",
  maxHeight: 284,
  maxWidth: 300,
  minHeight: 0,
  minWidth: 0,
  position: "right",
  useCapture: false,
  useFormControl: false
};

export default Dropdown;
