import classNames from 'classnames';
import uniq from 'lodash.uniq';
import flatten from 'lodash.flatten';
import isequal from 'lodash.isequal';
import uniqwith from 'lodash.uniqwith';
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';

import wrap from '../ItemContainerWrapper';
import MainButton from './MainButton';
import OptionsList from './OptionsList';
import ClearAll from '../ClearAll';
import { itemHandlerCreator } from '../../utils';
import { ADD_CHICKLET, NON_SELECTED, ARROW_UP, ARROW_DOWN, ENTER, ESCAPE } from '../../constants';

const defaultProps = {
  placeholderText: 'Select option...',
  placeholderHandler:
    (selecteds, placeholderText) => `${selecteds.length} ${placeholderText}(s) selected`,
  chicklets: { container: { visible: false } },
  selectBehavior: ADD_CHICKLET,
  events: {},
};

const propTypes = {
  theme: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.func,
  ]).isRequired,
  placeholderText: PropTypes.string,
  placeholderHandler: PropTypes.func,
  dropDownOptions: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string,
      options: PropTypes.arrayOf(
        PropTypes.shape({
          value: PropTypes.string,
          label: PropTypes.string,
        }),
      ),
    }),
  ),
  kdFilterModel: PropTypes.object.isRequired,
  wrapperState: PropTypes.object,
  onClearChicklets: PropTypes.func.isRequired,
  onSetChicklets: PropTypes.func.isRequired,
  selectedValuesInitial: PropTypes.array,
  isChickletNone: PropTypes.bool,
  emptyItems: PropTypes.bool,
};

class KdDropDownMultiple extends React.Component {
  constructor(props) {
    super(props);

    this.kdFilterOptions = {
      model: props.kdFilterModel,
      operator: 'Eq',
      itemHandler: itemHandlerCreator(props.kdFilterModel.getDataType()),
    };
    this.allOptions = flatten(props.dropDownOptions.map(group => group.options));
    this.state = {
      optionsListVisible: false,
      mainButtonLabel: props.placeHolderText,
      activeValue: null,
      activeOption: NON_SELECTED,
    };
  }

  handleOutsideClick = (e) => {
    if (ReactDOM.findDOMNode(this).contains(e.target)) {
      return;
    }
    this.resetDisplayed();
  }

