import React, { Component, type JSX } from 'react';
import { AutoCompleteAssistiveHint } from '@digigov/react-core/AutoCompleteAssistiveHint';
import { AutoCompleteContainer } from '@digigov/react-core/AutoCompleteContainer';
import { AutoCompleteInputBase } from '@digigov/react-core/AutoCompleteInputBase';
import { AutoCompleteMultipleInput } from '@digigov/react-core/AutoCompleteMultipleInput';
import { AutoCompleteMultipleInputContainer } from '@digigov/react-core/AutoCompleteMultipleInputContainer';
import { AutoCompleteResultList } from '@digigov/react-core/AutoCompleteResultList';
import { AutoCompleteResultListItem } from '@digigov/react-core/AutoCompleteResultListItem';
import { Base } from '@digigov/react-core/Base';
import { CaretIcon } from '@digigov/react-icons/CaretIcon';
import { Chip, ChipContainer } from '@digigov/ui/content/Chip';
import Status from '@digigov/ui/form/AutoComplete/Status';
import {
  isIosDevice,
  keyCodes,
  isPrintableKeyCode,
} from '@digigov/ui/form/AutoComplete/utils';
import Button from '@digigov/ui/form/Button';
import Checkbox, { CheckboxItem } from '@digigov/ui/form/Checkbox';
import { NormalText } from '@digigov/ui/typography/NormalText';

export interface AutoCompleteProps {
  source: (query: string, syncResults: (options: string[]) => void) => void;
  id: string;
  multiple?: boolean;
  tStatusResults?: (x: number, y: string) => string;
  tStatusNoResults?: () => string;
  tStatusQueryTooShort?: (x: number) => string;
  tStatusSelectedOption?: (x: string, y: number, z: number) => string;
  tNoResults?: () => string;
  tAssistiveHint?: () => string;
  templates?: {
    suggestion?: (value: any, query: string | string[]) => any;
    inputValue?: (value: any) => string;
  };
  width?: '25%' | '33.3%' | '50%' | '66.6%' | '75%' | '100%' | 'full';
  autoselect?: boolean;
  hint?: boolean;
  defaultValue?: string | string[];
  minLength?: number;
  name?: string;
  placeholder?: string;
  onConfirm?: (x: any) => void;
  confirmOnBlur?: boolean;
  required?: boolean;
  numberOfSelected?: 1 | 2 | 3 | 'all';
}

export interface State {
  focused: any;
  hovered: any;
  menuOpen: boolean;
  options: any[];
  query: string;
  selectedValues: any[];
  validChoiceMade: boolean;
  selected: any;
  ariaHint: boolean;
}

export interface NewQueryProps {
  menuOpen?: boolean;
  query?: string;
}

export default class AutoComplete extends Component<AutoCompleteProps, State> {
  static defaultProps = {
    width: 'full',
    autoselect: false,
    minLength: 0,
    name: 'ds-input-autocomplete',
    placeholder: '',
    hint: false,
    onConfirm: (): void => {
      return;
    },
    confirmOnBlur: false,
    required: false,
    tNoResults: (): string => 'No results found',
    tAssistiveHint: (): string =>
      'When autocomplete results are available use up and down arrows to review and enter to select.  Touch device users, explore by touch or with swipe gestures.',
  };

  elementReferences = {};

  constructor(props: AutoCompleteProps) {
    super(props);
    const customState: any = {};
    if (Array.isArray(props.defaultValue)) {
      customState.selectedValues = props.defaultValue as string[];
    } else if (props.defaultValue) {
      customState.selectedValues = [props.defaultValue as string];
    }

    this.state = {
      focused: null,
      hovered: null,
      menuOpen: false,
      options: [],
      query: this.props.multiple ? '' : (props.defaultValue as string) || '',
      selectedValues: [],
      validChoiceMade: false,
      selected: null,
      ariaHint: true,
      ...customState,
    };
    this.handleComponentBlur = this.handleComponentBlur.bind(this);
    this.handleAutoCompleteBlur = this.handleAutoCompleteBlur.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleUpArrow = this.handleUpArrow.bind(this);
    this.handleDownArrow = this.handleDownArrow.bind(this);
    this.handleEnter = this.handleEnter.bind(this);
    this.handlePrintableKey = this.handlePrintableKey.bind(this);

    this.handleListMouseLeave = this.handleListMouseLeave.bind(this);

    if (this.props.multiple) {
      this.handleOptionClick = this.handleOptionClickMultiple.bind(this);
    } else {
      this.handleOptionClick = this.handleOptionClick.bind(this);
    }
    this.handleOptionFocus = this.handleOptionFocus.bind(this);
    this.handleOptionMouseDown = this.handleOptionMouseDown.bind(this);
    this.handleOptionMouseEnter = this.handleOptionMouseEnter.bind(this);

    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleInputFocus = this.handleInputFocus.bind(this);

    this.getDirectInputChanges = this.getDirectInputChanges.bind(this);
  }

