// @flow
import React, { useState, useCallback, useEffect } from 'react';
import styled from 'styled-components';

import type { SearchInputProps } from './search-input.proptypes';
import { searchInputPropTypes } from './search-input.proptypes';

import { getPaletteColor } from '../../../mixins/branding';
import { Box } from '../../../primitives';

import { SearchResultsWrapper } from './components/search-results-wrapper';
import { ClickableSearchResult } from './components/clickable-search-result';
import { SearchResultSeparator } from './components/search-result-separator';
import { SearchInputField } from '../search-input-field';

const NotFoundItem = styled(ClickableSearchResult)`
  color: ${getPaletteColor('greyscale-dark')};
`;

export const SearchInput = ({
  value = '',
  placeholder,
  searchResults,
  onChange,
  handleClear = () => {},
  onSearchResultClicked = (_id, _value) => {},
  large = true,
  bold = false,
  allowNotFound = false,
  notFoundText,
  hideLabel,
  label,
  renderItem = ({ text }) => text,
}: SearchInputProps) => {
  const [searchInput, setSearchInput] = useState(value);
  const [showSearchResults, setShowSearchResults] = useState(false);
  const [selectedSearchResultIndex, setSelectedSearchResultIndex] = useState(
    -1,
  );
  const [timeoutId, setTimeoutId] = useState();

  const handleChange = useCallback(
    newSearchInput => {
      setSelectedSearchResultIndex(-1);
      setSearchInput(newSearchInput);
      onChange(newSearchInput);
      setShowSearchResults(!!newSearchInput);
    },
    [onChange],
  );

  const onClear = useCallback(() => {
    setSelectedSearchResultIndex(-1);
    setSearchInput('');
    onChange('');
    setShowSearchResults(false);
    handleClear();
  }, [handleClear, onChange]);

  const handleSearchResultClicked = useCallback(
    (id, newValue) => {
      setSelectedSearchResultIndex(-1);
      setShowSearchResults(false);
      setSearchInput(newValue ?? '');
      onSearchResultClicked(id, newValue);
    },
    [onSearchResultClicked],
  );

  const handleSearchInputFocused = useCallback(() => {
    setShowSearchResults(!!searchInput.length);
  }, [searchInput]);

  const handleSearchInputBlurred = useCallback(() => {
    // Wait before closing the drop-down so clicking on an item has time
    // to register before the list is dismissed.
    setTimeoutId(window.setTimeout(() => setShowSearchResults(false), 200));
  }, []);
  // Clear any outstanding timeout when the component is unmounted.
  useEffect(() => () => timeoutId && window.clearTimeout(timeoutId));

  const handleKeyDown = useCallback(
    evt => {
      const enterKey = evt.keyCode === 13;

      if (enterKey && selectedSearchResultIndex >= 0) {
        const clickedResult = searchResults[selectedSearchResultIndex];
        if (clickedResult) {
          handleSearchResultClicked(clickedResult.id, clickedResult.text);
        } else {
          handleSearchResultClicked();
        }

        evt.stopPropagation();
        evt.preventDefault();
      }
    },
    [handleSearchResultClicked, searchResults, selectedSearchResultIndex],
  );

  const handleKeyUp = useCallback(
    evt => {
      let highlightedIndex = selectedSearchResultIndex;
      const upKey = evt.keyCode === 38;
      const downKey = evt.keyCode === 40;
      const escapeKey = evt.keyCode === 27;

      // Search results should be 1 longer for if the last option is the "Not Found" option
      const searchResultsLength =
        searchResults.length - (allowNotFound ? 0 : 1);

      if (escapeKey) {
        highlightedIndex = -1;
        setShowSearchResults(false);
      } else if (upKey && selectedSearchResultIndex >= 0) {
        let selectedSearchResult;
        do {
          highlightedIndex--;
          selectedSearchResult = searchResults[highlightedIndex];
        } while (selectedSearchResult && selectedSearchResult.isSeparator);
      } else if (downKey && selectedSearchResultIndex < searchResultsLength) {
        let selectedSearchResult;
        do {
          highlightedIndex++;
          selectedSearchResult = searchResults[highlightedIndex];
        } while (selectedSearchResult && selectedSearchResult.isSeparator);
      }

      setSelectedSearchResultIndex(highlightedIndex);
    },
    [allowNotFound, searchResults, selectedSearchResultIndex],
  );

  const handleClick = (id, result) => {
    handleSearchResultClicked(id, result);
  };

  /**
   * In the handlers, `searchResults` is outdated so we need to check it in the render.
   * Make an exception to show search results regardless if there are any for `allowNotFound`
   */
  const shouldShowSearchResults =
    showSearchResults && (searchResults.length || allowNotFound);

  return (
    <Box
      stacked="column"
      onKeyUp={handleKeyUp}
      onKeyDown={handleKeyDown}
      onFocus={handleSearchInputFocused}
      onBlur={handleSearchInputBlurred}
    >
      <div style={{ position: 'relative' }}>
        <SearchInputField
          label={label}
          onChange={handleChange}
          value={searchInput}
          bold={bold}
          placeholder={placeholder}
          large={large}
          handleClear={onClear}
          hideLabel={hideLabel}
        />
        <SearchResultsWrapper
          large={large}
          style={{ display: shouldShowSearchResults ? 'block' : 'none' }}
        >
          {searchResults.map((result, i) =>
            result.isSeparator ? (
              <SearchResultSeparator key={result.id} large={large}>
                {result.text}
              </SearchResultSeparator>
            ) : (
              <ClickableSearchResult
                key={result.id}
                onClick={() => handleClick(result.id, result.text)}
                selected={selectedSearchResultIndex === i}
                large={large}
                bold={bold}
              >
                {renderItem(result)}
              </ClickableSearchResult>
            ),
          )}
          {allowNotFound && (
            <NotFoundItem
              onClick={() => handleClick()}
              selected={selectedSearchResultIndex === searchResults.length}
              large={large}
              bold={false}
            >
              {notFoundText}
            </NotFoundItem>
          )}
        </SearchResultsWrapper>
      </div>
    </Box>
  );
};

SearchInput.displayName = 'SearchInput';
SearchInput.propTypes = searchInputPropTypes;
