// Libs
// -----------------------------------------------------------------------------
import React, {
  FC,
  useRef,
  useState,
  ChangeEvent,
  useEffect,
  useCallback,
} from 'react';
import { Dropdown, Input, InputProps, InputRef } from 'antd';
import _defer from 'lodash/defer';
import classNames from 'classnames';

// App
// -----------------------------------------------------------------------------
import styles from './AutoCompleteMulti.module.scss';
import { useClickOutsideElement } from 'hooks';

// Props
// -----------------------------------------------------------------------------
type AutocompleteInputProps = InputProps & {
  value?: string;
  onChange?: (value: string) => void;
  options: Array<{ label: string; value: string }>;
  disabled?: boolean;
};

/**
 * Компонент поля ввода с автодополнением в _любом месте строки_.
 */
export const AutoCompleteMulti: FC<AutocompleteInputProps> = ({
  value,
  onChange,
  options = [],
  disabled = false,
  ...inputProps
}) => {
  const [currentWord, setCurrentWord] = useState<string>('');
  const [showDropdown, setShowDropdown] = useState<boolean>(false);
  const [visibleOptions, setVisibleOptions] = useState<
    AutocompleteInputProps['options']
  >([]);
  const [currentItem, setCurrentItem] = useState<number>(0);

  const inputRef = useRef<InputRef>(null);
  const menuRef = useRef<HTMLDivElement>(null);

  useClickOutsideElement(menuRef, () => {
    setShowDropdown(false);
  });

  const onOptionClick = useCallback(
    (selectedWord: string): void => {
      const input = inputRef.current?.input;

      if (!input) {
        return;
      }

      const selectionEnd = input.selectionEnd ?? input.value.length - 1;
      const wordPartToAdd = selectedWord.slice(currentWord.length);
      const newValueWithAddition =
        input.value.slice(0, selectionEnd) +
        wordPartToAdd +
        input.value.slice(selectionEnd);
      const newCursorPosition = (
        input.value.slice(0, selectionEnd) + wordPartToAdd
      ).length;

      setShowDropdown(false);
      setCurrentItem(0);
      if (onChange) {
        onChange(newValueWithAddition);
      }

      input.focus();
      // NOTE: _defer (он же setTimeout c 0 задержкой) нужен для корректной
      // очередности событий
      _defer(() => {
        input.setSelectionRange(newCursorPosition, newCursorPosition);
      });
    },
    [currentWord, onChange],
  );

  const onKeyDown = useCallback(
    (ev: KeyboardEvent): void => {
      if (showDropdown) {
        if (ev.key === 'ArrowDown') {
          ev.preventDefault();
          inputRef.current?.input?.blur();
          if (currentItem !== visibleOptions.length - 1) {
            setCurrentItem((prev) => prev + 1);
          }
        } else if (ev.key === 'ArrowUp') {
          ev.preventDefault();
          inputRef.current?.input?.blur();
          if (currentItem !== 0) {
            setCurrentItem((prev) => prev - 1);
          }
        } else if (ev.key === 'Enter') {
          ev.preventDefault();
          onOptionClick(visibleOptions[currentItem].value);
        }
      }
    },
    [showDropdown, visibleOptions, currentItem, onOptionClick],
  );

  useEffect(() => {
    window.addEventListener('keydown', onKeyDown);

    return () => {
      window.removeEventListener('keydown', onKeyDown);
    };
  }, [onKeyDown]);

  const onInputChange = (ev: ChangeEvent<HTMLInputElement>): void => {
    if (onChange) {
      onChange(ev.target.value);
    }

    const input = inputRef.current?.input;
    if (!input) {
      return;
    }

    const selectionEnd = input.selectionEnd ?? undefined;
    const textBeforeSelectionEnd = input.value.slice(0, selectionEnd);
    const textWords = textBeforeSelectionEnd.split(' ');
    const wordBeforeCursor = textWords[textWords.length - 1] ?? '';
    const newVisibleOptions = wordBeforeCursor
      ? options.filter((option) => option.value.startsWith(wordBeforeCursor))
      : [];

    setCurrentWord(wordBeforeCursor);
    setVisibleOptions(newVisibleOptions);
    setShowDropdown(newVisibleOptions.length > 0);
  };

  return (
    <Dropdown
      overlay={
        <div className={styles['overlay']} ref={menuRef}>
          {visibleOptions.map((option, index) => (
            <div
              key={option.value}
              className={classNames(
                styles['overlay-item'],
                index === currentItem && styles['overlay-item--current'],
              )}
              onClick={() => {
                onOptionClick(option.value);
              }}
            >
              {option.label}
            </div>
          ))}
        </div>
      }
      visible={showDropdown}
      disabled={disabled}
    >
      <Input
        value={value}
        ref={inputRef}
        onChange={onInputChange}
        {...inputProps}
      />
    </Dropdown>
  );
};