  isQueryAnOption(query: string, options: string[]): boolean {
    return (
      options
        .map((entry) => this.templateInputValue(entry).toLowerCase())
        .indexOf(query.toLowerCase()) !== -1
    );
  }

  componentDidMount(): void {
    this.getDirectInputChanges();
  }

  getDirectInputChanges(): void {
    const inputReference = this.elementReferences[-1];
    const queryHasChanged =
      inputReference && inputReference.value !== (this.state.query ?? '');
    if (queryHasChanged) {
      this.handleInputChange({ target: { value: inputReference.value } });
    }
  }

  componentDidUpdate(_, prevState: Readonly<State>): void {
    const { focused } = this.state;
    const componentLostFocus = focused === null;
    const focusedChanged = prevState.focused !== focused;
    const focusDifferentElement = focusedChanged && !componentLostFocus;
    if (focusDifferentElement) {
      this.elementReferences[focused].focus();
    }
    const focusedInput = focused === -1;
    const componentGainedFocus = focusedChanged && prevState.focused === null;
    const selectAllText = focusedInput && componentGainedFocus;
    if (selectAllText) {
      const inputElement = this.elementReferences[focused];
      inputElement.setSelectionRange(0, inputElement.value.length);
    }
  }

  hasAutoselect(): boolean {
    return (isIosDevice() as boolean)
      ? false
      : (this.props.autoselect as boolean);
  }

  // This template is used when converting from a state.options object into a state.query.
  templateInputValue(value: string): string {
    const inputValueTemplate =
      this.props.templates && this.props.templates.inputValue;
    return inputValueTemplate ? inputValueTemplate(value) : value;
  }

  // This template is used when displaying results / suggestions.
  templateSuggestion(value: string): string {
    const suggestionTemplate =
      this.props.templates && this.props.templates.suggestion;

    return suggestionTemplate
      ? suggestionTemplate(value, this.state.query)
      : value;
  }

  handleComponentBlur(newState?: NewQueryProps): void {
    const { options, query, selected } = this.state;
    const { confirmOnBlur, autoselect, onConfirm } = this.props;
    let newQuery = query;
    if (confirmOnBlur || autoselect) {
      newQuery = newState?.query ?? query;
    }
    if (confirmOnBlur) {
      onConfirm?.(options[selected as number]);
    }
    this.setState({
      focused: null,
      menuOpen: newState?.menuOpen || false,
      query: newQuery,
      selected: null,
      validChoiceMade: this.isQueryAnOption(newQuery, options),
    });
  }

  handleListMouseLeave(): void {
    this.setState({
      hovered: null,
    });
  }

