import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react';
import classNames from 'classnames';
import { PopperDropdownWrapper, DROPDOWN_PLACEMENT_TYPES } from '../dropdown';
import TypeaheadOptionList, { getOptionId } from './typeahead-option-list';
import { ItemComponents } from './item-component';
import { MESSAGE_DISPLAY_LOCATION, ValidityMessages } from '../form-field';
import { MESSAGE_TYPES } from '../message';
import { Input } from '../input';
import { useElementsSize } from '../hooks/use-elements-size';
import { useVisibleOptions } from './use-visible-options';
import { useTranslation } from '../hooks/use-translation';
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const defaultItem = /*#__PURE__*/_jsx(ItemComponents.DefaultItem, {
  nameKey: "title"
});
let typeaheadId = 0;

/** Searchable select
 * [CAPITALIZATION] Use sentence case for options, label, and input text. Do not end with ellipses (...).
 * @midnight-gray-supported
 * @alpine-light-supported
 */
function Typeahead({
  defaultValue = '',
  onSearch = () => {},
  optionComponent = defaultItem,
  OptionList = TypeaheadOptionList,
  onBlur = () => {},
  onKeyDown = () => {},
  onClear = () => {},
  onSelect = () => {},
  onDelete = () => {},
  searchFields = [],
  placement = DROPDOWN_PLACEMENT_TYPES.BOTTOM_START,
  options = [],
  items = [],
  optionId,
  optionLabel = 'label',
  isMultiSelect = false,
  focused = false,
  isTouched = false,
  forceShowAllOnFocus = false,
  showAllOnFocus = false,
  isDisabled = false,
  disabledInfo,
  isRequired = false,
  isOptional = false,
  isClearable = false,
  optionalLabelText,
  handleFocus = () => {},
  placeholder = '',
  containerRef,
  inputRef: inputRefProp,
  helperTextPosition,
  testId = 'typeahead-input',
  id = '',
  label = '',
  labelTestId,
  ariaLabel,
  validity,
  validationTestId,
  asyncValidation,
  labelIcon,
  labelIconTooltipText = '',
  labelIconTestId = '',
  dropdownContentClass,
  wrapperClass,
  fieldClass,
  helperText,
  autoComplete = 'on',
  keepInvalidText = false,
  formatSearchValue,
  className,
  autoFocus = false,
  noItemsText,
  shouldCallOnSelectWithOldValue = false
}) {
  const {
    t
  } = useTranslation();
  const listId = useMemo(() => `typeahead-scrollable-container-${typeaheadId++}`, []);
  const conditionallyUsedInputRef = useRef(null);
  const inputRef = inputRefProp || conditionallyUsedInputRef;
  const [searchValue, onSearchValueChange] = useState(defaultValue);
  const [isOpen, toggleDropdown] = useState(false);
  const [isShowAll, toggleShowAll] = useState(false);
  const [openedWithKeyboard, setOpenedWithKeyboard] = useState(false);
  const [isFocus, setFocus] = useState(focused);
  const [touched, setIsTouched] = useState(isTouched);
  const [active, setActive] = useState(null);
  const validityType = validity?.type;
  const isValidationFailed = validityType === MESSAGE_TYPES.ERROR;
  const isValidationSucceeded = validityType === MESSAGE_TYPES.SUCCESS;
  const isValidationWarned = validityType === MESSAGE_TYPES.WARNING;
  const validityDisplayLocation = validity?.displayLocation ?? MESSAGE_DISPLAY_LOCATION.INLINE;
  const showValidationResult = Boolean(validity?.value);

  // Multiple select typeahead requires to take width of container element
  const anchorRef = containerRef || inputRef;
  const [width] = useElementsSize(anchorRef, true);
  const openDropdown = () => toggleDropdown(true);
  const closeDropdown = () => {
    setOpenedWithKeyboard(false);
    toggleDropdown(false);
  };
  const handleSelect = (option, isLastAvailableOption = false) => {
    if (isMultiSelect) {
      // If option SHOULD close dropdown OR selected option is the last
      // option available, clear search value.
      if (!option?.shouldNotCloseDropdown || isLastAvailableOption) {
        onSearchValueChange('');
      }
    } else {
      setFocus(false);
      if (option) {
        onSearchValueChange(String(option[optionLabel]));
      }
    }
    if (isMultiSelect) {
      onSelect(option);
    } else if (String(option?.[optionLabel]) !== defaultValue || shouldCallOnSelectWithOldValue) {
      onSelect(option);
    }
    setIsTouched(true);
    if (!option?.shouldNotCloseDropdown) {
      toggleShowAll(false);
      closeDropdown();
      setActive(null);
    }
    if (!isMultiSelect) {
      handleFocus(false);
    }
  };
  const handleChange = event => {
    onSearchValueChange(event.target.value);
    onSearch(event.target.value);

    // Toggle off all results when the user types
    toggleShowAll(false);
    openDropdown();
    setIsTouched(true);
  };
  const handleKeyDown = event => {
    const {
      key,
      target
    } = event;

    // eslint-disable-next-line default-case
    switch (key) {
      case 'ArrowDown':
        if (!searchValue) {
          toggleShowAll(true);
          setOpenedWithKeyboard(true);
          openDropdown();
        }
        break;
      case 'Backspace':
        {
          if (isMultiSelect && !target.value && items.length) {
            const item = [...items].pop();
            if (item) {
              onDelete(item, items.length - 1);
            }
          }
          break;
        }
      case 'Tab':
        // Release the focus if tabbing away
        handleExit();
        break;
    }
    onKeyDown(event, searchValue);
  };
  function handleExit() {
    const isExact = !!searchValue && options.find(option => searchFields.some(key => option[key] === searchValue));
    if (!keepInvalidText) {
      if (isExact) {
        handleSelect(isExact);
      } else {
        onSearchValueChange('');
        onSearch('');
        onClear();
        if (!isMultiSelect) {
          handleSelect();
        }
      }
    }

    // Remove focus from typeahead container & input
    handleFocus(false);
    setFocus(false);
    closeDropdown();
  }
  const handleClear = () => {
    onSearchValueChange('');
    handleSelect();
    onClear();
  };
  const showClearButton = isClearable && searchValue !== '' && !isDisabled;
  const handleClickElsewhere = useCallback(event => {
    // Handle focus to false if the click is outside of the typeahead container
    if (!anchorRef.current?.contains(event?.target)) {
      if (isFocus || isMultiSelect) {
        inputRef.current?.blur();
        handleExit();
      }
    }
  },
  // searchValue in dependency array needed for proper clear logic
  [isFocus, anchorRef, inputRef, searchValue]);
  useEffect(() => {
    // Click handler on elsewhere to not trigger toggleFocus after dropdown selection.
    window.addEventListener('click', handleClickElsewhere);
    return () => {
      window.removeEventListener('click', handleClickElsewhere);
    };
  });
  useEffect(() => {
    if (!isDisabled) {
      // Focus within input field if they click a surrounding element. This is implemented in the multi select typeahead
      if (focused) {
        inputRef.current?.focus();
        if (!!searchValue) {
          openDropdown();
        }
      }
      if ((showAllOnFocus && searchValue === '' || forceShowAllOnFocus) && isFocus) {
        toggleShowAll(true);
        openDropdown();
      }
    }
  }, [isDisabled, showAllOnFocus, forceShowAllOnFocus, isFocus, searchValue]);
  useEffect(() => {
    if (!focused) {
      inputRef.current?.blur();
      closeDropdown();
    }
    setFocus(focused);
  }, [focused]);
  useEffect(() => {
    onSearchValueChange(defaultValue || '');
  }, [defaultValue]);
  useEffect(() => setIsTouched(isTouched), [isTouched]);
  const PopperWrapper = useMemo(() => /*#__PURE__*/_jsx(PopperDropdownWrapper, {
    closeDropdown: e => {
      handleClickElsewhere(e);
    },
    anchorRef: anchorRef.current,
    open: isOpen,
    contentClassName: classNames('typeahead-dropdown-content', dropdownContentClass),
    placement: placement,
    wrapperContentTestId: "typeahead-dropdown-content",
    children: /*#__PURE__*/_jsx(OptionList, {
      optionComponent: optionComponent,
      searchFields: searchFields,
      closeDropdown: closeDropdown,
      searchValue: formatSearchValue && formatSearchValue(searchValue) || searchValue,
      handleSelect: handleSelect,
      handleMakeActive: activeId => setActive(activeId),
      inputRef: inputRef,
      optionId: optionId,
      optionLabel: optionLabel,
      options: options,
      width: width,
      showAll: isShowAll,
      openedWithKeyboard: openedWithKeyboard,
      items: items,
      listId: listId,
      noItemsText: noItemsText
    })
  }), [isOpen, width, searchValue, options, isShowAll, handleClickElsewhere]);
  const {
    isShowOptions
  } = useVisibleOptions({
    formatSearchValue,
    searchValue,
    options,
    searchFields,
    isShowAll
  });
  return /*#__PURE__*/_jsxs(_Fragment, {
    children: [/*#__PURE__*/_jsx(Input, {
      required: isRequired,
      label: label,
      isDisabled: isDisabled,
      disabledInfo: disabledInfo,
      isOptional: isOptional,
      optionalLabelText: optionalLabelText ?? t('Optional'),
      labelTestId: labelTestId,
      labelIcon: labelIcon,
      labelIconTooltipText: labelIconTooltipText,
      labelIconTestId: labelIconTestId,
      helperText: helperText,
      helperTextPosition: helperTextPosition,
      placeholder: placeholder,
      "test-id": testId,
      ref: inputRef,
      value: searchValue,
      onBlur: () => {
        // clear state if input was cleared through ref
        if (inputRef?.current?.value === '' && searchValue !== '') {
          handleExit();
        }
        onBlur(searchValue);
      },
      onChange: handleChange,
      asyncValidation: asyncValidation,
      autoFocus: autoFocus,
      onFocus: () => {
        setFocus(true);
        handleFocus(true);
        setIsTouched(true);
      },
      onClear: showClearButton ? handleClear : undefined,
      onKeyDown: handleKeyDown,
      id: id,
      autoComplete: autoComplete,
      className: classNames(className, 'typeahead-input-group'),
      wrapperClass: wrapperClass,
      fieldClass: classNames(fieldClass, {
        'is-failed': touched && showValidationResult && isValidationFailed,
        'is-succeeded': touched && showValidationResult && isValidationSucceeded,
        'is-warned': touched && showValidationResult && isValidationWarned,
        'is-empty': searchValue === '' && touched && isRequired
      }),
      "aria-haspopup": "listbox",
      "aria-label": ariaLabel ?? undefined,
      ...(isOpen && active && {
        'aria-activedescendant': getOptionId(active)
      }),
      ...(isShowOptions && {
        role: 'combobox',
        'aria-owns': listId,
        'aria-controls': listId,
        'aria-expanded': isOpen,
        'aria-autocomplete': 'list'
      })
    }), touched && validity && showValidationResult && validityDisplayLocation === MESSAGE_DISPLAY_LOCATION.INLINE && /*#__PURE__*/_jsx(ValidityMessages, {
      validationTestId: validationTestId,
      validity: validity,
      fieldId: id
    }), PopperWrapper]
  });
}
export default Typeahead;