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 { twJoin } from "tailwind-merge";

import { useInputStyles } from "../input/Input";
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 { paperStyles } from "../menu/Menu";
import { Typography } from "../typography/Typography";
import { MenuItem } from "../menu/MenuItem";
import { RenderedValue } from "../select/RenderedValue";
import { twMerge } from "../../../tailwind.config";

export function useAutocompleteStyles({
  height,
}: Pick<AutocompleteProps, "height">) {
  const classes = {
    inputRoot: "!pl-6 !pr-[48px] py-6",
    input: twJoin(
      "placeholder:text-N60 min-w-[100px]",
      height === "40" ? "pl-6 pr-6 py-4" : "pl-2 pr-2 py-0"
    ),
    endAdornment: twJoin(
      "right-2",
      height === "40" ? "top-[7px]" : "top-[1px] mt-2"
    ),
    popupIndicator: "transform-none",
    clearIndicator: "mr-24",
    listbox: "p-0",
    paper: twJoin(paperStyles, "max-w-[unset] mt-4"),
    option: twJoin(
      "items-start rounded-6 p-8 text-style-R200 hover:bg-N20 [&.Mui-focused]:bg-N20",
      "aria-selected:bg-transparent aria-selected:hover:bg-N20 aria-selected:[&.Mui-focused]:bg-N20"
    ),
    tag: "m-0",
    noOptions: "p-8 text-N700 text-style-R200",
  };

  return classes;
}

export interface AutocompleteProps
  extends Omit<
    MUIAutocompleteProps<SelectOption, boolean, boolean, false>,
    "onChange" | "renderInput" | "className"
  > {
  height?: "40" | "32";
  options: SelectOption[];
  multiple?: boolean;
  value?: SelectOption | SelectOption[] | null;
  label?: string | ReactNode;
  required?: boolean;
  filterSelectedOptions?: boolean;
  noOptionsText?: string;
  NoOptionFindComponent?: React.ReactNode;
  placeholder?: string;
  error?: boolean;
  errorText?: string;
  onInputChange?: (event: React.ChangeEvent<{}>, value: string) => void;
  onChange?: (
    event: React.ChangeEvent<{}>,
    value:
      | SelectOption
      | SelectOption[]
      | (SelectOption | SelectOption[])[]
      | null
  ) => void;
  ESCAPE_className?: string;
}

export const Autocomplete = forwardRef<HTMLDivElement, AutocompleteProps>(
  (
    {
      height = "40",
      options,
      multiple,
      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
                  ),
                  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={(event, selected) => onChange?.(event, selected)}
          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
                key={optionValue}
                value={optionValue}
                data-testid={optionValue}
                leftIcon={icon}
                rightIcon={selected ? "checkmark" : undefined}
                disabled={option?.disabled}
                {...restProps}
              >
                <div className="flex-1">
                  <Typography
                    size="200"
                    color="main"
                    ESCAPE_className="disabled:text-N60"
                  >
                    {label}
                  </Typography>
                </div>
              </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;
          }}
          {...restProps}
        />
        {required && <RequiredIndicator hasLabel={!!label} />}
      </FormControl>
    );
  }
);
