import {
  FormControl,
  TextField,
  Autocomplete as MUIAutocomplete,
  type AutocompleteProps as MUIAutocompleteProps,
} from "@mui/material";
import React, { forwardRef, ReactNode } from "react";
import Popper from "@mui/material/Popper";

import { useInputStyles } from "../input/Input.styles";
import { RequiredIndicator } from "../input/RequiredIndicator";
import { Icon } from "../icon/Icon";
import { SelectIcon } from "../select/SelectIcon";
import { SelectOption } from "../select/types";
import { InputLabel } from "../inputLabel/InputLabel";
import { ErrorIcon } from "../input/ErrorIcon";
import { Typography } from "../typography/Typography";
import { MenuItem } from "../menu/MenuItem";
import { RenderedValue } from "../select/RenderedValue";
import { twMerge } from "../../../tailwind.config";
import { useAutocompleteStyles } from "./Autocomplete.styles";
import { twJoin } from "tailwind-merge";

export interface AutocompleteProps
  extends Omit<
    MUIAutocompleteProps<SelectOption, boolean, boolean, false>,
    | "renderInput"
    | "className"
    | "groupBy"
    | "renderGroup"
    | "getOptionDisabled"
  > {
  height?: "40" | "32";
  options: SelectOption[];
  multiple?: boolean;
  grouped?: boolean;
  value?: SelectOption | SelectOption[] | null;
  label?: string | ReactNode;
  required?: boolean;
  filterSelectedOptions?: boolean;
  NoOptionFindComponent?: React.ReactNode;
  placeholder?: string;
  error?: boolean;
  errorText?: string;
  ESCAPE_className?: string;
}

export const Autocomplete = forwardRef<HTMLDivElement, AutocompleteProps>(
  (
    {
      height = "40",
      options,
      multiple,
      grouped,
      fullWidth,
      onChange,
      label,
      required,
      value,
      error,
      errorText,
      filterSelectedOptions,
      noOptionsText,
      NoOptionFindComponent,
      onInputChange,
      placeholder,
      id,
      disabled,
      ESCAPE_className,
      ...restProps
    },
    ref
  ) => {
    const autocompleteClasses = useAutocompleteStyles({ height });
    const inputClasses = useInputStyles({
      height,
      variant: "outlined",
      error,
      disabled,
      gap: (value as SelectOption[])?.length > 1 ? 6 : undefined,
    });

    const errorIconComponent = (
      <ErrorIcon
        style={{
          position: "absolute",
          marginRight: "12px",
          right: "0",
        }}
        errorText={errorText}
      />
    );

    return (
      <FormControl fullWidth={fullWidth}>
        {typeof label === "string" ? (
          <InputLabel htmlFor={id}>{label}</InputLabel>
        ) : (
          label
        )}
        <MUIAutocomplete
          id={id}
          ref={ref}
          filterSelectedOptions={filterSelectedOptions}
          multiple={multiple}
          classes={{
            root: ESCAPE_className,
            ...autocompleteClasses,
          }}
          options={options}
          disabled={disabled}
          getOptionDisabled={(option) => option?.disabled ?? false}
          getOptionLabel={(option) => (option as SelectOption).label}
          popupIcon={error ? errorIconComponent : <SelectIcon />}
          clearIcon={
            <Icon name="xmark" size="16" weight="bold" color="secondary" />
          }
          fullWidth={fullWidth}
          PopperComponent={({ style, ...props }) => {
            return (
              <Popper
                {...props}
                style={{
                  ...style,
                  width: (style?.width as number) + 8,
                  paddingTop: "4px",
                }}
              />
            );
          }}
          renderInput={({ InputProps, ...params }) => (
            <TextField
              placeholder={placeholder}
              error={error}
              InputProps={{
                ...InputProps,
                // ↓ To override the `className` coming from the `InputProps` and use `classes` instead
                className: "",
                classes: {
                  ...inputClasses,
                  root: twMerge(
                    inputClasses.root,
                    "gap-6",
                    InputProps.className,
                    disabled && "pointer-events-none"
                  ),
                  input: twMerge(inputClasses.input, height === "32" && "py-0"),
                },
              }}
              {...params}
              variant="outlined"
            />
          )}
          renderTags={(selectedOptions, getTagProps) => (
            <RenderedValue
              height={height}
              getTagProps={getTagProps}
              selected={
                selectedOptions.map((option) => option.value) as
                  | string
                  | string[]
              }
              options={options}
              multiple={multiple}
              placeholder={placeholder}
            />
          )}
          onChange={onChange}
          value={value}
          renderOption={(props, option, { selected }) => {
            const { value: optionValue, icon, label } = option as SelectOption;
            if (optionValue === "no-option-found" && NoOptionFindComponent) {
              return NoOptionFindComponent;
            }

            // Types of property color are incompatible
            const { color: _, ...restProps } = props;

            return (
              <MenuItem
                value={optionValue}
                data-testid={optionValue}
                leftIcon={icon}
                rightIcon={selected ? "checkmark" : undefined}
                disabled={option?.disabled}
                {...restProps}
                key={optionValue} // MUI uses the label as the key in props 🤡
              >
                {label}
              </MenuItem>
            );
          }}
          noOptionsText={noOptionsText}
          onInputChange={onInputChange}
          filterOptions={(options, { inputValue }) => {
            const filtredOptions = options.filter((option) => {
              return (option as SelectOption).label
                .toLowerCase()
                .includes(inputValue.toLowerCase());
            });

            if (!filtredOptions.length && NoOptionFindComponent) {
              return [{ value: "no-option-found", label: inputValue }];
            }

            return filtredOptions;
          }}
          groupBy={
            grouped
              ? (option) => (option as SelectOption).groupLabel ?? ""
              : undefined
          }
          renderGroup={({ key, group, children }) => {
            // MUI messed up the typings - key is the index of the group
            // casting it to int just to be sure
            const index = parseInt(key);

            return (
              <div key={key}>
                <Typography
                  size="75"
                  weight="medium"
                  uppercase
                  color="inherit"
                  ESCAPE_className={twJoin(
                    "text-N100 -mx-8 block max-w-full overflow-hidden text-ellipsis whitespace-nowrap py-8 px-16",
                    index !== 0 &&
                      "mt-8 border-0 border-t-1 border-solid border-N30 pt-16",
                    group === "" && (index !== 0 ? "p-4" : "p-0")
                  )}
                >
                  {group}
                </Typography>
                {children}
              </div>
            );
          }}
          {...restProps}
        />
        {required && <RequiredIndicator hasLabel={!!label} />}
      </FormControl>
    );
  }
);

Autocomplete.displayName = "Autocomplete";
