import React, {
  HTMLAttributes,
  PropsWithChildren,
  useEffect,
  useRef,
  useState,
} from "react";
import {
  useCombobox,
  useSelect,
  UseSelectReturnValue,
  UseSelectStateChange,
} from "downshift";
import classNames from "classnames";
import { useCunningham } from ":/components/Provider";
import { Field, FieldProps } from ":/components/Forms/Field";
import { LabelledBox } from ":/components/Forms/LabelledBox";
import { Button } from ":/components/Button";

interface Option {
  value?: string;
  label: string;
  disabled?: boolean;
}

type Props = PropsWithChildren &
  FieldProps & {
    label: string;
    hideLabel?: boolean;
    options: Option[];
    searchable?: boolean;
    name?: string;
    defaultValue?: string | number;
    value?: string | number;
    onChange?: (event: {
      target: { value: string | number | undefined };
    }) => void;
    disabled?: boolean;
    clearable?: boolean;
  };

function getOptionsFilter(inputValue?: string) {
  return (option: Option) => {
    return (
      !inputValue ||
      option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
      option.value?.toLowerCase().includes(inputValue.toLowerCase())
    );
  };
}

const optionToString = (option: Option | null) => {
  return option ? option.label : "";
};

const optionToValue = (option: Option) => {
  return option.value ?? option.label;
};

interface SubProps extends Props {
  defaultSelectedItem?: Option;
  downshiftProps: {
    initialSelectedItem?: Option;
    onSelectedItemChange?: any;
  };
}

interface SelectAuxProps extends SubProps {
  options: Option[];
  labelAsPlaceholder: boolean;
  downshiftReturn: {
    isOpen: boolean;
    wrapperProps?: HTMLAttributes<HTMLDivElement>;
    selectedItem?: Option | null;
    getLabelProps: any;
    toggleButtonProps: any;
    getMenuProps: any;
    getItemProps: any;
    highlightedIndex: number;
    selectItem: UseSelectReturnValue<Option>["selectItem"];
  };
}

/**
 * This component is used by searchable and non-searchable select components.
 * It contains the common logic between the two.
 */
const SelectAux = ({
  children,
  state = "default",
  text,
  rightText,
  fullWidth,
  options,
  name,
  label,
  hideLabel,
  labelAsPlaceholder,
  downshiftProps,
  downshiftReturn,
  value,
  disabled,
  clearable = true,
}: SelectAuxProps) => {
  const { t } = useCunningham();
  const labelProps = downshiftReturn.getLabelProps();

  // When component is controlled, this useEffect will update the local selected item.
  useEffect(() => {
    if (downshiftProps.initialSelectedItem !== undefined) {
      return;
    }
    const optionToSelect = options.find(
      (option) => optionToValue(option) === value
    );
    downshiftReturn.selectItem(optionToSelect ?? null);
  }, [value]);

  return (
    <Field
      state={state}
      text={text}
      rightText={rightText}
      fullWidth={fullWidth}
    >
      <div
        className={classNames("c__select", "c__select--" + state, {
          "c__select--disabled": disabled,
        })}
      >
        {/* We disabled linting for this specific line because we consider that the onClick props is only used for */}
        {/* mouse users, so this do not engender any issue for accessibility. */}
        {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
        <div
          className={classNames("c__select__wrapper", {
            "c__select__wrapper--focus": downshiftReturn.isOpen && !disabled,
          })}
          {...downshiftReturn.wrapperProps}
        >
          {downshiftReturn.selectedItem && (
            <input
              type="hidden"
              name={name}
              value={optionToValue(downshiftReturn.selectedItem)}
            />
          )}

          <LabelledBox
            label={label}
            hideLabel={hideLabel}
            labelAsPlaceholder={labelAsPlaceholder}
            htmlFor={labelProps.htmlFor}
            labelId={labelProps.id}
          >
            <div className="c__select__inner">
              <div className="c__select__inner__value">{children}</div>
              <div className="c__select__inner__actions">
                {clearable && !disabled && downshiftReturn.selectedItem && (
                  <>
                    <Button
                      color="tertiary"
                      size="small"
                      aria-label={t(
                        "components.forms.select.clear_button_aria_label"
                      )}
                      className="c__select__inner__actions__clear"
                      onClick={(e) => {
                        downshiftReturn.selectItem(null);
                        e.stopPropagation();
                      }}
                      icon={<span className="material-icons">close</span>}
                    />
                    <div className="c__select__inner__actions__separator" />
                  </>
                )}

                <Button
                  color="tertiary"
                  size="small"
                  className="c__select__inner__actions__open"
                  icon={
                    <span
                      className={classNames("material-icons", {
                        opened: downshiftReturn.isOpen,
                      })}
                    >
                      arrow_drop_down
                    </span>
                  }
                  disabled={disabled}
                  {...downshiftReturn.toggleButtonProps}
                />
              </div>
            </div>
          </LabelledBox>
        </div>
        <div
          className={classNames("c__select__menu", {
            "c__select__menu--opened": downshiftReturn.isOpen,
          })}
          {...downshiftReturn.getMenuProps()}
        >
          <ul>
            {downshiftReturn.isOpen &&
              options.map((item, index) => {
                const isActive = index === downshiftReturn.highlightedIndex;
                return (
                  <li
                    className={classNames("c__select__menu__item", {
                      "c__select__menu__item--highlight": isActive,
                      "c__select__menu__item--selected":
                        downshiftReturn.selectedItem === item,
                      "c__select__menu__item--disabled": item.disabled,
                    })}
                    key={`${item.value}${index}`}
                    {...downshiftReturn.getItemProps({
                      item,
                      index,
                      isActive,
                      disabled: item.disabled,
                    })}
                  >
                    <span>{item.label}</span>
                  </li>
                );
              })}
          </ul>
        </div>
      </div>
    </Field>
  );
};

const SelectSimple = (props: SubProps) => {
  const downshiftReturn = useSelect({
    ...props.downshiftProps,
    items: props.options,
    itemToString: optionToString,
  });
  const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
    !downshiftReturn.selectedItem
  );

  useEffect(() => {
    setLabelAsPlaceholder(!downshiftReturn.selectedItem);
  }, [downshiftReturn.selectedItem]);

  return (
    <SelectAux
      {...props}
      downshiftReturn={{
        ...downshiftReturn,
        wrapperProps: downshiftReturn.getToggleButtonProps({
          disabled: props.disabled,
        }),
        toggleButtonProps: {},
      }}
      labelAsPlaceholder={labelAsPlaceholder}
    >
      {downshiftReturn.selectedItem && (
        <span>{optionToString(downshiftReturn.selectedItem)}</span>
      )}
    </SelectAux>
  );
};

