import _extends from 'babel-runtime/helpers/extends';
import _Object$getPrototypeOf from 'babel-runtime/core-js/object/get-prototype-of';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _createClass from 'babel-runtime/helpers/createClass';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
import React from 'react';
import PropTypes from 'prop-types';

import KeyCode from './consts';
import { moveItem } from './utils';
import { getDisplayName, isEdge } from './utils';

var SCROLL_RANGE = 150;
var SCROLL_ACC_PX = 10;

/* eslint-disable no-restricted-globals */

var DragNDropContainer = function DragNDropContainer(WrappedComponent) {
  var Wrapper = function (_React$Component) {
    _inherits(Wrapper, _React$Component);

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

      var _this = _possibleConstructorReturn(this, (Wrapper.__proto__ || _Object$getPrototypeOf(Wrapper)).call(this, props));

      _this.state = {
        isDragging: false,
        sourceIndex: -1,
        lastOverIndex: -1, // as drag over does not happen sequentially, we need to store last over index and render it when new element is being dragged over
        overIndex: -1,
        isKeyboardMoving: false,
        curPreview: '',
        keyInsertIndex: -1
      };

      _this.setClientY = function (e) {
        _this.clientY = e.clientY;
      };

      _this.leaveKeyboardMoving = function () {
        if (_this.state.isKeyboardMoving) {
          _this.setState({
            isDragging: false,
            isKeyboardMoving: false,
            sourceIndex: -1,
            keyInsertIndex: -1
          });
        }
      };

      _this.onDrag = function (e, dragPreviewRef) {
        // position move is out of control of react render, so we use id instead of ref
        if (_this.props.boundingElementId && !isEdge()) {
          // In some browsers (e.g. Chrome, Safari) the preview item does not get fixed position if an ancestor element uses translateZ
          // See: https://stackoverflow.com/questions/15194313/transform3d-not-working-with-position-fixed-children
          // boundingElementId represents an alternate ancestor/container element that can be used to calculate the position while dragging
          dragPreviewRef.style.top = _this.clientY - document.getElementById(_this.props.boundingElementId).getBoundingClientRect().top + 'px'; // eslint-disable-line
        } else {
          dragPreviewRef.style.top = _this.clientY + 'px'; // eslint-disable-line no-param-reassign
        }

        // increase scroll area
        if (_this.clientY < SCROLL_RANGE) {
          if (_this.scrollContainer) {
            _this.scrollContainer.scrollTop -= SCROLL_ACC_PX;
          } else {
            scrollBy(0, 0 - SCROLL_ACC_PX);
          }
        } else if (innerHeight - _this.clientY < SCROLL_RANGE) {
          if (_this.scrollContainer) {
            _this.scrollContainer.scrollTop += SCROLL_ACC_PX;
          } else {
            scrollBy(0, SCROLL_ACC_PX);
          }
        }
      };

      _this.onDragStart = function (e, index) {
        /**
         * Updating state causes the dnd item to re-render thus manipulating the DOM. In some cases
         * this causes dragend event to fire immediately. To prevent this wrap in a timeout.
         *
         * See https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
         */
        setTimeout(function () {
          _this.setState({
            isDragging: true,
            isKeyboardMoving: false,
            sourceIndex: index,
            overIndex: -1
          });
        });
      };

      _this.onDragEnd = function (e) {
        if (_this.containerRef) {
          e.preventDefault();
          _this.setState({
            isDragging: false,
            isKeyboardMoving: false,
            sourceIndex: -1,
            overIndex: -1
          });
        }
      };

      _this.onDrop = function (e, targetIndex) {
        var items = _this.props.items;

        e.preventDefault();
        var newOrderItems = moveItem(items, targetIndex, _this.state.sourceIndex);
        var sourceDragItem = items[_this.state.sourceIndex];
        _this.props.onReorderItem(newOrderItems, sourceDragItem);
      };

      _this.onDragOver = function (e, index) {
        e.preventDefault();
        _this.setState({
          lastOverIndex: index,
          overIndex: index
        });
      };

      _this.onDragLeave = function (e, index) {
        e.preventDefault();
        _this.setState({
          lastOverIndex: index,
          overIndex: -1
        });
      };

      _this.onTouchMove = function (e, dragPreviewRef) {
        var touchPoint = e.touches[0];
        var dropZone = document.elementFromPoint(touchPoint.clientX, touchPoint.clientY);
        var position = dropZone.dataset.position;

        if (position) {
          _this.setState({
            overIndex: parseInt(position, 10)
          });
        } else {
          _this.onDragLeave(e);
        }

        // position move is out of control of react render, so we use id instead of ref
        dragPreviewRef.style.top = touchPoint.clientY + 'px'; // eslint-disable-line no-param-reassign

        // increase scroll area
        if (touchPoint.clientY < SCROLL_RANGE) {
          if (_this.scrollContainer) {
            _this.scrollContainer.scrollTop -= SCROLL_ACC_PX;
          } else {
            scrollBy(0, 0 - SCROLL_ACC_PX);
          }
        } else if (innerHeight - touchPoint.clientY < SCROLL_RANGE) {
          if (_this.scrollContainer) {
            _this.scrollContainer.scrollTop += SCROLL_ACC_PX;
          } else {
            scrollBy(0, SCROLL_ACC_PX);
          }
        }
      };

      _this.onTouchDrop = function (e) {
        var touchPoint = e.changedTouches[0];
        var dropZone = document.elementFromPoint(touchPoint.clientX, touchPoint.clientY);
        var position = dropZone.dataset.position;
        if (position) {
          _this.onDrop(e, parseInt(position, 10), 0);
          _this.onDragEnd(e);
        } else {
          _this.onDragEnd(e);
        }
      };

      _this.onClickDrag = function (e, index, preview) {
        e.stopPropagation(); // as binded a event on window to reset isKeyboardMoving state, need to stop propagation here to avoid mis-triggering it
        _this.setState({
          isDragging: false,
          isKeyboardMoving: true,
          sourceIndex: index,
          keyInsertIndex: index + 1,
          curPreview: preview
        });
      };

      _this.onKeyChangeOrder = function (e) {
        var items = _this.props.items;

        if (_this.state.isKeyboardMoving) {
          e.preventDefault();
          e.stopPropagation();
          switch (e.keyCode) {
            case KeyCode.ESC:
              {
                _this.leaveKeyboardMoving();
                break;
              }
            case KeyCode.ENTER:
              {
                var newOrderItems = moveItem(items, _this.state.keyInsertIndex, _this.state.sourceIndex);
                var sourceDragItem = items[_this.state.sourceIndex];
                _this.props.onReorderItem(newOrderItems, sourceDragItem);
                _this.setState({
                  isKeyboardMoving: false,
                  sourceIndex: -1,
                  keyInsertIndex: -1,
                  curPreview: ''
                });
                break;
              }
            case KeyCode.ARROW_UP:
              {
                // skip displaying of item which is above the source cause its insert place holder should never be displayed
                if (_this.state.keyInsertIndex - 1 === _this.state.sourceIndex) {
                  if (_this.state.keyInsertIndex - 2 >= 0) {
                    _this.setState({ keyInsertIndex: _this.state.keyInsertIndex - 2 });
                  }
                } else if (_this.state.keyInsertIndex - 1 >= 0) {
                  _this.setState({ keyInsertIndex: _this.state.keyInsertIndex - 1 });
                }
                break;
              }
            case KeyCode.ARROW_DOWN:
              {
                // skip displaying of item which is above the source cause its insert place holder should never be displayed
                if (_this.state.keyInsertIndex + 1 === _this.state.sourceIndex) {
                  if (_this.state.keyInsertIndex + 2 <= items.length) {
                    _this.setState({ keyInsertIndex: _this.state.keyInsertIndex + 2 });
                  }
                } else if (_this.state.keyInsertIndex + 1 <= items.length) {
                  _this.setState({ keyInsertIndex: _this.state.keyInsertIndex + 1 });
                }
                break;
              }
            default:
              break;
          }
        }
      };

      _this.actions = {
        onDrag: _this.onDrag,
        onDragStart: _this.onDragStart,
        onDragEnd: _this.onDragEnd,
        leaveKeyboardMoving: _this.leaveKeyboardMoving,
        onDrop: _this.onDrop,
        onDragOver: _this.onDragOver,
        onDragLeave: _this.onDragLeave,
        onTouchMove: _this.onTouchMove,
        onTouchDrop: _this.onTouchDrop,
        onClickDrag: _this.onClickDrag,
        onKeyChangeOrder: _this.onKeyChangeOrder
      };

      _this.clientY = 0;
      return _this;
    }

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

        // to retrieve clientY in FF
        // https://stackoverflow.com/questions/887316/how-do-i-get-clientx-and-clienty-to-work-inside-my-drag-event-handler-on-firef
        window.addEventListener('dragover', this.setClientY);

        window.addEventListener('click', this.leaveKeyboardMoving);

        setTimeout(function () {
          _this2.scrollContainer = document.getElementById(_this2.props.scrollContainerId);
        }, 0);
      }
    }, {
      key: 'componentWillUnmount',
      value: function componentWillUnmount() {
        window.removeEventListener('dragover', this.setClientY);
        window.removeEventListener('click', this.leaveKeyboardMoving);
      }

      // we don't have to render the component for every onDragOver callback

    }, {
      key: 'shouldComponentUpdate',
      value: function shouldComponentUpdate(nextProps, nextState) {
        if (nextState.isDragging !== this.state.isDragging) {
          // deal with dragOver delay
          return true;
        }
        if (nextState.overIndex === this.state.overIndex && this.state.isDragging) {
          return false;
        }
        return true;
      }
    }, {
      key: 'render',
      value: function render() {
        var _this3 = this;

        return React.createElement(
          'div',
          { ref: function ref(_ref) {
              _this3.containerRef = _ref;
            } },
          React.createElement('div', { className: 'dnd-drag-placeholder' }),
          React.createElement(WrappedComponent, _extends({}, this.props, {
            state: this.state,
            actions: this.actions,
            keyInsertIndex: state.keyInsertIndex,
            isKeyboardMoving: state.isKeyboardMoving
          }))
        );
      }
    }]);

    return Wrapper;
  }(React.Component);

  Wrapper.propTypes = {
    items: PropTypes.arrayOf(PropTypes.object).isRequired,
    onReorderItem: PropTypes.func.isRequired,
    boundingElementId: PropTypes.string,
    scrollContainerId: PropTypes.string
  };

  Wrapper.displayName = 'DragNDropContainer(' + getDisplayName(WrappedComponent) + ')';

  return Wrapper;
};

export default DragNDropContainer;