  handleAutoCompleteBlur(event, index?: number): void {
    const { autoselect } = this.props;
    const { focused, options, query, selected } = this.state;
    let focusingOutsideComponent = false;
    if (event.relatedTarget === null) {
      focusingOutsideComponent = true;
    } else {
      // This affects if we have multiple autocompletes in same page and we click the first button-arrow and then another button-arrow
      focusingOutsideComponent =
        this.elementReferences['button-arrow'] !== event.relatedTarget;
    }
    const focusingInput = event.relatedTarget === this.elementReferences[-1];
    const focusingButtonArrow =
      this.elementReferences['button-arrow'] === event.relatedTarget || false;
    let focusingAnotherOption = focused !== null && focused !== -1;
    if (index !== undefined) {
      focusingAnotherOption = focused !== index && focused !== -1;
    }
    // Check if the user clicks outside and not either on option, either on input or on button arrow
    const blurComponent =
      focusingOutsideComponent &&
      !(focusingAnotherOption || focusingInput || focusingButtonArrow);

    if (blurComponent && !autoselect) {
      // In handleInputBlur:
      const newQuery = isIosDevice()
        ? query
        : this.templateInputValue(options[selected as number]);
      this.handleComponentBlur({
        menuOpen: false,
        query: newQuery,
      });
    }
    if (blurComponent && autoselect) {
      // In handleInputBlur:
      const selectedOption = this.templateInputValue(
        options[selected as number]
      );
      const newQuery = isIosDevice()
        ? query
        : selectedOption?.toLowerCase().includes(query.toLowerCase())
          ? selectedOption
          : query;
      this.handleComponentBlur({
        menuOpen: false,
        query: newQuery,
      });
    }
  }
  handleInputChange(event: { target: any }): void {
    const { source, minLength } = this.props;
    const query = event.target.value;
    const queryEmpty = query.length === 0;
    this.setState({
      query,
      ariaHint: queryEmpty,
    });
    const minLengthQuery =
      minLength != undefined && minLength > query.length ? '' : query;
    source(minLengthQuery, (options) => {
      const optionsAvailable = options.length > 0;
      this.setState({
        menuOpen: optionsAvailable,
        options,
        selected: -1,
        validChoiceMade: false,
      });
    });
  }

  handleInputClick(
    event: React.MouseEvent<HTMLInputElement, MouseEvent>
  ): void {
    this.handleInputChange(event);
  }

  handleInputFocus(): void {
    const { source } = this.props;
    const { query, validChoiceMade, options } = this.state;
    const { minLength } = this.props;
    const shouldReopenMenu =
      !validChoiceMade &&
      query.length >= (minLength as number) &&
      options.length > 0;
    if (shouldReopenMenu) {
      this.setState(({ menuOpen }) => ({
        focused: -1,
        menuOpen: shouldReopenMenu || menuOpen,
        selected: -1,
      }));
    } else {
      source(query, (options) => {
        this.setState({
          focused: -1,
          options,
        });
      });
    }
  }

  handleOptionFocus(index: number): void {
    this.setState({
      focused: index,
      hovered: null,
      selected: index,
    });
  }

  handleOptionMouseEnter(index: number): void {
    // iOS Safari prevents click event if mouseenter adds hover background colour
    // See: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW4
    if (!isIosDevice()) {
      this.setState({
        hovered: index,
      });
    }
    this.handleOptionFocus(index);
  }

  handleOptionClickMultiple(index: number): void {
    const selectedOption = this.state.options[index];
    let selectedValues = this.state.selectedValues;
    if (this.state.selectedValues.indexOf(selectedOption) === -1) {
      selectedValues = selectedValues.concat([selectedOption]);
    } else {
      selectedValues.splice(
        this.state.selectedValues.indexOf(selectedOption),
        1
      );
    }
    this.props.onConfirm?.(selectedValues);
    this.setState({
      focused: index,
      hovered: null,
      menuOpen: true,
      query: '',
      selectedValues,
      selected: index,
      validChoiceMade: true,
    });
    this.forceUpdate();
  }

  handleOptionClick(index: number): void {
    const selectedOption = this.state.options[index];
    const newQuery = this.templateInputValue(selectedOption);
    this.props.onConfirm?.(selectedOption);
    const selectedValues = this.props.multiple
      ? this.state.selectedValues.concat(selectedOption)
      : [selectedOption];
    this.setState({
      focused: -1,
      hovered: null,
      menuOpen: false,
      query: newQuery,
      selectedValues,
      selected: -1,
      validChoiceMade: true,
    });
    this.forceUpdate();
  }

  handleOptionMouseDown(event: { preventDefault: () => void }): void {
    // Safari triggers focusOut before click, but if you
    // preventDefault on mouseDown, you can stop that from happening.
    // If this is removed, clicking on an option in Safari will trigger
    // `handleOptionBlur`, which closes the menu, and the click will
    // trigger on the element underneath instead.
    // See: http://stackoverflow.com/questions/7621711/how-to-prevent-blur-running-when-clicking-a-link-in-jquery
    event.preventDefault();
  }

  handleUpArrow(event: { preventDefault: () => void }): void {
    event.preventDefault();
    const { menuOpen, selected } = this.state;
    const isNotAtTop = selected !== -1;
    const allowMoveUp = isNotAtTop && menuOpen;
    if (allowMoveUp) {
      this.handleOptionFocus((selected as number) - 1);
    }
  }

