import React, { useCallback } from 'react';

import Autocomplete from '@mui/material/Autocomplete';
import { StyledEngineProvider } from '@mui/material/styles';

import { Chip, Tooltip, Typography, TYPOGRAPHY_TYPE } from 'components';
import { bem } from 'utils/bem';

import { DefaultOption, Input } from './components';
import { SearchFieldProps, SearchFieldOption, SearchFieldFilterState } from './types';
import { THEME } from '../../constants';

import styles from './search-field.scss';

export const searchFieldClassName = bem(styles, 'search-field');

export const SearchField = <T extends SearchFieldOption>({
  value,
  options,
  inputValue,
  error,
  onChange,
  onInputChange,
  placeholder,
  icon,
  id,
  className,
  inputClassName,
  noOptionsClassName,
  listboxClassName,
  endAdornment,
  hint,
  maxItems,
  filterOptions,
  popperProps = {},
  paperProps = {},
  optionComponent,
  theme = THEME.light,
  disabled = false,
  multiple = false,
}: SearchFieldProps<T>): JSX.Element => {
  const maxItemsReached = multiple && !!maxItems && Array.isArray(value) && value.length >= maxItems;

  const OptionComponent = optionComponent ?? DefaultOption;

  const handleChange = useCallback(
    (event: React.SyntheticEvent, newValue: T | T[] | null) => {
      onChange(newValue);
    },
    [onChange],
  );

  const handleInputChange = useCallback(
    (event: React.SyntheticEvent, newValue: string) => {
      onInputChange?.(newValue);
    },
    [onInputChange],
  );

  const onDeleteItem = useCallback(
    (valueToDelete: T) => () => {
      if (!value || !Array.isArray(value)) return;

      const newValue = value.filter((val) => val.id !== valueToDelete.id);

      onChange(newValue);
    },
    [value, onChange],
  );

  const getIsOptionSelected = useCallback(
    (option: T) => {
      if (Array.isArray(value)) return value.some(({ id: optionId }) => optionId === option.id);

      return option.id === value?.id;
    },
    [value],
  );

  const defaultFilterOptions = useCallback(
    (filteredOptions: T[], state: SearchFieldFilterState<T>) =>
      filteredOptions.filter(
        (val) =>
          val.title.toLowerCase().includes(state.inputValue.toLowerCase()) ||
          val.description?.toLowerCase().includes(state.inputValue.toLowerCase()),
      ),
    [],
  );

  const getOptionLabel = useCallback((val: T): string => val.title, []);

  const getOptionDisabled = useCallback((val: T) => Boolean(val.isDisabled), []);

  return (
    <StyledEngineProvider injectFirst>
      <div className={searchFieldClassName(null, { [theme]: true }, className)}>
        <Autocomplete
          id={id}
          options={options}
          disablePortal={true}
          value={value}
          componentsProps={{
            popper: popperProps,
            paper: paperProps,
          }}
          ListboxProps={{ className: searchFieldClassName('listbox', undefined, listboxClassName) }}
          filterOptions={filterOptions ?? defaultFilterOptions}
          inputValue={inputValue}
          onChange={handleChange}
          onInputChange={handleInputChange}
          getOptionLabel={getOptionLabel}
          getOptionDisabled={getOptionDisabled}
          popupIcon={null}
          clearIcon={null}
          clearOnBlur={false}
          multiple={multiple}
          disabled={disabled || maxItemsReached}
          noOptionsText={
            <div className={searchFieldClassName('no-results', undefined, noOptionsClassName)}>
              <Typography
                type={TYPOGRAPHY_TYPE.body4}
                className={searchFieldClassName('no-results-text')}
              >
                No results found.
                <br />
                Please try editing your query.
              </Typography>
            </div>
          }
          renderInput={(params): React.ReactNode => (
            <Input
              placeholder={placeholder}
              icon={icon}
              customClassName={inputClassName}
              endAdornment={endAdornment}
              hasError={Boolean(error)}
              {...params}
            />
          )}
          renderTags={(selectedValues: T[]): React.ReactNode =>
            selectedValues.map((selectedValue) => (
              <Tooltip
                key={selectedValue.id}
                tooltip={selectedValue.description}
                isInteractive={false}
                isDisabled={!selectedValue.description}
              >
                <Chip
                  disabled={disabled}
                  onDelete={onDeleteItem(selectedValue)}
                  label={selectedValue.title}
                  theme={theme}
                />
              </Tooltip>
            ))
          }
          renderOption={(props, option: T): React.ReactNode => (
            <li
              {...props}
              key={option.id}
              className={searchFieldClassName('option')}
            >
              <OptionComponent
                option={option}
                isDisabled={Boolean(option.isDisabled)}
                isSelected={getIsOptionSelected(option)}
                theme={theme}
              />
            </li>
          )}
        />
        {error && (
          <Typography
            className={searchFieldClassName('error')}
            type={TYPOGRAPHY_TYPE.body4}
          >
            {error}
          </Typography>
        )}
        {hint && (
          <Typography
            className={searchFieldClassName('hint')}
            type={TYPOGRAPHY_TYPE.body4}
          >
            {hint}
          </Typography>
        )}
      </div>
    </StyledEngineProvider>
  );
};
