import PropTypes from 'prop-types';
import { useCallback, useRef, useState, useEffect, forwardRef } from 'react';

import classNames from 'classnames';

import ImmutablePropTypes from 'react-immutable-proptypes';

import Textarea from 'react-textarea-autosize';

import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';

import { AutosuggestEmoji } from './autosuggest_emoji';
import { AutosuggestHashtag } from './autosuggest_hashtag';
import { LocalCustomEmojiProvider } from './emoji/context';
import { textAtCursorMatchesToken } from './autosuggest/utils';
import { Popover } from './popover';

const AutosuggestTextarea = forwardRef(({
  value,
  suggestions,
  disabled,
  placeholder,
  onSuggestionSelected,
  onSuggestionsClearRequested,
  onSuggestionsFetchRequested,
  onChange,
  onKeyUp,
  onKeyDown,
  onPaste,
  onDrop,
  onFocus,
  onBlur,
  autoFocus = true,
  lang,
  className,
  ...props
}, textareaRef) => {

  const [suggestionsHidden, setSuggestionsHidden] = useState(true);
  const [selectedSuggestion, setSelectedSuggestion] = useState(0);
  const [textareaElement, setTextareaElement] = useState(null);
  const lastTokenRef = useRef(null);
  const tokenStartRef = useRef(0);

  const handleChange = useCallback((e) => {
    const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, ['@', '＠', ':', '#', '＃']);

    if (token !== null && lastTokenRef.current !== token) {
      tokenStartRef.current = tokenStart;
      lastTokenRef.current = token;
      setSelectedSuggestion(0);
      onSuggestionsFetchRequested(token);
    } else if (token === null) {
      lastTokenRef.current = null;
      onSuggestionsClearRequested();
    }

    onChange(e);
  }, [onSuggestionsFetchRequested, onSuggestionsClearRequested, onChange, setSelectedSuggestion]);

  const handleKeyDown = useCallback((e) => {
    if (disabled) {
      e.preventDefault();
      return;
    }

    if (e.which === 229 || e.isComposing) {
      // Ignore key events during text composition
      // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
      return;
    }

    switch(e.key) {
    case 'Escape':
      if (suggestions.size === 0 || suggestionsHidden) {
        document.querySelector('.ui').parentElement.focus();
      } else {
        e.preventDefault();
        setSuggestionsHidden(true);
      }

      break;
    case 'ArrowDown':
      if (suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        setSelectedSuggestion(Math.min(selectedSuggestion + 1, suggestions.size - 1));
      }

      break;
    case 'ArrowUp':
      if (suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        setSelectedSuggestion(Math.max(selectedSuggestion - 1, 0));
      }

      break;
    case 'Enter':
    case 'Tab':
      // Select suggestion
      if (lastTokenRef.current !== null && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        e.stopPropagation();
        onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestions.get(selectedSuggestion));
      }

      break;
    }

    if (e.defaultPrevented || !onKeyDown) {
      return;
    }

    onKeyDown(e);
  }, [disabled, suggestions, suggestionsHidden, selectedSuggestion, setSelectedSuggestion, setSuggestionsHidden, onSuggestionSelected, onKeyDown]);

  const closeMenu = useCallback((e) => {
    setSuggestionsHidden(true);
    onBlur?.(e);
  }, [setSuggestionsHidden]);

  const handleFocus = useCallback((e) => {
    onFocus?.(e);
  }, [onFocus]);

  const handleSuggestionClick = useCallback((e) => {
    const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index'));
    e.preventDefault();
    onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestion);
    textareaRef.current?.focus();
  }, [suggestions, onSuggestionSelected, textareaRef]);

  const handlePaste = useCallback((e) => {
    onPaste(e);
  }, [onPaste]);

  const handleDrop = useCallback((e) => {
    if (onDrop) {
      onDrop(e);
    }
  }, [onDrop]);

  // Show the suggestions again whenever they change and the textarea is focused
  useEffect(() => {
    if (suggestions.size > 0 && textareaRef.current === document.activeElement) {
      setSuggestionsHidden(false);
    }
  }, [suggestions, textareaRef, setSuggestionsHidden]);

  // Hack to force Firefox to change language in autocorrect
  useEffect(() => {
    if (lang && textareaRef.current && textareaRef.current === document.activeElement) {
      textareaRef.current.blur();
      textareaRef.current.focus();
    }
  }, [lang]);

  const renderSuggestion = useCallback((suggestion, i) => {
    let inner, key;

    if (suggestion.type === 'emoji') {
      inner = <AutosuggestEmoji emoji={suggestion} />;
      key   = suggestion.id;
    } else if (suggestion.type === 'hashtag') {
      inner = <AutosuggestHashtag tag={suggestion} />;
      key   = suggestion.name;
    } else if (suggestion.type === 'account') {
      inner = <AutosuggestAccountContainer id={suggestion.id} />;
      key   = suggestion.id;
    }

    return (
      <div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={handleSuggestionClick}>
        {inner}
      </div>
    );
  }, [selectedSuggestion, handleSuggestionClick]);

  const handleRef = useCallback((element) => {
    textareaRef.current = element;
    setTextareaElement(element);
  }, []);

  return (
    <div className={classNames('autosuggest-textarea', className)}>
      <Textarea
        {...props}
        ref={handleRef}
        className='autosuggest-textarea__textarea'
        disabled={disabled}
        placeholder={placeholder}
        autoFocus={autoFocus}
        value={value}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        onKeyUp={onKeyUp}
        onFocus={handleFocus}
        onBlur={closeMenu}
        onPaste={handlePaste}
        onDrop={handleDrop}
        dir='auto'
        aria-autocomplete='list'
        aria-label={placeholder}
        lang={lang}
      />

      <LocalCustomEmojiProvider>
        <Popover
          matchReferenceWidth
          isOpen={!(suggestionsHidden || suggestions.isEmpty())}
          onClose={closeMenu}
          reference={textareaElement}
        >
          {({ props }) => (
            <div {...props}>
              <div className='autosuggest-textarea__suggestions' style={{ width: textareaElement?.clientWidth }}>
                {suggestions.map(renderSuggestion)}
              </div>
            </div>
          )}
        </Popover>
      </LocalCustomEmojiProvider>
    </div>
  );
});

AutosuggestTextarea.propTypes = {
  value: PropTypes.string,
  suggestions: ImmutablePropTypes.list,
  disabled: PropTypes.bool,
  placeholder: PropTypes.string,
  onSuggestionSelected: PropTypes.func.isRequired,
  onSuggestionsClearRequested: PropTypes.func.isRequired,
  onSuggestionsFetchRequested: PropTypes.func.isRequired,
  onChange: PropTypes.func.isRequired,
  onKeyUp: PropTypes.func,
  onKeyDown: PropTypes.func,
  onPaste: PropTypes.func.isRequired,
  onDrop: PropTypes.func,
  onFocus:PropTypes.func,
  autoFocus: PropTypes.bool,
  lang: PropTypes.string,
};

export default AutosuggestTextarea;