  handleDownArrow(event: { preventDefault: () => void }): void {
    event.preventDefault();
    // if not open, open
    if (this.state.menuOpen === false) {
      event.preventDefault();
      this.props.source('', (options) => {
        this.setState({
          menuOpen: true,
          options,
          selected: 0,
          focused: 0,
          hovered: null,
        });
      });
    } else if (this.state.menuOpen === true) {
      const { menuOpen, options, selected } = this.state;
      const isNotAtBottom = selected !== options.length - 1;
      const allowMoveDown = isNotAtBottom && menuOpen;
      if (allowMoveDown) {
        this.handleOptionFocus((selected as number) + 1);
      }
    }
  }

  handleSpace(event: { preventDefault: () => void }): void {
    // if not open, open
    if (this.state.menuOpen === false && this.state.query === '') {
      event.preventDefault();
      this.props.source('', (options) => {
        this.setState({
          menuOpen: true,
          options,
        });
      });
    }
    const focusIsOnOption = this.state.focused !== -1;
    if (focusIsOnOption) {
      event.preventDefault();
      this.handleOptionClick(this.state.focused);
    }
  }

  handleEnter(event: { preventDefault: () => void }): void {
    if (this.state.menuOpen) {
      event.preventDefault();
      const hasSelectedOption = this.state.selected >= 0;
      if (hasSelectedOption) {
        this.handleOptionClick(this.state.selected);
        if (this.props.multiple) {
          this.setState({
            menuOpen: false,
            focused: -1,
          });
        }
      }
    }
  }

  handlePrintableKey(event: { target: any }): void {
    const inputElement = this.elementReferences[-1];
    const eventIsOnInput = event.target === inputElement;
    if (!eventIsOnInput) {
      // FIXME: This would be better if it was in componentDidUpdate,
      // but using setState to trigger that seems to not work correctly
      // in preact@8.1.0.
      inputElement.focus();
    }
  }

