import {
  Autocomplete as MUIAutocomplete,
  autocompleteClasses as muiAutocompleteClasses,
  AutocompleteProps as MUIAutocompleteProps,
  AutocompleteRenderInputParams as MUIAutocompleteRenderInputParams,
  Box,
  Grow as MUIGrow,
  ListSubheader,
  outlinedInputClasses as muiOutlinedInputClasses,
  Paper as MUIPaper,
  PaperProps as MUIPaperProps,
  svgIconClasses as muiSvgIconClasses,
  styled,
} from '@mui/material';
import { useMemo } from 'react';

import { GREY, TEXT } from '../lib/consts/DEFAULT_THEME';

import InputMessageBox from './InputMessageBox';
import { MessageBoxProps } from './MessageBox';
import OutlinedInputWithLabel, {
  OutlinedInputWithLabelProps,
} from './OutlinedInputWithLabel';

type AutocompleteOptionalProps = {
  extendRenderInput?: (
    inputWithLabelProps: OutlinedInputWithLabelProps,
    renderInputParams?: MUIAutocompleteRenderInputParams,
  ) => void;
  getGroupLabel?: (group: string) => React.ReactNode;
  messageBoxProps?: Partial<MessageBoxProps>;
};

type AutocompleteProps<
  T,
  Multiple extends boolean | undefined,
  DisableClearable extends boolean | undefined,
  FreeSolo extends boolean | undefined,
> = AutocompleteOptionalProps & { label: string } & Omit<
    MUIAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
    'renderInput'
  > &
  Partial<
    Pick<
      MUIAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
      'renderInput'
    >
  >;

const GrowPaper = (paperProps: MUIPaperProps): JSX.Element => (
  <MUIGrow in>
    <MUIPaper {...paperProps} />
  </MUIGrow>
);

const GroupChildren = styled('ul')({
  padding: 0,
});

const GroupHeader = ListSubheader;

const Autocomplete = <
  T,
  Multiple extends boolean | undefined = undefined,
  DisableClearable extends boolean | undefined = undefined,
  FreeSolo extends boolean | undefined = undefined,
>(
  autocompleteProps: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>,
): JSX.Element => {
  const {
    componentsProps,
    extendRenderInput,
    getGroupLabel,
    label,
    messageBoxProps,
    renderGroup,
    renderInput,
    sx,
    ...autocompleteRestProps
  } = autocompleteProps;

  const combinedComponentsProps = useMemo<
    AutocompleteProps<
      T,
      Multiple,
      DisableClearable,
      FreeSolo
    >['componentsProps']
  >(
    () => ({
      paper: {
        sx: {
          backgroundColor: TEXT,

          [`& .${muiAutocompleteClasses.groupLabel}`]: {
            backgroundColor: TEXT,
          },
        },
      },

      ...componentsProps,
    }),
    [componentsProps],
  );

  const combinedRenderGroup = useMemo<
    AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>['renderGroup']
  >(() => {
    if (renderGroup) return renderGroup;

    return (
      getGroupLabel &&
      ((params) => (
        <li key={params.key}>
          <GroupHeader
            component="div"
            className={muiAutocompleteClasses.groupLabel}
          >
            {getGroupLabel(params.group)}
          </GroupHeader>
          <GroupChildren className={muiAutocompleteClasses.groupUl}>
            {params.children}
          </GroupChildren>
        </li>
      ))
    );
  }, [getGroupLabel, renderGroup]);

  const combinedRenderInput = useMemo<
    Exclude<
      AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>['renderInput'],
      undefined
    >
  >(
    () =>
      renderInput ??
      ((params) => {
        const { fullWidth, InputProps, InputLabelProps, inputProps } = params;
        const inputWithLabelProps: OutlinedInputWithLabelProps = {
          formControlProps: {
            fullWidth,
            ref: InputProps.ref,
          },
          inputLabelProps: InputLabelProps,
          inputProps: {
            className: InputProps.className,
            endAdornment: InputProps.endAdornment,
            inputProps,
            startAdornment: InputProps.startAdornment,
          },
          label,
        };

        extendRenderInput?.call(null, inputWithLabelProps, params);

        return <OutlinedInputWithLabel {...inputWithLabelProps} />;
      }),
    [extendRenderInput, label, renderInput],
  );

  const combinedSx = useMemo<
    AutocompleteProps<T, Multiple, DisableClearable, FreeSolo>['sx']
  >(
    () => ({
      [`& .${muiOutlinedInputClasses.root} .${muiAutocompleteClasses.endAdornment}`]:
        {
          right: `7px`,

          [`& .${muiSvgIconClasses.root}`]: {
            color: GREY,
          },
        },

      ...sx,
    }),
    [sx],
  );

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column' }}>
      <MUIAutocomplete
        PaperComponent={GrowPaper}
        {...autocompleteRestProps}
        componentsProps={combinedComponentsProps}
        renderGroup={combinedRenderGroup}
        renderInput={combinedRenderInput}
        sx={combinedSx}
      />
      <InputMessageBox {...messageBoxProps} />
    </Box>
  );
};

export type { AutocompleteProps };

export default Autocomplete;
