import React, { PureComponent } from "react";
import {
  getSelectedByAllItems,
  filterUnselectedByIds,
  findItem,
  getMinMaxIndexes,
  isWithin,
  getNewSelectedItems
} from "./multi_select_state_utils";

const withMultiSelectState = WrappedComponent =>
  class extends PureComponent {
    static defaultProps = {
      filterFunction: value => item =>
        String(item.label)
          .toLowerCase()
          .includes(value.toLowerCase()),
      items: [],
      selectedItems: [],
      isLocked: item => item.disabled
    };

    constructor(props) {
      super(props);
      this.selectItem = this.selectItem.bind(this);
      this.unselectItems = this.unselectItems.bind(this);
      this.filterItems = this.filterItems.bind(this);
      this.selectAllItems = this.selectAllItems.bind(this);
      this.isAllSelected = this.isAllSelected.bind(this);
      this.clearAll = this.clearAll.bind(this);
      this.handleChange = this.handleChange.bind(this);
      this.getList = this.getList.bind(this);
      this.onKeyUp = this.onKeyUp.bind(this);
      this.filterSelectedItems = this.filterSelectedItems.bind(this);
      this.getNewFilteredSelectedItems = this.getNewFilteredSelectedItems.bind(
        this
      );
      const { items, selectedItems } = props;
      this.selectedItemsFilterFunction =
        props.selectedItemsFilterFunction || props.filterFunction;
      this.state = {
        selectedItems,
        items,
        filteredItems: items,
        filteredSelectedItems: selectedItems
      };
    }

    componentWillReceiveProps(nextProps) {
      const { searchValue, searchSelectedItemsValue } = this.props;
      if (this.props.selectedItems !== nextProps.selectedItems) {
        this.setState({
          selectedItems: nextProps.selectedItems,
          filteredSelectedItems: nextProps.selectedItems
        });
      }
      if (this.props.items !== nextProps.items) {
        const { items } = nextProps;
        this.setState({ items, filteredItems: items });
      }
      if (searchValue !== nextProps.searchValue) {
        this.filterItems({ target: { value: nextProps.searchValue } });
      }
      if (searchSelectedItemsValue !== nextProps.searchSelectedItemsValue) {
        this.filterSelectedItems({
          target: { value: nextProps.searchSelectedItemsValue }
        });
      }
    }

    handleMultiSelection(index) {
      const { items, isLocked } = this.props;
      const { filteredItems, firstItemShiftSelected } = this.state;

      const interval = getMinMaxIndexes(index, firstItemShiftSelected);
      const newSelectedItems = items.filter(
        (item, index) =>
          (isWithin(index, interval) &&
            !isLocked(item) &&
            findItem(item, filteredItems)) ||
          findItem(item, this.state.selectedItems)
      );
      const newFilteredSelectedItems = this.getNewFilteredSelectedItems(
        newSelectedItems
      );
      this.setState(
        {
          selectedItems: newSelectedItems,
          filteredSelectedItems: newFilteredSelectedItems
        },
        this.handleChange
      );
    }

    getNewFilteredSelectedItems(items) {
      const { searchSelectedItemsValue } = this.props;

      return searchSelectedItemsValue
        ? items.filter(
            this.selectedItemsFilterFunction(searchSelectedItemsValue)
          )
        : items;
    }
    componentDidMount() {
      window.addEventListener("keyup", this.onKeyUp);
    }
    componentWillUnmount() {
      window.removeEventListener("keyup", this.onKeyUp, false);
    }

    onKeyUp(event) {
      if (event.keyCode === 16) {
        this.setState({ firstItemShiftSelected: undefined });
      }
    }

    selectItem(event, id) {
      const { items } = this.props;
      const { selectedItems, firstItemShiftSelected } = this.state;
      if (!selectedItems.find(item => item.id === id)) {
        if (event.shiftKey && firstItemShiftSelected !== undefined) {
          this.handleMultiSelection(items.findIndex(item => item.id === id));
        } else {
          if (event.shiftKey && firstItemShiftSelected === undefined) {
            const index = items.findIndex(item => item.id === id);
            this.setState({ firstItemShiftSelected: index });
          }
          this.setNewItemsBySelectItem(id, items, selectedItems);
        }
      } else {
        this.unselectItems([id]);
      }
    }

    setNewItemsBySelectItem(id, items, selectedItems) {
      const newSelectedItems = getNewSelectedItems(id, items, selectedItems);
      const newFilteredSelectedItems = this.getNewFilteredSelectedItems(
        newSelectedItems
      );
      this.setState(
        {
          selectedItems: newSelectedItems,
          filteredSelectedItems: newFilteredSelectedItems
        },
        this.handleChange
      );
    }
    unselectItems(ids) {
      const { selectedItems, filteredSelectedItems } = this.state;
      const { isLocked } = this.props;
      const newSelectedItems = filterUnselectedByIds(
        selectedItems,
        ids,
        isLocked
      );
      const newFilteredSelectedItems = filterUnselectedByIds(
        filteredSelectedItems,
        ids,
        isLocked
      );
      this.setState(
        {
          selectedItems: newSelectedItems,
          filteredSelectedItems: newFilteredSelectedItems
        },
        this.handleChange
      );
    }

    clearAll() {
      const { selectedItems, isLocked } = this.props;
      const lockedItems = selectedItems.filter(isLocked);
      this.setState(
        {
          selectedItems: lockedItems,
          filteredSelectedItems: lockedItems
        },
        this.handleChange
      );
    }

    filterItems(event) {
      const { items, filterFunction, searchValueChanged } = this.props;
      const { value } = event.target;
      this.setState({ filteredItems: items.filter(filterFunction(value)) });
      searchValueChanged && searchValueChanged(value);
    }

    filterSelectedItems(event) {
      const { searchSelectedItemsChanged } = this.props;
      const { value } = event.target;
      const { selectedItems } = this.state;
      this.setState({
        filteredSelectedItems: selectedItems.filter(
          this.selectedItemsFilterFunction(value)
        )
      });
      searchSelectedItemsChanged && searchSelectedItemsChanged(value);
    }

    selectAllItems() {
      const { filteredItems, selectedItems } = this.state;
      const { items } = this.props;
      if (this.isAllSelected()) {
        this.unselectItems(filteredItems.map(filteredItem => filteredItem.id));
      } else {
        const newSelectItems = getSelectedByAllItems(
          filteredItems,
          selectedItems,
          items
        );
        const newFilteredSelectedItems = this.getNewFilteredSelectedItems(
          newSelectItems
        );
        this.setState(
          {
            selectedItems: newSelectItems,
            filteredSelectedItems: newFilteredSelectedItems
          },
          this.handleChange
        );
      }
    }

    isAllSelected() {
      const { filteredItems, selectedItems } = this.state;
      const { isLocked } = this.props;
      const selectedItemsInFilteredItems = selectedItems.filter(
        selectedItem =>
          !isLocked(selectedItem) && findItem(selectedItem, filteredItems)
      );
      const selectableFilteredItems = filteredItems.filter(
        item => !isLocked(item)
      );
      return (
        selectedItemsInFilteredItems.length ===
          selectableFilteredItems.length && selectableFilteredItems.length > 0
      );
    }

    handleChange() {
      const { onChange } = this.props;
      const { selectedItems } = this.state;
      this.list && this.list.update();
      onChange && onChange(selectedItems);
    }

    getList(ref) {
      this.list = ref;
    }

    render() {
      return (
        <WrappedComponent
          {...this.props}
          {...this.state}
          unselectItems={this.unselectItems}
          selectItem={this.selectItem}
          filterItems={this.filterItems}
          selectAllItems={this.selectAllItems}
          isAllSelected={this.isAllSelected()}
          clearAll={this.clearAll}
          getList={this.getList}
          filterSelectedItems={this.filterSelectedItems}
        />
      );
    }
  };

export default withMultiSelectState;