  componentWillMount() {
    document.addEventListener('click', this.handleOutsideClick, false);
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.handleOutsideClick, false);
  }

  componentDidMount() {
    this.setSelectedValuesInitial(this.props.selectedValuesInitial);
  }

  componentWillReceiveProps(nextProps) {
    const { selectedValuesInitial, onClearChicklets } = this.props;
    if (selectedValuesInitial && !isequal(selectedValuesInitial, nextProps.selectedValuesInitial)) {
      onClearChicklets(false);
      this.setSelectedValuesInitial(nextProps.selectedValuesInitial);
    }
  }

  setSelectedValuesInitial = (initialValues) => {
    const { dropDownOptions } = this.props;
    const options = initialValues
      .filter(option => this.allOptions.some(o => o.value === option.value));
    options.forEach(option => this.changeOption(option, true));
  }

  changeOption = (option, checked) => {
    const { itemsState } = this.props.wrapperState;
    const stateOption = this.buildItem(option);
    let newSelecteds;
    if (checked) {
      newSelecteds = [...itemsState, stateOption];
    } else {
      newSelecteds =
        itemsState.filter(selectedOption => selectedOption.value !== stateOption.value);
    }
    this.addItems(newSelecteds);
  }

  addItems = (selecteds) => {
    const { onSetChicklets, onClearChicklets, events: { onChange } } = this.props;
    if (selecteds && selecteds.length) {
      const event = () => {
        onChange && onChange({ data: selecteds });
      };
      onSetChicklets(selecteds, event);
    } else {
      onClearChicklets();
    }
  }

  changeGroup = (group, checked) => {
    const { itemsState } = this.props.wrapperState;
    let newSelecteds;
    if (checked) {
      newSelecteds = [...itemsState, ...group.options.map(this.buildItem)];
    } else {
      newSelecteds = itemsState.filter(selected => !group.options.find(o => o.value === selected.value));
    }
    this.addItems(uniqwith(newSelecteds, (x, y) => x.value === y.value));
  }

  buildItem = ({ value, label }) => {
    return ({
      value,
      label,
      key: value,
      chickletLabel: label,
      kdFilterOptions: this.kdFilterOptions,
    });
  }

  checkAll = () => {
    const { dropDownOptions } = this.props;
    this.addItems(
      this.allOptions.map(this.buildItem),
    );
  }

  uncheckAll = () => {
    this.props.onClearChicklets();
  }

  toggleOptionsList = () => {
    this.setState(
      { optionsListVisible: !this.state.optionsListVisible },
      () => (this.state.optionsListVisible && this.optionList.focus()),
    );
  }

  mainButtonLabel = () => {
    const { itemsState } = this.props.wrapperState;
    const { placeholderText, placeholderHandler } = this.props;

    if (itemsState && itemsState.length === 0) {
      return `${placeholderText}` || 'Search...';
    }

    if (placeholderHandler) {
      return placeholderHandler(itemsState, placeholderText);
    }

    return `${itemsState.length} Selected`;
  }

  resetDisplayed = () => {
    this.setState({
      optionsListVisible: false,
      activeOption: NON_SELECTED,
      activeValue: null,
    });
  }

  isOptionSelected = () => {
    const { activeOption } = this.state;
    return (activeOption !== NON_SELECTED);
  }

  setActiveOption = (value) => {
    if (value) {
      const option = this.allOptions.findIndex(o => o.value === value);
      this.setState({ activeOption: option, activeValue: this.allOptions[option] });
    }
  }

  resetActiveOption = () => {
    this.setState({ activeOption: NON_SELECTED });
  }

  handleKeyDown = (e) => {
    const { allOptions } = this;
    const { activeOption } = this.state;
    const allOptionsLength = allOptions.length;
    e.preventDefault();
    if (allOptionsLength) {
      switch (e.keyCode) {
        case ARROW_DOWN: {
          const next = this.isOptionSelected() ? (activeOption + 1) % allOptionsLength : 0;
          this.setState({ activeOption: next, activeValue: this.allOptions[next] });
          break;
        }
        case ARROW_UP: {
          const prev = this.isOptionSelected() && (activeOption !== 0)
            ? (activeOption - 1) % allOptionsLength
            : allOptionsLength - 1;
          this.setState({ activeOption: prev, activeValue: this.allOptions[prev] });
          break;
        }
        case ENTER: {
          const { activeValue } = this.state;
          const { itemsState } = this.props.wrapperState;
          const checked = !itemsState.find(o => o.value === activeValue.value);
          this.changeOption(activeValue, checked);
          break;
        }
        case ESCAPE: {
          this.resetDisplayed();
          break;
        }
        default:
          break;
      }
    }
  }

  renderOptionsList = () => {
    const { itemsState } = this.props.wrapperState;
    if (this.state.optionsListVisible) {
      return (
        <OptionsList
          theme={this.props.theme}
          checkAll={this.checkAll}
          uncheckAll={this.uncheckAll}
          dropDownOptions={this.props.dropDownOptions}
          changeOption={this.changeOption}
          changeGroup={this.changeGroup}
          toggleOptionsList={this.toggleOptionsList}
          selecteds={itemsState}
          optionRef={ref => (this.optionList = ref)}
          activeValue={this.state.activeValue && this.state.activeValue.value}
          handleKeyDown={this.handleKeyDown}
          resetActiveOption={this.resetActiveOption}
          setActiveOption={this.setActiveOption}
        />
      );
    }
    return null;
  }

  render() {
    const { theme, chicklets, emptyItems, isChickletNone, onClearChicklets } = this.props;

    return (
      <div className={classNames('c-kd-multiple-select', theme['c-kd-multiple-select'])}>
        <MainButton
          theme={theme}
          label={this.mainButtonLabel()}
          active={this.state.optionsListVisible}
          onClick={this.toggleOptionsList}
        />
        {this.renderOptionsList()}
        {
          !emptyItems &&
            !isChickletNone &&
              chicklets.clearAll.visible &&
            <ClearAll theme={theme} onClick={onClearChicklets} />
        }
      </div>
    );
  }
}

KdDropDownMultiple.propTypes = propTypes;
KdDropDownMultiple.defaultProps = defaultProps;

export default wrap(KdDropDownMultiple);
