'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _lodash = require('lodash');

var _styledComponents = require('styled-components');

var _styledComponents2 = _interopRequireDefault(_styledComponents);

var _MultiSelectGridItem = require('./MultiSelectGridItem');

var _MultiSelectGridItem2 = _interopRequireDefault(_MultiSelectGridItem);

var _SelectBox = require('./SelectBox');

var _SelectBox2 = _interopRequireDefault(_SelectBox);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

// TODO: update this to use CSS grid when it's ready
var Grid = _styledComponents2.default.div.withConfig({
  displayName: 'MultiSelectGrid__Grid'
})(['display: flex;flex-direction: row;flex-wrap: wrap;padding: 1em;user-select: none;position: relative;cursor: crosshair;']);

var boxContains = function boxContains(containingBox, candidateBox) {
  var percentTolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;

  var xTolerance = percentTolerance * 0.01 * candidateBox.width;
  var yTolerance = percentTolerance * 0.01 * candidateBox.height;
  return candidateBox.left + xTolerance >= containingBox.left && candidateBox.right - xTolerance <= containingBox.right && candidateBox.top + yTolerance >= containingBox.top && candidateBox.bottom - yTolerance <= containingBox.bottom;
};

var MultiSelectGrid = function (_React$Component) {
  _inherits(MultiSelectGrid, _React$Component);

  function MultiSelectGrid(props) {
    _classCallCheck(this, MultiSelectGrid);

    var _this = _possibleConstructorReturn(this, (MultiSelectGrid.__proto__ || Object.getPrototypeOf(MultiSelectGrid)).call(this, props));

    _this.wrappedChildren = function () {
      var children = _this.props.children;
      var _this$state = _this.state,
          selectedIndexes = _this$state.selectedIndexes,
          pendingSelectedIndexes = _this$state.pendingSelectedIndexes;


      _this._items = [];

      return _react2.default.Children.map(children, function (child, idx) {
        return _react2.default.createElement(
          _MultiSelectGridItem2.default,
          {
            key: idx,
            innerRef: function innerRef(el) {
              _this._items[idx] = el;
            },
            active: selectedIndexes.includes(idx),
            pendingToggle: pendingSelectedIndexes.includes(idx)
          },
          child
        );
      });
    };

    _this.toggleIndividualItem = function (idx) {
      var selectedIndexes = _this.state.selectedIndexes;

      _this.setState({ selectedIndexes: (0, _lodash.xor)(selectedIndexes, [idx]) });
    };

    _this.calcElementsWithinBounds = function (elements, bounds) {
      var percentTolerance = _this.props.percentTolerance;


      return elements.reduce(function (result, element, idx) {
        var box = element.getBoundingClientRect();
        // if selection contains item within tolerance, OR item contains whole selection
        if (boxContains(bounds, box, percentTolerance) || boxContains(box, bounds)) {
          result.push(idx);
        }
        return result;
      }, []);
    };

    _this.handleMouseDown = function (event) {
      _this.setState({
        selectStartLocation: {
          x: event.clientX,
          y: event.clientY
        },
        isSelecting: true
      });

      window.document.addEventListener('mousemove', _this.handleMouseMove);
      window.document.addEventListener('mouseup', _this.handleMouseUp);
    };

    _this.handleMouseUp = function (event) {
      window.document.removeEventListener('mousemove', _this.handleMouseMove);
      window.document.removeEventListener('mouseup', _this.handleMouseUp);
      // calculate selected items, forcing a new pending selected items calculation first
      var selectBox = _this.recalculateSelectionBox(event.clientX, event.clientY);
      var pendingSelectedIndexes = _this.calcElementsWithinBounds(_this._items, selectBox);
      var selectedIndexes = (0, _lodash.xor)(pendingSelectedIndexes, _this.state.selectedIndexes);

      _this.setState({
        isSelecting: false,
        selectBox: {
          left: Number.MAX_SAFE_INTEGER,
          bottom: Number.MAX_SAFE_INTEGER,
          right: Number.MIN_SAFE_INTEGER,
          top: Number.MIN_SAFE_INTEGER
        },
        selectedIndexes: selectedIndexes,
        pendingSelectedIndexes: []
      });

      if (_this.props.onSelectionChanged) {
        _this.props.onSelectionChanged(selectedIndexes);
      }
    };

    _this.handleMouseMove = function (event) {
      if (!_this.state.isSelecting) {
        return;
      }
      // calc new min/max
      var selectBox = _this.recalculateSelectionBox(event.clientX, event.clientY);
      _this.setState({ selectBox: selectBox });
    };

    _this.recalculateSelectionBox = function (mouseX, mouseY) {
      var selectStartLocation = _this.state.selectStartLocation;


      var left = Math.min(mouseX, selectStartLocation.x);
      var top = Math.min(mouseY, selectStartLocation.y);
      var bottom = Math.max(mouseY, selectStartLocation.y);
      var right = Math.max(mouseX, selectStartLocation.x);
      return { left: left, right: right, top: top, bottom: bottom, x: left, y: top, width: Math.abs(right - left), height: Math.abs(bottom - top) };
    };

    _this.recalculatePendingSelection = (0, _lodash.debounce)(function () {
      var _items = _this._items;
      var selectBox = _this.state.selectBox;

      var pendingSelectedIndexes = _this.calcElementsWithinBounds(_items, selectBox);
      _this.setState({ pendingSelectedIndexes: pendingSelectedIndexes });
    }, 100);

    _this.boxToCSSPosition = function (box) {
      var gridBounds = _this._grid.getBoundingClientRect();
      return {
        bottom: gridBounds.height - (box.bottom - gridBounds.top),
        left: box.left - gridBounds.left,
        top: box.top - gridBounds.top,
        right: gridBounds.width - (box.right - gridBounds.left)
      };
    };

    _this.state = {
      selectedIndexes: [],
      pendingSelectedIndexes: [],
      selectBox: {
        left: Number.MAX_SAFE_INTEGER,
        bottom: Number.MAX_SAFE_INTEGER,
        right: Number.MIN_SAFE_INTEGER,
        top: Number.MIN_SAFE_INTEGER
      },
      isSelecting: false,
      selectStartLocation: {
        x: 0,
        y: 0
      }
    };
    return _this;
  }

  _createClass(MultiSelectGrid, [{
    key: 'render',
    value: function render() {
      var _this2 = this;

      var _state = this.state,
          isSelecting = _state.isSelecting,
          selectBox = _state.selectBox;


      return _react2.default.createElement(
        Grid,
        {
          onMouseDownCapture: this.handleMouseDown,
          innerRef: function innerRef(el) {
            _this2._grid = el;
          }
        },
        this.wrappedChildren(),
        isSelecting ? _react2.default.createElement(_SelectBox2.default, { style: this.boxToCSSPosition(selectBox) }) : null
      );
    }
  }]);

  return MultiSelectGrid;
}(_react2.default.Component);

MultiSelectGrid.propTypes = {
  children: _propTypes2.default.node,
  onSelectionChanged: _propTypes2.default.func,
  percentTolerance: _propTypes2.default.number
};
MultiSelectGrid.defaultProps = {
  children: null,
  percentTolerance: 25,
  onSelectionChanged: function onSelectionChanged() {} // no-op
};


MultiSelectGrid.usage = '\n# MultiSelectGrid\n\nExperimental Grid technology. Allows multiple selections of items and box select. Currently very non-performant.\n\nI don\'t think this was very well-designed. I\'d probably rewrite it if I needed to use it.\n\n```\n<MultiSelectGrid onSelectionChanged={this.handleSelectionChanged}>\n  <div>one</div>\n  <div>two</div>\n  <div>three</div>\n</MultiSelectGrid>\n```\n';

exports.default = MultiSelectGrid;