text: /** @component combo-box */
  
  import React from 'react';
  import PropTypes from 'prop-types';
  import {
    EventOverlay,
    Input,
    ListItem,
    InputSearch,
  } from '@momentum-ui/react';
  import omit from 'lodash/omit';
  import uniqueId from 'lodash/uniqueId';
  
  class ComboBox extends React.Component {
    static displayName = 'ComboBox';
  
    state = {
      filteredOptions: [],
      focus: -1,
      id: this.props.id || uniqueId('md-combo-box-'),
      isOpen: false,
      value: '',
    };
  
    componentDidMount() {
      const { children } = this.props;
  
      this.options =
        (children && React.Children.toArray(children))
        || this.mapOptionsToListItem();
  
      this.setFilteredOptions();
    }
  
    componentDidUpdate(prevProps) {
      const { options, children } = this.props;
      const { value } = this.state;
  
      if (
        (prevProps.options !== options)
        || (prevProps.children !== children)
      ) {
  
        this.options =
          (children && React.Children.toArray(children))
          || this.mapOptionsToListItem();
  
        this.setFilteredOptions(value);
      }
    }
  
    mapOptionsToListItem = () => {
      const { options } = this.props;
  
      return options.map((option, i) =>
        <ListItem key={i} label={option} />
      );
    }
  
    setFilteredOptions = filter => {
      const { onChange } =  this.props;
  
      const filteredOptions = !onChange
        ? this.applyFilter(filter)
        : this.options;
  
      this.setState({
        isOpen: !!filteredOptions.length,
        filteredOptions,
      });
    }
  
    hidePopover = () => {
      this.setState({
        isOpen: false
      });
    };
  
    handleToggle = () => {
      const { filteredOptions } = this.state;
  
      filteredOptions.length
      && this.setState({ isOpen: true });
    };
  
    applyFilter = value => {
      const { searchProp } = this.props;
      const isSubString = string => value && string.toLowerCase().includes(value.toLowerCase());
  
      return this.options.filter(option =>
        (option.props[searchProp] && isSubString(option.props[searchProp]))
          || ['ListItemHeader'].includes(option.type.displayName)
      );
    };
  
    handleChange = e => {
      const { onChange } = this.props;
      const { focus } = this.state;
  
      this.setFilteredOptions(e.target.value);
      this.setState({
        value: e.target.value,
        focus: !onChange ? -1 : focus,
      }, () => onChange && onChange(e, e.target.value));
    };
  
    handleClick = (e, selectedOption) => {
      const { searchProp } = this.props;
      const { onSelect } = this.props;
  
      this.setFilteredOptions(selectedOption.props[searchProp]);
      this.setState({
        value: selectedOption.props[searchProp],
        isOpen: false,
        focus: -1,
      }, () => onSelect && onSelect(e, selectedOption));
    };
  
    setFocus = index => {
      this.setState({ focus: index });
    };
  
    handleKeyDown = e => {
      let flag = false;
      let newIndex;
      const { filteredOptions, focus, isOpen } = this.state;
      const length = filteredOptions && filteredOptions.length - 1;
  
      const getNewIndex = (currentIndex, change) => {
        const getPossibleIndex = () => {
          if (currentIndex + change < 0) {
            return length;
          } else if (currentIndex + change > length) {
            return 0;
          }
  
          return currentIndex + change;
        };
  
        const possibleIndex = getPossibleIndex();
        const potentialTarget = React.Children.toArray(filteredOptions)[possibleIndex];
  
        return (potentialTarget.props.disabled || potentialTarget.props.isReadOnly)
          ? getNewIndex(possibleIndex, change)
          : possibleIndex;
      };
  
      switch (e.which) {
  
        case 13:
          isOpen
          && (focus !== -1)
          && this.handleClick(e, filteredOptions[focus]);
          flag = true;
          break;
  
        case 38:
          if(isOpen) {
            newIndex = getNewIndex(focus, -1);
            this.setFocus(newIndex);
          }
          flag = true;
          break;
  
        case 40:
          if(isOpen) {
            newIndex = getNewIndex(focus, 1);
            this.setFocus(newIndex);
          }
          flag = true;
          break;
  
        default:
          break;
      }
      if (flag) {
        e.stopPropagation();
        e.preventDefault();
      }
    };
  
    render() {
      const {
        className,
        clear,
        disabled,
        hasSearchIcon,
        inputProps,
        placeholder,
        ...props
      } = this.props;
  
      const otherProps = omit({...props}, [
        'children',
        'id',
        'onChange',
        'onSelect',
        'options',
        'searchProp',
      ]);
  
      const {
        filteredOptions,
        focus,
        id,
        isOpen,
        value,
      } = this.state;
  
      const activeDescendant = this.activeChild && this.activeChild.id;
      const InputComp = hasSearchIcon ? InputSearch : Input;
  
      const input = (
        <InputComp
          aria-autocomplete='list'
          clear={clear}
          disabled={disabled}
          inputRef={ref => this.anchorNode = ref}
          onChange={this.handleChange}
          onClick={this.handleToggle}
          onKeyDown={this.handleKeyDown}
          placeholder={placeholder}
          value={value}
          {...activeDescendant && { 'aria-activedescendant': activeDescendant }}
          {...inputProps}
        />
      );
  
      const renderFilteredOption = filteredOptions
        && filteredOptions.map((option, i) =>
          React.cloneElement(option, {
            active: i === focus,
            key: i,
            onClick: e => this.handleClick(e, option),
            refName: 'option',
            role: 'option',
            ...focus === i && { ref: ref => this.activeChild = ref },
          })
        );
  
      const dropdownElement = (
        this.anchorNode 
        && isOpen 
        && (
          <EventOverlay
            allowClickAway
            anchorNode={this.anchorNode}
            close={this.hidePopover}
            isOpen={isOpen}
            {...otherProps}
          >
            <div
              className='md-combo-box__options'
              id={id}
              role='listbox'
              {...this.anchorNode &&
                {
                  style: {
                    width: this.anchorNode.getBoundingClientRect().width
                  }
                }
              }
            >
              {renderFilteredOption}
            </div>
          </EventOverlay>
        )
      );
  
      return (
        <div
          aria-controls={id}
          aria-haspopup='listbox'
          aria-expanded={isOpen}
          className={
            'md-combo-box' +
            `${(className && ` ${className}`) || ''}`
          }
          role='combobox'
        >
          {input}
          {dropdownElement}
        </div>
      );
    }
  }
  
  ComboBox.propTypes = {
    /** @prop Children nodes to render inside ComboBox | null */
    children: PropTypes.node,
    /** @prop Optional css class string | '' */
    className: PropTypes.string,
    /** @prop Sets the initial input element as empty | false */
    clear: PropTypes.bool,
    /** @prop Sets the attribute disabled to the ComboBox | false */
    disabled: PropTypes.bool,
    /** @prop Sets the ComboBox to have a search icon | true */
    hasSearchIcon: PropTypes.bool,
    /** @prop Sets the ID of the ComboBox */
    id: PropTypes.string,
    /** @prop Collection of props unique for Input element | null */
    inputProps: PropTypes.shape({}),
    /** @prop Handler invoked when the user presses any key | null */
    onChange: PropTypes.func,
    /** @prop Handler invoked when the user selects the ComboBox | null  */
    onSelect: PropTypes.func,
    /** @prop Array of options for the ComboBox dropdown | [] */
    options: PropTypes.arrayOf(PropTypes.string),
    /** @prop Text that initially populates the input field for guidence | ''  */
    placeholder: PropTypes.string,
    /** @prop Sets the search prop | 'label' */
    searchProp: PropTypes.string,
    /** @prop Sets the target offset | { horizontal: 0, vertical: 4 } */
    targetOffset: PropTypes.shape({
      horizontal: PropTypes.number,
      vertical: PropTypes.number,
    }),
  };
  
  ComboBox.defaultProps = {
    children: null,
    className: '',
    clear: false,
    disabled: false,
    hasSearchIcon: true,
    id: null,
    inputProps: null,
    onChange: null,
    onSelect: null,
    options: [],
    placeholder: '',
    searchProp: 'label',
    targetOffset: {
      horizontal: 0,
      vertical: 4,
    },
  };
  
  export default ComboBox;
  