  handleKeyDown(event: {
    preventDefault: () => void;
    keyCode: number;
    target: any;
  }): void {
    switch (keyCodes[event.keyCode]) {
      case 'up':
        this.handleUpArrow(event);
        break;
      case 'down':
        this.handleDownArrow(event);
        break;
      case 'space':
        this.handleSpace(event);
        break;
      case 'enter':
        this.handleEnter(event);
        break;
      case 'escape': {
        this.handleComponentBlur({
          query: this.state.query,
          menuOpen: false,
        });
        break;
      }
      case 'backspace':
        if (this.props.multiple) {
          if (
            this.state.query.length === 0 &&
            this.state.selectedValues.length > 0
          ) {
            event.preventDefault();
            const updatedSelectedValues = this.state.selectedValues.slice(
              0,
              -1
            );
            this.setState(
              {
                selectedValues: updatedSelectedValues,
              },
              () => {
                this.props.onConfirm?.(updatedSelectedValues);
              }
            );
          } else {
            if (isPrintableKeyCode(event.keyCode)) {
              this.handlePrintableKey(event);
            }
          }
        } else {
          this.props.onConfirm?.('');
        }
        break;
      default:
        if (isPrintableKeyCode(event.keyCode)) {
          this.handlePrintableKey(event);
        }
        break;
    }
  }
  render(): JSX.Element {
    const {
      id,
      width,
      minLength,
      name,
      placeholder,
      required,
      hint,
      tNoResults,
      tStatusQueryTooShort,
      tStatusNoResults,
      tStatusSelectedOption,
      tStatusResults,
      tAssistiveHint,
      multiple,
      source,
    } = this.props;
    const {
      focused,
      hovered,
      menuOpen,
      options,
      selected,
      ariaHint,
      validChoiceMade,
    } = this.state;
    const autoselect = this.hasAutoselect();
    const query = this.state.query ?? '';
    const inputFocused = focused === -1;
    const noOptionsAvailable = options.length === 0;
    const queryNotEmpty = query.length !== 0;
    const queryLongEnough = query.length >= (minLength as number);
    const showNoOptionsFound =
      inputFocused && noOptionsAvailable && queryNotEmpty && queryLongEnough;
    const componentIsFocused = focused !== null;
    const optionFocused = focused !== -1 && focused !== null;
    const menuIsVisible = menuOpen || showNoOptionsFound;
    const selectedOptionText = this.templateInputValue(options[selected]);
    const optionBeginsWithQuery =
      selectedOptionText &&
      selectedOptionText.toLowerCase().indexOf(query.toLowerCase()) === 0;
    const hintValue =
      optionBeginsWithQuery && (autoselect || hint)
        ? query + selectedOptionText.substr(query.length)
        : '';

    const assistiveHintID = id + '__assistiveHint';
    const ariaDescribedProp = ariaHint
      ? {
          'aria-describedby': assistiveHintID,
        }
      : null;

    const showAutoCompleteResultListItems = (
      options: string[],
      multiple: boolean
    ) => {
      {
        return options.map((option, index) => {
          const showFocused =
            focused === -1 ? selected === index : focused === index;
          const iosPosinsetHtml = isIosDevice() ? (
            <Base
              as="span"
              id={`${id}__option-suffix--${index}`}
              style={{
                border: '0',
                clip: 'rect(0 0 0 0)',
                height: '1px',
                marginBottom: '-1px',
                marginRight: '-1px',
                overflow: 'hidden',
                padding: '0',
                position: 'absolute',
                whiteSpace: 'nowrap',
                width: '1px',
              }}
            >
              {index + 1} από {options.length}
            </Base>
          ) : (
            ''
          );
          return (
            <AutoCompleteResultListItem
              isFocused={showFocused}
              isHovered={hovered}
              index={index}
              aria-selected={focused === index ? 'true' : 'false'}
              key={index}
              onBlur={(event) => this.handleAutoCompleteBlur(event, index)}
              onClick={() => this.handleOptionClick(index)}
              onMouseDown={this.handleOptionMouseDown}
              onMouseEnter={() => this.handleOptionMouseEnter(index)}
              ref={(optionEl) => {
                this.elementReferences[index] = optionEl;
              }}
              role="option"
              tabIndex={-1}
              // @ts-ignore
              value={typeof option === 'object' ? option?.value : option}
              aria-posinset={index + 1}
              aria-setsize={options.length}
            >
              {multiple ? (
                <CheckboxItem
                  value={option}
                  checked={this.state.selectedValues.includes(option)}
                  onChange={() => {}}
                >
                  {this.templateSuggestion(option)}
                </CheckboxItem>
              ) : (
                this.templateSuggestion(option)
              )}
              {iosPosinsetHtml}
            </AutoCompleteResultListItem>
          );
        });
      }
    };

    return (
      <AutoCompleteContainer onKeyDown={this.handleKeyDown} width={width}>
        <Status
          id={id}
          length={options.length}
          queryLength={query.length}
          minQueryLength={minLength as number}
          selectedOption={this.templateInputValue(options[selected])}
          selectedOptionIndex={selected}
          validChoiceMade={validChoiceMade}
          isInFocus={this.state.focused !== null}
          tQueryTooShort={tStatusQueryTooShort}
          tNoResults={tStatusNoResults}
          tSelectedOption={tStatusSelectedOption}
          tResults={tStatusResults}
        />
        {multiple && !autoselect ? (
          <AutoCompleteMultipleInputContainer
            isFocused={componentIsFocused}
            numberOfSelected={this.props.numberOfSelected}
          >
            <ChipContainer>
              {this.state.selectedValues.map((value, index) => {
                return (
                  <Chip
                    key={index}
                    onDelete={() => {
                      this.setState(
                        (prevState) => ({
                          selectedValues: prevState.selectedValues.filter(
                            (v) => {
                              if (typeof v === 'string') {
                                return v !== value;
                              } else {
                                return v.value !== value.value;
                              }
                            }
                          ),
                        }),
                        () => {
                          this.props.onConfirm?.(this.state.selectedValues);
                        }
                      );
                    }}
                  >
                    {typeof value === 'string' ? value : value.label.primary}
                  </Chip>
                );
              })}
              {!componentIsFocused &&
                typeof this.props.numberOfSelected === 'number' &&
                this.state.selectedValues.length >
                  this.props.numberOfSelected && (
                  <NormalText fontWeight="bold" margin={0}>
                    +
                    {this.state.selectedValues.length -
                      this.props.numberOfSelected}
                  </NormalText>
                )}
            </ChipContainer>
            <AutoCompleteMultipleInput
              aria-expanded={menuOpen ? 'true' : 'false'}
              aria-activedescendant={
                optionFocused ? `${id}__option--${focused}` : ''
              }
              aria-owns={`${id}__listbox`}
              aria-autocomplete={this.hasAutoselect() ? 'both' : 'list'}
              {...ariaDescribedProp}
              autoComplete="off"
              // isFocused={componentIsFocused}
              id={id}
              onClick={(event) => this.handleInputClick(event)}
              onBlur={(event) => this.handleAutoCompleteBlur(event)}
              onChange={this.handleInputChange}
              onFocus={this.handleInputFocus}
              name={name}
              placeholder={hintValue || placeholder}
              ref={(inputElement) => {
                this.elementReferences[-1] = inputElement;
              }}
              role="combobox"
              required={required}
              value={query}
            />
            <Button
              id={`${id}__button-arrow`}
              ref={(buttonElement) => {
                this.elementReferences['button-arrow'] = buttonElement;
              }}
              variant="link"
              onClick={() => {
                if (!this.state.menuOpen) {
                  source(query, (options) => {
                    const optionsAvailable = options.length > 0;
                    this.setState({
                      menuOpen: optionsAvailable,
                      options,
                      selected: -1,
                      validChoiceMade: false,
                    });
                  });
                } else {
                  this.setState({
                    menuOpen: false,
                    focused: null,
                  });
                }
              }}
              onBlur={(event) => this.handleAutoCompleteBlur(event)}
            >
              <CaretIcon direction="down" size="md" />
            </Button>

            <AutoCompleteResultList
              isVisible={menuIsVisible}
              onMouseLeave={() => this.handleListMouseLeave()}
              id={`${id}__listbox`}
              role="listbox"
            >
              <Checkbox>
                {showAutoCompleteResultListItems(options, multiple)}
              </Checkbox>

              {showNoOptionsFound && (
                <AutoCompleteResultListItem empty>
                  {tNoResults?.()}
                </AutoCompleteResultListItem>
              )}
            </AutoCompleteResultList>

            <AutoCompleteAssistiveHint id={assistiveHintID}>
              {tAssistiveHint?.()}
            </AutoCompleteAssistiveHint>
          </AutoCompleteMultipleInputContainer>
        ) : (
          <>
            <AutoCompleteInputBase
              aria-expanded={menuOpen ? 'true' : 'false'}
              aria-activedescendant={
                optionFocused ? `${id}__option--${focused}` : ''
              }
              aria-owns={`${id}__listbox`}
              aria-autocomplete={this.hasAutoselect() ? 'both' : 'list'}
              {...ariaDescribedProp}
              autoComplete="off"
              isFocused={componentIsFocused}
              id={id}
              onClick={(event) => this.handleInputClick(event)}
              onBlur={this.handleAutoCompleteBlur}
              onChange={this.handleInputChange}
              onFocus={this.handleInputFocus}
              name={name}
              placeholder={hintValue || placeholder}
              ref={(inputElement) => {
                this.elementReferences[-1] = inputElement;
              }}
              role="combobox"
              required={required}
              value={query}
            />
            <CaretIcon direction="down" size="md" />

            <AutoCompleteResultList
              isVisible={menuIsVisible}
              onMouseLeave={() => this.handleListMouseLeave()}
              id={`${id}__listbox`}
              role="listbox"
            >
              {showAutoCompleteResultListItems(options, false)}
              {showNoOptionsFound && (
                <AutoCompleteResultListItem empty>
                  {tNoResults?.()}
                </AutoCompleteResultListItem>
              )}
            </AutoCompleteResultList>
            <AutoCompleteAssistiveHint id={assistiveHintID}>
              {tAssistiveHint?.()}
            </AutoCompleteAssistiveHint>
          </>
        )}
      </AutoCompleteContainer>
    );
  }
}

export {
  AutoComplete,
  AutoCompleteInputBase,
  AutoCompleteResultList,
  AutoCompleteResultListItem,
  AutoCompleteContainer,
  AutoCompleteAssistiveHint,
  AutoCompleteMultipleInputContainer,
  AutoCompleteMultipleInput,
};