const SelectSearchable = (props: SubProps) => {
  const { t } = useCunningham();
  const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
  const [hasInputFocused, setHasInputFocused] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);
  const downshiftReturn = useCombobox({
    ...props.downshiftProps,
    items: optionsToDisplay,
    itemToString: optionToString,
    onInputValueChange: (e) => {
      setOptionsToDisplay(props.options.filter(getOptionsFilter(e.inputValue)));
      if (!e.inputValue) {
        downshiftReturn.selectItem(null);
      }
    },
  });

  const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
    !downshiftReturn.selectedItem
  );
  useEffect(() => {
    if (hasInputFocused || downshiftReturn.inputValue) {
      setLabelAsPlaceholder(false);
      return;
    }
    setLabelAsPlaceholder(!downshiftReturn.selectedItem);
  }, [
    downshiftReturn.selectedItem,
    hasInputFocused,
    downshiftReturn.inputValue,
  ]);

  const inputProps = downshiftReturn.getInputProps({
    ref: inputRef,
    disabled: props.disabled,
  });

  return (
    <SelectAux
      {...props}
      downshiftReturn={{
        ...downshiftReturn,
        wrapperProps: {
          onClick: () => {
            inputRef.current?.focus();
          },
        },
        toggleButtonProps: downshiftReturn.getToggleButtonProps({
          disabled: props.disabled,
          "aria-label": t("components.forms.select.toggle_button_aria_label"),
        }),
      }}
      labelAsPlaceholder={labelAsPlaceholder}
      options={optionsToDisplay}
    >
      <input
        className="w-full p-1.5"
        {...inputProps}
        onFocus={(e) => {
          inputProps.onFocus(e);
          setHasInputFocused(true);
        }}
        onBlur={(e) => {
          inputProps.onBlur(e);
          setHasInputFocused(false);
        }}
      />
    </SelectAux>
  );
};

export const Select = (props: Props) => {
  if (props.defaultValue && props.value) {
    throw new Error(
      "You cannot use both defaultValue and value props on Select component"
    );
  }

  const defaultSelectedItem = props.defaultValue
    ? props.options.find(
        (option) => optionToValue(option) === props.defaultValue
      )
    : undefined;

  const commonDownshiftProps: SubProps["downshiftProps"] = {
    initialSelectedItem: defaultSelectedItem,
    onSelectedItemChange: (e: UseSelectStateChange<Option>) => {
      props.onChange?.({
        target: {
          value: e.selectedItem ? optionToValue(e.selectedItem) : undefined,
        },
      });
    },
  };

  return props.searchable ? (
    <SelectSearchable {...props} downshiftProps={commonDownshiftProps} />
  ) : (
    <SelectSimple {...props} downshiftProps={commonDownshiftProps} />
  );
};
