function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

import React from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash/cloneDeep';
import DualListArrows from './components/DualListArrows';
import DualListSelector from './components/DualListSelector';
import DualListHiddenSelect from './components/DualListHiddenSelect';
import { noop, debounce } from '../../common/helpers';
import { arrangeArray, isAllChildrenChecked, reverseAllItemsOrder, getItem, getUpdatedSelectCount, itemHasParent, itemHasChildren, toggleAllItems, isAllItemsChecked, isItemExistOnList, filterByHiding, getFilterredItemsLength, makeAllHiddenFilteredItemsVisible, getSelectedFilterredItemsLength, isItemSelected, getFilterredItems } from './helpers';

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

    _defineProperty(this, "onItemChange", ({
      target: {
        checked,
        dataset: {
          position,
          side,
          parentPosition
        }
      }
    }) => {
      const {
        [side]: {
          selectCount: originalSelectCount,
          items: originalItems,
          isSortAsc,
          filterTerm
        }
      } = this.props;
      const items = cloneDeep(originalItems);
      const item = getItem(isSortAsc, items, position, parentPosition);
      let selectCount = originalSelectCount;
      item.checked = checked;

      if (itemHasParent(item)) {
        const parent = getItem(isSortAsc, items, parentPosition);
        parent.checked = isAllChildrenChecked(parent);
        selectCount = getUpdatedSelectCount(selectCount, checked);
      } else if (itemHasChildren(item)) {
        const {
          children
        } = item;
        toggleAllItems(children, checked);
        selectCount = getUpdatedSelectCount(selectCount, checked, children.length);
      } else {
        selectCount = getUpdatedSelectCount(selectCount, checked);
      }

      let isMainChecked = false;

      if (filterTerm) {
        const filteredItemsLength = getFilterredItemsLength(items);
        const selectedFilteredItemsLength = getSelectedFilterredItemsLength(items);
        isMainChecked = filteredItemsLength > 0 && selectedFilteredItemsLength === filteredItemsLength;
      } else {
        isMainChecked = isAllItemsChecked(items, selectCount);
      }

      this.props.onItemChange({
        side,
        items,
        selectCount,
        isMainChecked
      });
    });

    _defineProperty(this, "onMainCheckboxChange", ({
      target: {
        checked,
        dataset: {
          side
        }
      }
    }) => {
      const {
        [side]: {
          items: originalItems,
          filterTerm,
          selectCount: originalSelectCount
        }
      } = this.props;
      const items = cloneDeep(originalItems);
      let selectCount = originalSelectCount;

      if (filterTerm) {
        const filterredItems = getFilterredItems(items);
        const toggledAmount = toggleAllItems(filterredItems, checked);
        selectCount += toggledAmount * (checked ? 1 : -1);
      } else {
        const toggledAmount = toggleAllItems(items, checked);
        selectCount = checked ? selectCount + toggledAmount : 0;
      }

      this.props.onMainCheckboxChange({
        side,
        checked,
        items,
        selectCount
      });
    });

    _defineProperty(this, "onSortClick", ({
      target: {
        dataset: {
          side
        }
      }
    }) => {
      const {
        [side]: {
          items,
          isSortAsc
        }
      } = this.props;
      const itemsReversed = reverseAllItemsOrder(items);
      this.props.onSortClick({
        side,
        items: itemsReversed,
        isSortAsc: !isSortAsc
      });
    });

    _defineProperty(this, "onFilterChange", event => {
      /** https://reactjs.org/docs/events.html#event-pooling */
      event.persist();
      this.onFilterChangeDebounced(event);
    });

    _defineProperty(this, "emitFilterChange", ({
      target: {
        value,
        dataset: {
          side
        }
      }
    }) => {
      const {
        [side]: {
          items: originalItems,
          selectCount
        }
      } = this.props;
      const filterTerm = value.trim();

      if (!value) {
        const items = makeAllHiddenFilteredItemsVisible(originalItems);
        const isMainChecked = isAllItemsChecked(items, selectCount);
        this.props.onFilterChange({
          side,
          filterTerm,
          items,
          isMainChecked
        });
        return;
      }

      const items = filterByHiding(originalItems, filterTerm);
      const filteredItemsLength = getFilterredItemsLength(items);
      const isMainChecked = filteredItemsLength > 0 && getSelectedFilterredItemsLength(items) === filteredItemsLength;
      this.props.onFilterChange({
        side,
        filterTerm,
        items,
        isMainChecked
      });
    });

    _defineProperty(this, "moveTo", otherSide => {
      const side = otherSide === 'right' ? 'left' : 'right';
      const sideState = this.props[side];
      const otherSideState = this.props[otherSide];
      const sideItemsWithRemainChildren = [];
      let otherSideItems = cloneDeep(otherSideState.items);
      let sideItems = sideState.items.filter(item => {
        if (isItemSelected(item)) {
          if (itemHasChildren(item)) {
            const {
              isParentExist,
              parentIndex
            } = isItemExistOnList(otherSideItems, item.label);

            if (isParentExist) {
              const {
                children
              } = otherSideItems[parentIndex];
              children.push(...item.children);
              return false;
            }
          }

          otherSideItems.push(item);
          return false;
        } else if (itemHasChildren(item)) {
          const selectedChildren = [];
          const unselectedChildren = [];
          item.children.forEach(childItem => {
            if (isItemSelected(childItem)) {
              selectedChildren.push(childItem);
            } else {
              unselectedChildren.push(childItem);
            }
          });

          if (selectedChildren.length > 0) {
            const {
              isParentExist,
              parentIndex
            } = isItemExistOnList(otherSideItems, item.label);

            if (isParentExist) {
              otherSideItems[parentIndex].children.push(...selectedChildren);
            } else {
              otherSideItems.push(_objectSpread({}, item, {
                checked: true,
                children: selectedChildren
              }));
            }

            if (unselectedChildren.length > 0) {
              sideItemsWithRemainChildren.push(_objectSpread({}, item, {
                children: unselectedChildren
              }));
            }

            return false;
          }
        }

        return true;
      });

      if (sideItemsWithRemainChildren.length > 0) {
        sideItems.push(...sideItemsWithRemainChildren);
      }

      const updatedSideState = _objectSpread({}, sideState, {
        selectCount: 0,
        isMainChecked: false
      });

      const updatedOtherSideState = _objectSpread({}, otherSideState, {
        selectCount: 0,
        isMainChecked: false
      });

      sideItems = arrangeArray(_objectSpread({}, updatedSideState, {
        items: sideItems
      }));
      otherSideItems = arrangeArray(_objectSpread({}, updatedOtherSideState, {
        items: otherSideItems,
        resetAllSelected: true
      }));
      this.props.onChange({
        [side]: _objectSpread({}, updatedSideState, {
          items: sideItems
        }),
        [otherSide]: _objectSpread({}, updatedOtherSideState, {
          items: otherSideItems
        })
      });
    });

    _defineProperty(this, "leftArrowClick", () => {
      const {
        arrows: {
          left
        }
      } = this.props;
      left.onClick();
      this.moveTo('left');
    });

    _defineProperty(this, "rightArrowClick", () => {
      const {
        arrows: {
          right
        }
      } = this.props;
      right.onClick();
      this.moveTo('right');
    });

    this.onFilterChangeDebounced = debounce(this.emitFilterChange, 200);
  }

  render() {
    const {
      left,
      right,
      arrows,
      allowHiddenInputs
    } = this.props;
    const hiddenInputs = allowHiddenInputs ? React.createElement(React.Fragment, null, React.createElement(DualListHiddenSelect, _extends({}, left.inputProps, {
      items: left.items
    })), React.createElement(DualListHiddenSelect, _extends({}, right.inputProps, {
      items: right.items
    }))) : null;
    return React.createElement("div", {
      className: "dual-list-pf"
    }, hiddenInputs, React.createElement(DualListSelector, _extends({
      side: "left"
    }, left, {
      onItemChange: this.onItemChange,
      onSortClick: this.onSortClick,
      onFilterChange: this.onFilterChange,
      onMainCheckboxChange: this.onMainCheckboxChange
    })), React.createElement(DualListArrows, {
      left: {
        onClick: this.leftArrowClick,
        ariaLabel: arrows.left.ariaLabel
      },
      right: {
        onClick: this.rightArrowClick,
        ariaLabel: arrows.right.ariaLabel
      }
    }), React.createElement(DualListSelector, _extends({
      side: "right"
    }, right, {
      onItemChange: this.onItemChange,
      onSortClick: this.onSortClick,
      onFilterChange: this.onFilterChange,
      onMainCheckboxChange: this.onMainCheckboxChange
    })));
  }

}

