import { memo, useCallback, useEffect, useState } from 'react';
import clsx from 'clsx';

import type { AutocompleteRenderGetTagProps } from '@mui/material/Autocomplete';
import type { FilterOptionsState } from '@mui/material/useAutocomplete';
import Tooltip from '@mui/material/Tooltip';

import { ASSETS_URL } from '../../../consts/common';
import { Chips } from '../../chips';
import type { ChipsProps } from '../../chips';
import { CustomIcon } from '../../custom-icon';
import { truncateWithEllipsis } from '../../../utils';

import { AutocompleteWithListbox } from './components/autocomplete-with-listbox';
import type { AutocompleteProps, AutocompleteOptionProps } from './types';
import createClasses from './styles';

/**
 * The autocomplete is a normal text input enhanced by a panel of suggested options.
 * It's meant to be an improved version of the "react-select" and "downshift" packages.
 * @link https://mui.com/material-ui/react-autocomplete/
 */
const Autocomplete = (props: AutocompleteProps) => {
  const {
    defaultLimitTags = 2,
    showLimitTagsTooltip,
    size = 'medium',
    handleClear,
    handleCreate,
    handleDelete,
    listItemsMapFN,
    menuContentProps,
    ...otherProps
  } = props;
  const classes = createClasses({ multiple: otherProps.multiple, size });

  const defaultValue =
    (typeof otherProps.defaultValue === 'string' && otherProps.defaultValue) || '';
  const hasStringInputValue = !otherProps.multiple && typeof otherProps.value === 'string';
  const [inputText, setInputText] = useState(defaultValue);

  useEffect(() => {
    if (hasStringInputValue) setInputText(otherProps.value as string);
  }, [hasStringInputValue, setInputText, otherProps.value]);

  const getLimitTagsTooltip: AutocompleteProps['getLimitTagsText'] = more => {
    const tooltipTitle = (otherProps.value as AutocompleteOptionProps[])
      ?.slice(defaultLimitTags)
      .map(option => option?.name)
      .join(', ');
    return (
      <Tooltip title={truncateWithEllipsis(tooltipTitle, 240)} placement="top">
        <span>{`+${more}`}</span>
      </Tooltip>
    );
  };

  const onBlur = useCallback<NonNullable<AutocompleteProps['onBlur']>>(
    e => {
      if (inputText && otherProps.freeSolo) {
        handleCreate?.(e, [inputText] as unknown as AutocompleteOptionProps[]);
        setInputText('');
      }
    },
    [handleCreate, inputText, otherProps.freeSolo]
  );

  const onChange = useCallback<NonNullable<AutocompleteProps['onChange']>>(
    (e, v, reason) => {
      switch (reason) {
        case 'clear':
          if (hasStringInputValue) {
            setInputText('');
          }
          handleClear?.(e);
          break;

        case 'createOption':
          handleCreate?.(e, v as AutocompleteOptionProps[]);
          break;

        case 'removeOption':
          handleDelete?.(e, v as AutocompleteOptionProps[]);
          break;
      }
    },
    [handleClear, handleCreate, handleDelete, hasStringInputValue]
  );

  const getOptSearchWords = (option: AutocompleteOptionProps) => {
    const { name, searchWords: _searchWords = [], additionalProps } = option;
    const searchWords = [..._searchWords, name];
    if (additionalProps)
      searchWords.push(
        ...Object.entries(additionalProps)
          .filter(ent => typeof ent[1] === 'string')
          .map(ent => ent[1] as string)
      );
    return searchWords;
  };

  const filterOptions = (
    options: AutocompleteOptionProps[],
    params: FilterOptionsState<AutocompleteOptionProps>
  ) => {
    const inputValue = (hasStringInputValue ? inputText : params.inputValue).toLowerCase();

    const opts = options.filter(option => {
      const optSearchWords = getOptSearchWords(option);
      const found = optSearchWords.some(word =>
        word?.toString().toLowerCase().includes(inputValue)
      );
      return found;
    });
    return opts;
  };

  const renderTags = (
    tagValue: AutocompleteOptionProps[],
    getTagProps: AutocompleteRenderGetTagProps
  ) => {
    return tagValue.map((option: AutocompleteOptionProps, index) => {
      const tagProps: ChipsProps = getTagProps({ index });
      return (
        <Chips
          {...tagProps}
          className={clsx(
            tagProps.className,
            classes.chipRoot,
            (option.error || option?.status === 'error') && classes.chipStatusError
          )}
          disabled={option.disabled}
          label={option.name}
        />
      );
    });
  };

  const clearIcon = (
    <CustomIcon
      data-testid="clearButton"
      color="inherit"
      src={`${ASSETS_URL}/icons2/icon_closeCircle.svg`}
      className={classes.clearButton}
    />
  );

  return (
    <AutocompleteWithListbox
      clearIcon={clearIcon}
      defaultLimitTags={defaultLimitTags}
      filterOptions={filterOptions}
      getLimitTagsText={showLimitTagsTooltip ? getLimitTagsTooltip : undefined}
      getOptionLabel={option => (typeof option !== 'string' && option.name?.toString()) || ''}
      inputText={inputText}
      isOptionEqualToValue={(o, v) => o.name === v.name}
      onBlur={onBlur}
      onChange={onChange}
      renderOption={(_, option) => option}
      renderTags={renderTags}
      size={size}
      {...otherProps}
      listItemsMapFN={listItemsMapFN}
      menuContentProps={menuContentProps}
      setInputText={setInputText}
    />
  );
};

const m = memo(Autocomplete);
export { m as Autocomplete };
