import { cloneElement, useRef } from 'react';
import Button from '../Button/Button';
import DropdownMenu from '../Dropdown/DropdownMenu';
import classNames from 'classnames';
import mergeRefs from '../utilities/mergeRefs';
import useId from '../utilities/useId';
import { config } from '../config';
import { renderReactStatelyItems, renderStatusMessage, getTextFieldChild, getActiveDescendant, removeUndefined, findItemById } from './utils';
import { t } from '../i18n';
import { useComboBox } from '../react-aria'; // from react-aria
import { useComboBoxState } from '../react-aria'; // from react-stately
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
 * The Autocomplete component wraps a TextField component and turns it into a combobox,
 * where a user can type into the text field and see matching results. They can then
 * select one of these results from the list, which will trigger an `onChange` event on
 * the Autocomplete.
 *
 * The two event handlers that should be used when this is a controlled component are
 * `onChange` and `onInputValueChange`. They are defined on the Autocomplete component
 * and not its child TextField component.
 *
 * As the user types and `onInputValueChange` is called, you should be supplying relevant
 * results to the Autocomplete through the `items` prop. The `items` prop is an array of
 * objects. Passing an empty array will show a "No results" message. If you do not yet
 * want to show results—for instance, because they haven't typed enough characters yet to
 * make a database call—the `items` prop should remain be undefined. If you are still
 * loading the results, use the `loading` boolean prop to display the loading message to
 * the user.
 *
 * For information about how and when to use this component,
 * [refer to its full documentation page](https://design.cms.gov/components/autocomplete/).
 */
export const Autocomplete = props => {
  const id = useId('autocomplete--', props.id);
  const menuId = `${id}__menu`;
  const {
    ariaClearLabel,
    autoCompleteLabel,
    autoFocus,
    children,
    className,
    clearInputText,
    clearSearchButton,
    focusTrigger,
    inputRef: userInputRef,
    items,
    itemToString,
    label: menuHeading,
    labelId: menuHeadingId,
    loading,
    loadingMessage,
    noResultsMessage,
    onChange,
    onInputValueChange,
    ...autocompleteProps
  } = props;
  const hasValidStandaloneItems = items?.some(item => !('items' in item));
  const hasValidGroupedItems = items?.some(item => 'items' in item && item.items?.length > 0);

  // Determine what we'll show based on state
  let reactStatelyItems = [];
  let statusMessage;
  if (hasValidStandaloneItems || hasValidGroupedItems) {
    reactStatelyItems = renderReactStatelyItems(items, itemToString);
  } else if (loading) {
    // If we're waiting for results to load, show the non-selected message
    statusMessage = renderStatusMessage(loadingMessage ?? t('autocomplete.loadingMessage'));
  } else if (items) {
    // If we have no results (empty array), show the non-selected message
    statusMessage = renderStatusMessage(noResultsMessage ?? t('autocomplete.noResultsMessage'));
  }
  const textField = getTextFieldChild(children);
  const size = textField.props.size;
  const labelId = textField.props.labelId ?? `${id}__label`;
  const state = useComboBoxState({
    ...autocompleteProps,
    allowsCustomValue: true,
    allowsEmptyCollection: true,
    children: reactStatelyItems,
    inputValue: textField.props.value,
    onInputChange: onInputValueChange ? value => {
      onInputValueChange(value);
    } : undefined,
    onSelectionChange: onChange ? selectedKey => {
      const selectedItem = items ? findItemById(String(selectedKey), items) : undefined;
      // We don't call onChange when the user deletes text, even though react-aria will call
      // this function with `null` if the input is cleared out. This is to maintain backwards
      // compatability, but we could consider changing this behavior in the future. If we
      // decide to remove this check, we can also remove the explicit `onChange` call in the
      // clear button handler.
      if (selectedItem) {
        onChange(selectedItem);
      }
    } : undefined
  });
  const inputRef = useRef();
  const listBoxRef = useRef();
  const wrapperRef = useRef();
  const useComboboxProps = useComboBox({
    ...autocompleteProps,
    name: textField.props.name,
    label: textField.props.label,
    isDisabled: textField.props.disabled,
    inputRef,
    listBoxRef,
    popoverRef: listBoxRef
  }, state);

  // The display of bottom placed errorMessages in TextField breaks the Autocomplete's UI design.
  // Add errorMessageClassName to fix the styles for bottom placed errors
  const bottomError = (textField.props.errorPlacement === 'bottom' || config().errorPlacementDefault === 'bottom') && textField.props.errorMessage != null;
  const errorMessageClassName = classNames(textField.props.errorMessageClassName, bottomError && 'ds-c-autocomplete__error-message', bottomError && clearSearchButton && 'ds-c-autocomplete__error-message-clear-btn');
  const textFieldProps = removeUndefined({
    ...useComboboxProps.inputProps,
    autoComplete: autoCompleteLabel,
    autoFocus: autoFocus || focusTrigger,
    'aria-activedescendant': useComboboxProps.inputProps['aria-activedescendant'] ? getActiveDescendant(id, state, items) : undefined,
    'aria-controls': menuId,
    'aria-labelledby': labelId,
    errorMessageClassName,
    id,
    labelId,
    inputRef: mergeRefs([inputRef, userInputRef]),
    // Restores previous functionality where if you had typed characters into the text
    // field to get results and then blur away and come back, it'll open the results
    // list again without having to press anything on the keyboard.
    onFocus: event => {
      useComboboxProps.inputProps.onFocus?.(event);
      textField.props.onFocus?.(event);
      state.open();
    },
    // Allow the user to continue to attach their own event handlers to the TextField.
    // The following event handlers would normally be overwritten by useCombobox.
    onChange: event => {
      useComboboxProps.inputProps.onChange?.(event);
      textField.props.onChange?.(event);
    },
    onBlur: event => {
      useComboboxProps.inputProps.onBlur?.(event);
      textField.props.onBlur?.(event);
    },
    onTouchEnd: event => {
      useComboboxProps.inputProps.onTouchEnd?.(event);
      textField.props.onTouchEnd?.(event);
    },
    onKeyDown: event => {
      useComboboxProps.inputProps.onKeyDown?.(event);
      textField.props.onKeyDown?.(event);
    }
  });
  const rootClassName = classNames('ds-c-autocomplete', className);
  return /*#__PURE__*/_jsxs("div", {
    className: rootClassName,
    ref: wrapperRef,
    children: [/*#__PURE__*/cloneElement(textField, textFieldProps), (state.isOpen && reactStatelyItems.length > 0 || state.isFocused && statusMessage) && /*#__PURE__*/_jsx(DropdownMenu, {
      ...useComboboxProps.listBoxProps,
      componentClass: "ds-c-autocomplete",
      heading: menuHeading,
      labelId: menuHeadingId,
      menuId: menuId,
      rootId: id,
      size: size,
      state: state,
      triggerRef: wrapperRef,
      listBoxRef: listBoxRef,
      children: statusMessage
    }), clearSearchButton && /*#__PURE__*/_jsx(Button, {
      "aria-label": ariaClearLabel ?? t('autocomplete.ariaClearLabel'),
      className: "ds-u-padding-right--0 ds-c-autocomplete__clear-btn",
      onClick: () => {
        state.setSelectedKey(null);
        state.setInputValue('');
        if (state.selectedKey) {
          onChange?.(null);
        }
      },
      size: "small",
      variation: "ghost",
      disabled: textField.props.disabled,
      children: clearInputText ?? t('autocomplete.clearInputText')
    })]
  });
};
Autocomplete.defaultProps = {
  autoCompleteLabel: 'off',
  clearSearchButton: true
};
export default Autocomplete;