DualList.propTypes = {
  /**
   * - items: Array of objects that must contain a label and a value.
   * - options: The Kebab menu items.
   * - isSortAsc: Set the list items sorting direction.
   * - sortBy: set the list items sorting factor.
   * - isMainChecked: Set the main checkbox state.
   */
  left: PropTypes.shape({
    items: PropTypes.arrayOf(PropTypes.shape({
      label: PropTypes.string.isRequired,
      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
      children: PropTypes.arrayOf(PropTypes.shape({
        label: PropTypes.string,
        value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
      }))
    })),
    options: PropTypes.node,
    isSortAsc: PropTypes.bool,
    sortBy: PropTypes.string,
    isMainChecked: PropTypes.bool,
    inputProps: PropTypes.object
  }),

  /**
   * - items: Array of objects that must contain a label and a value.
   * - options: The Kebab menu items.
   * - isSortAsc: Set the list items sorting direction.
   * - isMainChecked: Set the main checkbox state.
   * - sortBy: set the list items sorting factor.
   */
  right: PropTypes.shape({
    items: PropTypes.arrayOf(PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
    })),
    options: PropTypes.node,
    isSortAsc: PropTypes.bool,
    sortBy: PropTypes.string,
    isMainChecked: PropTypes.bool,
    inputProps: PropTypes.object
  }),

  /**
   * - Conatains the left and right arrows properties,
   * - Where in every object there are:
   * - onClick - function which determine the onClick event,
   * - ariaLabel - set the aria-label text.
   */
  arrows: PropTypes.shape({
    left: PropTypes.shape({
      onClick: PropTypes.func,
      ariaLabel: PropTypes.string
    }),
    right: PropTypes.shape({
      onClick: PropTypes.func,
      ariaLabel: PropTypes.string
    })
  }),

  /**
   * Allows adding hidden select inputs
   * that reflects the dual list selected items.
   * It can be used while submitting a form to access the dual list data.
   * */
  allowHiddenInputs: PropTypes.bool,

  /**
   * Function that runs after an item was clicked.
   * receives an object of: { side, items, selectCount, isMainChecked } as a callback.
   */
  onItemChange: PropTypes.func,

  /**
   * Function that runs after the sort icon was clicked.
   * receives an object of: { side, items, isSortAsc } as a callback.
   */
  onSortClick: PropTypes.func,

  /**
   * Function that runs after the filter input has changed.
   * receives an object of: { side, filterTerm } as a callback.
   */
  onFilterChange: PropTypes.func,

  /**
   * Function that runs after the main checkbox was clicked.
   * receives an object of: { side, checked, items, selectCount } as a callback.
   */
  onMainCheckboxChange: PropTypes.func,

  /**
   * Function that runs after items have been moved between the lists.
   * receives an object of: { left, right } updated sides as a callback.
   */
  onChange: PropTypes.func
};
DualList.defaultProps = {
  left: {
    items: [],
    options: null,
    isSortAsc: true,
    sortBy: 'label',
    filterTerm: '',
    isMainChecked: false,
    inputProps: null
  },
  right: {
    items: [],
    options: null,
    isSortAsc: true,
    sortBy: 'label',
    filterTerm: '',
    isMainChecked: false,
    inputProps: null
  },
  arrows: {
    left: {
      onClick: noop,
      ariaLabel: null
    },
    right: {
      onClick: noop,
      ariaLabel: null
    }
  },
  allowHiddenInputs: false,
  onItemChange: noop,
  onSortClick: noop,
  onFilterChange: noop,
  onMainCheckboxChange: noop,
  onChange: noop
};
export default DualList;