import React, { useContext, ReactNode, Ref, CSSProperties } from "react";
import classNames from "classnames";
import ReactSelect, {
  Props as ReactSelectProps,
  components as ReactSelectComponents,
  Styles as ReactSelectStyles,
  IndicatorProps as ReactSelectIndicatorProps,
  OptionProps as ReactSelectOptionProps,
  MultiValueProps as ReactSelectMultiValueProps,
} from "react-select";
import { SelectComponents } from "react-select/src/components";
import { MenuListComponentProps } from "react-select/src/components/Menu";
import ReactSelectCreatable, {
  Props as ReactSelectCreatableProps,
} from "react-select/creatable";
import ReactSelectAsync, {
  Props as ReactSelectAsyncProps,
} from "react-select/async";
import { colors, height, zIndex } from "../../tokens";
import { STATUS_VARIANT } from "../../types";
import { bemHOF, toNumber } from "../../utilities";
import { BoxProps, Box } from "../Box";
import { Flex } from "../Flex";
import { FormGroupContext } from "../FormGroup/FormGroupContext";
import { Icon, ICON_TYPE } from "../Icon";
import { CloseButton } from "../CloseButton";
import { Token } from "../Token";
import { adsTheme } from "./theme";

const cn = bemHOF("Select");

const MultiValue = ({
  removeProps,
  selectProps,
  children,
}: ReactSelectMultiValueProps<any>) => {
  const { onClick: onRemove, ...restRemoveProps } = removeProps;
  const { isDisabled } = selectProps;
  return (
    <Token
      className={classNames("m-1 h-7 px-2", isDisabled && "bg-gray-400")}
      removable={!isDisabled}
      onRemove={!isDisabled ? removeProps.onClick : undefined}
      {...(!isDisabled ? restRemoveProps : [])}
    >
      {children}
    </Token>
  );
};

const OptionComponent = ({
  children,
  isFocused = false,
  ...rest
}: {
  children: ReactNode;
  isFocused?: boolean;
}) => {
  return (
    <Flex
      className={classNames([
        cn({ e: "newOption" }),
        isFocused && cn({ e: "newOption", m: "isFocused" }),
      ])}
      {...rest}
    >
      <Flex className={cn({ e: "newOption-label" })}>
        <Icon
          className={cn({ e: "newOption-labelIcon" })}
          icon={ICON_TYPE.PLUS_CIRCLE}
        />
        <Box className={cn({ e: "newOption-labelText" })} as="span">
          {children}
        </Box>
      </Flex>
      <Box
        className={classNames([cn({ e: "newOption-helperText" })])}
        as="span"
      >
        Or press [Enter]
      </Box>
    </Flex>
  );
};

const adsComponents = {
  IndicatorSeparator: null,
  DropdownIndicator: ({ className: iconClassName }: BoxProps) => (
    <Icon
      className={classNames([cn({ e: "dropdown-indicator" }), iconClassName])}
      icon={ICON_TYPE.CARET_DOWN}
    />
  ),
  ClearIndicator: ({ innerProps }: ReactSelectIndicatorProps<any>) => (
    <CloseButton {...innerProps} />
  ),
  MultiValue,
  Option: (props: ReactSelectOptionProps<any>) => {
    const { data, innerRef, isFocused, children, innerProps } = props;
    if (data.__isNew__) {
      return (
        <Box ref={innerRef}>
          <OptionComponent isFocused={isFocused} {...innerProps}>
            {children}
          </OptionComponent>
        </Box>
      );
    }

    return <ReactSelectComponents.Option {...props} />;
  },
};

const getClassNames = (
  classNameProp: string | undefined,
  variant?: STATUS_VARIANT,
) => {
  return classNames([cn(), cn({ m: variant }), classNameProp]);
};

const getMenuListComponent = (menuRef?: Ref<HTMLDivElement>) => ({
  innerRef,
  children,
  ...listRest
}: MenuListComponentProps<any>) => (
  <ReactSelectComponents.MenuList innerRef={innerRef} {...listRest}>
    <Box ref={menuRef}>{children}</Box>
  </ReactSelectComponents.MenuList>
);

const generateHardcodedProps = ({
  controlHeight,
  menuInPortal,
  menuRef,
  components,
  styles,
}: {
  controlHeight: number;
  menuInPortal: boolean;
  menuRef?: Ref<HTMLDivElement>;
  components?: Partial<SelectComponents<any>>;
  styles?: ReactSelectStyles;
}) => {
  const customComponents = menuInPortal
    ? { ...adsComponents, MenuList: getMenuListComponent(menuRef) }
    : adsComponents;
  return {
    components: { ...customComponents, ...components },
    menuPortalTarget: menuInPortal ? document.body : undefined,
    styles: {
      ...styles,
      menuPortal: (base: CSSProperties): CSSProperties => ({
        ...base,
        zIndex: zIndex.max,
      }),
      singleValue: (base: CSSProperties, state: any): CSSProperties => {
        return {
          ...base,
          paddingLeft: state.selectProps.isSearchable ? "2px" : "0",
          color:
            state.selectProps.menuIsOpen && state.selectProps.isSearchable
              ? colors.gray["600"]
              : base.color,
        };
      },
    },
    classNamePrefix: cn(),
    theme: {
      ...adsTheme,
      spacing: {
        ...adsTheme.spacing,
        controlHeight,
      },
    },
  };
};

type ArrowSelectProps = {
  menuInPortal?: boolean;
  menuRef?: Ref<HTMLDivElement>;
  height?: number;
};

export type SelectProps = ReactSelectProps & ArrowSelectProps;
export type SelectCreatableProps = ReactSelectCreatableProps<any> &
  ArrowSelectProps;
export type SelectAsyncProps = ReactSelectAsyncProps<any> & ArrowSelectProps;

export type SelectBaseProps =
  | ({ type: "default" } & SelectProps)
  | ({ type: "creatable" } & SelectCreatableProps)
  | ({ type: "async" } & SelectAsyncProps);

const SelectBase = ({
  variant: variantProp = STATUS_VARIANT.DEFAULT,
  id: idProp,
  className,
  height: controlHeight = toNumber(height["10"]),
  menuInPortal = false,
  menuRef,
  components,
  styles,
  ...rest
}: SelectBaseProps) => {
  const { variant: variantContext, id: idContext } = useContext(
    FormGroupContext,
  );
  const id = idContext || idProp;
  const variant = variantContext || variantProp;
  const hardcodedProps = generateHardcodedProps({
    controlHeight,
    menuInPortal,
    menuRef,
    styles,
    components,
  });

  if (rest.type === "async") {
    return (
      <ReactSelectAsync
        className={getClassNames(className, variant)}
        id={id}
        {...rest}
        {...hardcodedProps}
      />
    );
  }

  if (rest.type === "creatable") {
    return (
      <ReactSelectCreatable
        className={getClassNames(className, variant)}
        id={id}
        {...rest}
        {...hardcodedProps}
        createOptionPosition="first"
      />
    );
  }

  return (
    <ReactSelect
      className={getClassNames(className, variant)}
      id={id}
      {...rest}
      {...hardcodedProps}
    />
  );
};

const SelectAsync = (props: SelectAsyncProps) => {
  return <SelectBase {...props} type="async" />;
};

const SelectCreatable = (props: SelectCreatableProps) => {
  return <SelectBase {...props} type="creatable" />;
};

const Select = (props: SelectProps) => {
  return <SelectBase {...props} type="default" />;
};

Select.Creatable = SelectCreatable;
Select.Async = SelectAsync;
export { Select };
