import {
  GetPropsCommonOptions,
  UseSelectGetLabelPropsOptions,
  UseSelectGetToggleButtonPropsOptions,
} from "downshift";
import classNames from "classnames";
import React, {
  Dispatch,
  KeyboardEvent,
  ReactNode,
  SetStateAction,
  useMemo,
  useRef,
} from "react";
import mergeRefs from "react-merge-refs";
import { bemHOF } from "../../../utilities";
import { Box } from "../../Box";
import { Flex } from "../../Flex";
import { Icon, ICON_TYPE } from "../../Icon";
import { Label } from "../../Label";
import { VisuallyHidden } from "../../VisuallyHidden";

const cn = bemHOF("SelectNew");

type DotProps = { delay: number };
const LoadingDot = ({ delay }: DotProps) => (
  <Box
    as="span"
    className={cn({ e: "loading-dot" })}
    style={{
      animation: `loadingDot 1s ease-in-out ${delay}ms infinite`,
    }}
  />
);

const SelectLoadingIndicator = () => {
  return (
    <Box className={cn({ e: "loading-indicator" })}>
      <LoadingDot delay={0} />
      <LoadingDot delay={160} />
      <LoadingDot delay={320} />
    </Box>
  );
};

interface SelectControlProps {
  setReferenceElement: Dispatch<SetStateAction<Element | null>>;
  label: string | undefined;
  hasContextLabel: boolean | undefined;
  getLabelProps: (options?: UseSelectGetLabelPropsOptions | undefined) => any;
  getToggleButtonProps: (
    options?: UseSelectGetToggleButtonPropsOptions | undefined,
    otherOptions?: GetPropsCommonOptions | undefined,
  ) => any;
  isDisabled: boolean | undefined;
  children: ReactNode;
  isClearable: boolean | undefined;
  isLoading: boolean | undefined;
  onClear: () => void;
  hasSelection: boolean | undefined;
  className: string;
}

export function SelectControl({
  setReferenceElement,
  label,
  hasContextLabel,
  getLabelProps,
  getToggleButtonProps,
  isDisabled,
  children,
  isClearable,
  isLoading,
  onClear,
  hasSelection,
  className,
}: SelectControlProps) {
  const isClearBtnVisible = isClearable && hasSelection && !isLoading;
  const clearBtnRef = useRef<HTMLButtonElement>(null);
  const toggleBtnRef = useRef<HTMLButtonElement>(null);

  const toggleButtonProps = useMemo(() => {
    const downshiftToggleButtonProps = getToggleButtonProps({
      disabled: isDisabled,
    });
    return {
      ...downshiftToggleButtonProps,
      ref: mergeRefs([toggleBtnRef, downshiftToggleButtonProps.ref]),
      onKeyDown: (e: KeyboardEvent<HTMLButtonElement>) => {
        downshiftToggleButtonProps.onKeyDown(e);

        if (e.key === "Tab" && isClearBtnVisible && clearBtnRef.current) {
          e.preventDefault();
          clearBtnRef.current.focus();
        }
      },
    };
  }, [getToggleButtonProps, isDisabled, isClearBtnVisible]);

  return (
    <Box ref={setReferenceElement}>
      {label && !hasContextLabel && (
        <VisuallyHidden>
          <Label {...getLabelProps()}>{label}</Label>
        </VisuallyHidden>
      )}
      <button type="button" className={className} {...toggleButtonProps}>
        {children}
        <Flex className="items-center">
          {/* This Box has a static width so it doesn't push selected items(s) around when hidden/shown */}
          <Box className="w-5 justify-center">
            {isClearBtnVisible && (
              <Box
                ref={clearBtnRef}
                role="button"
                tabIndex={0}
                className={classNames(
                  "flex",
                  "items-center",
                  "text-gray-600",
                  "hover:text-gray-700",
                  "leading-none",
                  "h-5",
                  "w-5",
                  "justify-center",
                  "focus:shadow-focus",
                  "outline-none",
                  "rounded",
                )}
                aria-label="clear selection"
                onClick={(e) => {
                  e.stopPropagation();
                  onClear();
                }}
                onKeyDown={(e) => {
                  e.stopPropagation();
                  if (e.key === "Enter" || e.key === " ") {
                    e.preventDefault();
                    onClear();
                    // Put focus back on the toggle button once cleared
                    if (toggleBtnRef.current) {
                      toggleBtnRef.current.focus();
                    }
                  }
                }}
              >
                <Icon icon={ICON_TYPE.TIMES} />
              </Box>
            )}
            {isLoading && <SelectLoadingIndicator />}
          </Box>
          <Icon icon={ICON_TYPE.CARET_DOWN} className="text-gray-600" />
        </Flex>
      </button>
    </Box>
  );
}
