import { IDClassNameProps, IOption } from '@rh-support/types';
import Downshift, { ChildrenFunction, ControllerStateAndHelpers, StateChangeOptions } from 'downshift';
import includes from 'lodash/includes';
import isEmpty from 'lodash/isEmpty';
import map from 'lodash/map';
import React from 'react';
import LoadingIndicator from '../LoadingIndicator';

interface IProps<T> extends IDClassNameProps {
  list: Array<IOption<T>>;
  selectedItem: IOption<T>;
  placeholder?: string;
  title: string;
  disabled?: boolean;
  isLoadingList?: boolean;
  searchable?: boolean;
  isInValid?: boolean;
  name?: string;
  onOuterClick?: (selectedItem: IOption<T>) => void;
  onClearSelection?: () => void;
  onChange: (selection: IOption<T>) => any;
}
const defaultProps: Partial<IProps<any>> = {
  className: '',
  id: '',
  searchable: false,
  isInValid: false,
  isLoadingList: false,
  disabled: false,
  placeholder: '',
  name: ''
};

function itemToString<T>(item: IOption<T>): string {
  return item ? item.label : '';
}

export function Dropdown<T>(props: IProps<T>) {
  const onOuterClick = (stateAndHelpers: ControllerStateAndHelpers<any>): void => {
    props.onOuterClick && props.onOuterClick(stateAndHelpers.selectedItem);
  };
  const onChange = (selection: IOption<T>): void => {
    selection && props.onChange && props.onChange(selection);
  };

  const onDownshiftStateChange = (options: StateChangeOptions<IOption<T>>, stateAndHelpers: ControllerStateAndHelpers<IOption<T>>): void => {
    switch (options.type) {
      case Downshift.stateChangeTypes.clickButton:
      case Downshift.stateChangeTypes.keyDownEscape:
      case Downshift.stateChangeTypes.keyDownSpaceButton: {
        !options.isOpen && onOuterClick(stateAndHelpers);
        break;
      }
    }
  };

  const doesMatchInput = (downshift: ControllerStateAndHelpers<IOption<T>>) => (item: IOption<T>): boolean => {
    const inputValueLC = downshift.inputValue ? downshift.inputValue.trim().toLowerCase() : '';
    const labelLC = itemToString(item).trim().toLowerCase();
    return includes(labelLC, inputValueLC);
  };
  const renderListItems = (downshift: ControllerStateAndHelpers<IOption<T>>): React.ReactNode[] => {
    const optionsToRender = props.searchable ? props.list.filter(doesMatchInput(downshift)) : props.list;
    const listToRender = [];
    downshift.isOpen && listToRender.push(...map(optionsToRender, (item, index) => (
      <li {...downshift.getItemProps({
        item, index,
        isSelected: (downshift.selectedItem.label === item.label).valueOf.toString(),
        key: `${index}-${props.id}-${itemToString(item)}`,
        disabled: item.disabled,
        className: `downshift-item${index === downshift.highlightedIndex ? ' highlighted' : ''}${itemToString(downshift.selectedItem) === itemToString(item) ? ' selected' : ''}`
      } as any)}
        title={itemToString(item)}
      >
        {itemToString(item)}
        {itemToString(downshift.selectedItem) === itemToString(item) && <i className="fa fa-check selected-check" aria-label={`${itemToString(item)} selected`} />}
        {item.additionalInfo && <div className="downshift-item-additional-info">{item.additionalInfo}</div>}
      </li>
    )));
    return listToRender;
  };

  const clearSelection = (downshift: ControllerStateAndHelpers<IOption<T>>) => (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    props.onClearSelection && props.onClearSelection();
    downshift.openMenu();
  };
  const onInputFocus = (downshift: ControllerStateAndHelpers<IOption<T>>) => (e: React.FocusEvent<HTMLInputElement>) => {
    downshift.openMenu();
  };

  const renderToggleButton = (downshift: ControllerStateAndHelpers<IOption<T>>): React.ReactNode => {
    return (<button
      {...downshift.getToggleButtonProps({
        id: `${props.id}-toggle`,
        className: `btn btn-dropdown ${props.isInValid ? 'form-invalid' : ''}`,
        disabled: props.disabled || props.isLoadingList,
        'aria-label': `${downshift.isOpen ? 'Close menu' : 'Open menu'}`,
        'aria-invalid': props.isInValid,
        name: props.name
      })}>
      <span className="sr-only">{props.title}</span>
      <label className="downshift-label" {...downshift.getLabelProps()} title={itemToString(downshift.selectedItem) || props.placeholder}>
        {itemToString(downshift.selectedItem) || props.placeholder}
      </label>
      <LoadingIndicator isInline={true} show={props.isLoadingList} />
      {!props.isLoadingList && <span className="caret" />}
    </button>);
  };

  const renderSearchableToggleButton = (downshift: ControllerStateAndHelpers<IOption<T>>): React.ReactNode => {
    return (<div className="downshift-search">
      <input
        {...downshift.getInputProps({
          placeholder: props.placeholder,
          className: `downshift-search-inline form-control${props.isInValid ? ' form-invalid' : ''}`,
          id: `${props.id}-downshift-search`,
          'aria-invalid': props.isInValid,
          disabled: props.disabled,
          onFocus: onInputFocus(downshift)
        })}
      />
      <div className="downshift-search-actions">{!isEmpty(downshift.inputValue) && (
        <button className="clear-button" onClick={clearSelection(downshift)} aria-label="clear selection" title="Clear selection" disabled={props.disabled}>
          <span className="">&times;</span>
        </button>
      )}
      <button {...downshift.getToggleButtonProps({ disabled: props.disabled, className: 'toggle-menu' })}>
        <span className="caret" />
      </button>
      </div>
    </div>);
  };

  const renderDropdown = (): ChildrenFunction<IOption<T>> => (downshift) => {
    return (
      <div id={`${props.id}`} className={`downshift ${props.className}`}>
        {props.searchable ? renderSearchableToggleButton(downshift) : renderToggleButton(downshift)}
        <ul {...downshift.getMenuProps({ className: `${downshift.isOpen ? 'downshift-open' : ''} downshift-menu` })}>
          {renderListItems(downshift)}
        </ul>
      </div>
    );
  };

  return (
    <Downshift
      onChange={onChange}
      onStateChange={onDownshiftStateChange}
      itemToString={itemToString}
      labelId={`${props.id}-label`}
      onOuterClick={onOuterClick}
      selectedItem={props.selectedItem}
    >
      {renderDropdown()}
    </Downshift>
  );
}
Dropdown.defaultProps = defaultProps;
