'use strict';

var _assertThisInitialized = require('@babel/runtime/helpers/assertThisInitialized');
var _get = require('@babel/runtime/helpers/get');
var _getPrototypeOf = require('@babel/runtime/helpers/getPrototypeOf');
var _inherits = require('@babel/runtime/helpers/inherits');
var _createSuper = require('@babel/runtime/helpers/createSuper');
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _createClass = require('@babel/runtime/helpers/createClass');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var prosemirrorState = require('prosemirror-state');
require('react');
var jsxRuntime = require('react/jsx-runtime');

var ReactNodeViewState = /*#__PURE__*/function () {
  function ReactNodeViewState() {
    _classCallCheck(this, ReactNodeViewState);
    _defineProperty(this, "changeHandlers", []);
    this.changeHandlers = [];
  }
  _createClass(ReactNodeViewState, [{
    key: "subscribe",
    value: function subscribe(cb) {
      this.changeHandlers.push(cb);
    }
  }, {
    key: "unsubscribe",
    value: function unsubscribe(cb) {
      this.changeHandlers = this.changeHandlers.filter(function (ch) {
        return ch !== cb;
      });
    }
  }, {
    key: "notifyNewSelection",
    value: function notifyNewSelection(fromPos, toPos) {
      this.changeHandlers.forEach(function (cb) {
        return cb(fromPos, toPos);
      });
    }
  }]);
  return ReactNodeViewState;
}();
var stateKey = new prosemirrorState.PluginKey('reactNodeView');
var plugin = new prosemirrorState.Plugin({
  state: {
    init: function init() {
      return new ReactNodeViewState();
    },
    apply: function apply(_tr, pluginState) {
      return pluginState;
    }
  },
  key: stateKey,
  view: function view(_view) {
    var pluginState = stateKey.getState(_view.state);
    return {
      update: function update(view) {
        var _view$state$selection = view.state.selection,
          from = _view$state$selection.from,
          to = _view$state$selection.to;
        pluginState.notifyNewSelection(from, to);
      }
    };
  }
});

var ReactNodeView = /*#__PURE__*/function () {
  function ReactNodeView(_node, view, getPos, portalProviderAPI, reactComponentProps, reactComponent) {
    var _this = this;
    var hasContext = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
    var viewShouldUpdate = arguments.length > 7 ? arguments[7] : undefined;
    _classCallCheck(this, ReactNodeView);
    _defineProperty(this, "domRef", void 0);
    _defineProperty(this, "contentDOMWrapper", void 0);
    _defineProperty(this, "reactComponent", void 0);
    _defineProperty(this, "portalProviderAPI", void 0);
    _defineProperty(this, "hasContext", void 0);
    _defineProperty(this, "_viewShouldUpdate", void 0);
    _defineProperty(this, "reactComponentProps", void 0);
    _defineProperty(this, "view", void 0);
    _defineProperty(this, "getPos", void 0);
    _defineProperty(this, "contentDOM", void 0);
    _defineProperty(this, "node", void 0);
    _defineProperty(this, "handleRef", function (node) {
      return _this._handleRef(node);
    });
    this.node = _node;
    this.view = view;
    this.getPos = getPos;
    this.portalProviderAPI = portalProviderAPI;
    this.reactComponentProps = reactComponentProps || {};
    this.reactComponent = reactComponent;
    this.hasContext = hasContext;
    this._viewShouldUpdate = viewShouldUpdate;
  }

  /**
   * This method exists to move initialization logic out of the constructor,
   * so object can be initialized properly before calling render first time.
   *
   * Example:
   * Instance properties get added to an object only after super call in
   * constructor, which leads to some methods being undefined during the
   * first render.
   */
  _createClass(ReactNodeView, [{
    key: "init",
    value: function init() {
      var _this2 = this;
      this.domRef = this.createDomRef();
      this.setDomAttrs(this.node, this.domRef);
      var _ref = this.getContentDOM() || {
          dom: undefined,
          contentDOM: undefined
        },
        contentDOMWrapper = _ref.dom,
        contentDOM = _ref.contentDOM;
      if (this.domRef && contentDOMWrapper) {
        this.domRef.appendChild(contentDOMWrapper);
        this.contentDOM = contentDOM || contentDOMWrapper;
        this.contentDOMWrapper = contentDOMWrapper || contentDOM;
      }

      // @see ED-3790
      // something gets messed up during mutation processing inside of a
      // nodeView if DOM structure has nested plain "div"s, it doesn't see the
      // difference between them and it kills the nodeView
      this.domRef.classList.add("".concat(this.node.type.name, "View-content-wrap"));
      this.renderReactComponent(function () {
        return _this2.render(_this2.reactComponentProps, _this2.handleRef);
      });
      return this;
    }
  }, {
    key: "renderReactComponent",
    value: function renderReactComponent(component) {
      if (!this.domRef || !component) {
        return;
      }
      this.portalProviderAPI.render(component, this.domRef, this.hasContext);
    }
  }, {
    key: "createDomRef",
    value: function createDomRef() {
      return this.node.isInline ? document.createElement('span') : document.createElement('div');
    }
  }, {
    key: "getContentDOM",
    value: function getContentDOM() {
      return undefined;
    }
  }, {
    key: "_handleRef",
    value: function _handleRef(node) {
      var contentDOM = this.contentDOMWrapper || this.contentDOM;

      // move the contentDOM node inside the inner reference after rendering
      if (node && contentDOM && !node.contains(contentDOM)) {
        node.appendChild(contentDOM);
      }
    }
  }, {
    key: "render",
    value: function render(props, forwardRef) {
      return this.reactComponent ? /*#__PURE__*/jsxRuntime.jsx(this.reactComponent, _objectSpread({
        view: this.view,
        getPos: this.getPos,
        node: this.node,
        forwardRef: forwardRef
      }, props)) : null;
    }

    // @see https://github.com/ProseMirror/prosemirror-view/blob/670cf3d8c98db82e24feb851d01d22c19fb30bb3/src/viewdesc.js
  }, {
    key: "update",
    value: function update(node, _decorations, _innerDecorations) {
      var _this3 = this;
      var validUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
        return true;
      };
      var isValidUpdate = this.node.type === node.type && validUpdate(this.node, node);
      if (!isValidUpdate) {
        return false;
      }
      if (this.domRef && !this.node.sameMarkup(node)) {
        this.setDomAttrs(node, this.domRef);
      }

      // View should not process a re-render if this is false.
      // We dont want to destroy the view, so we return true.
      if (!this.viewShouldUpdate(node)) {
        this.node = node;
        return true;
      }
      this.node = node;
      this.renderReactComponent(function () {
        return _this3.render(_this3.reactComponentProps, _this3.handleRef);
      });
      return true;
    }
  }, {
    key: "viewShouldUpdate",
    value: function viewShouldUpdate(nextNode) {
      if (this._viewShouldUpdate) {
        return this._viewShouldUpdate(nextNode);
      }
      return true;
    }

    /**
     * Copies the attributes from a ProseMirror Node to a DOM node.
     * @param node The Prosemirror Node from which to source the attributes
     */
  }, {
    key: "setDomAttrs",
    value: function setDomAttrs(node, element) {
      Object.keys(node.attrs || {}).forEach(function (attr) {
        element.setAttribute(attr, node.attrs[attr]);
      });
    }
  }, {
    key: "dom",
    get: function get() {
      return this.domRef;
    }
  }, {
    key: "destroy",
    value: function destroy() {
      if (!this.domRef) {
        return;
      }
      this.portalProviderAPI.remove(this.domRef);
      this.domRef = undefined;
      this.contentDOM = undefined;
    }
  }], [{
    key: "fromComponent",
    value: function fromComponent(component, portalProviderAPI, props, viewShouldUpdate) {
      return function (node, view, getPos) {
        return new ReactNodeView(node, view, getPos, portalProviderAPI, props, component, false, viewShouldUpdate).init();
      };
    }
  }]);
  return ReactNodeView;
}();
var SelectionBasedNodeView = /*#__PURE__*/function (_ReactNodeView) {
  _inherits(SelectionBasedNodeView, _ReactNodeView);
  var _super = _createSuper(SelectionBasedNodeView);
  function SelectionBasedNodeView(node, view, getPos, portalProviderAPI, reactComponentProps, reactComponent) {
    var _this4;
    var hasContext = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
    var viewShouldUpdate = arguments.length > 7 ? arguments[7] : undefined;
    _classCallCheck(this, SelectionBasedNodeView);
    _this4 = _super.call(this, node, view, getPos, portalProviderAPI, reactComponentProps, reactComponent, hasContext, viewShouldUpdate);
    _defineProperty(_assertThisInitialized(_this4), "oldSelection", void 0);
    _defineProperty(_assertThisInitialized(_this4), "selectionChangeState", void 0);
    _defineProperty(_assertThisInitialized(_this4), "pos", void 0);
    _defineProperty(_assertThisInitialized(_this4), "posEnd", void 0);
    _defineProperty(_assertThisInitialized(_this4), "isSelectionInsideNode", function (from, to, pos, posEnd) {
      pos = typeof pos !== 'number' ? _this4.pos : pos;
      posEnd = typeof posEnd !== 'number' ? _this4.posEnd : posEnd;
      if (typeof pos !== 'number' || typeof posEnd !== 'number') {
        return false;
      }
      return pos < from && to < posEnd;
    });
    _defineProperty(_assertThisInitialized(_this4), "isSelectedNode", function (selection) {
      if (selection instanceof prosemirrorState.NodeSelection) {
        var _this4$view$state$sel = _this4.view.state.selection,
          from = _this4$view$state$sel.from,
          to = _this4$view$state$sel.to;
        return selection.node === _this4.node ||
        // If nodes are not the same object, we check if they are referring to the same document node
        _this4.pos === from && _this4.posEnd === to && selection.node.eq(_this4.node);
      }
      return false;
    });
    _defineProperty(_assertThisInitialized(_this4), "insideSelection", function () {
      var _this4$view$state$sel2 = _this4.view.state.selection,
        from = _this4$view$state$sel2.from,
        to = _this4$view$state$sel2.to;
      return _this4.isSelectedNode(_this4.view.state.selection) || _this4.isSelectionInsideNode(from, to);
    });
    _defineProperty(_assertThisInitialized(_this4), "onSelectionChange", function () {
      _this4.update(_this4.node, []);
    });
    _this4.updatePos();
    _this4.oldSelection = view.state.selection;
    _this4.selectionChangeState = stateKey.getState(_this4.view.state);
    _this4.selectionChangeState.subscribe(_this4.onSelectionChange);
    return _this4;
  }

  /**
   * Update current node's start and end positions.
   *
   * Prefer `this.pos` rather than getPos(), because calling getPos is
   * expensive, unless you know you're definitely going to render.
   */
  _createClass(SelectionBasedNodeView, [{
    key: "updatePos",
    value: function updatePos() {
      if (typeof this.getPos === 'boolean') {
        return;
      }
      this.pos = this.getPos();
      this.posEnd = this.pos + this.node.nodeSize;
    }
  }, {
    key: "viewShouldUpdate",
    value: function viewShouldUpdate(_nextNode) {
      var selection = this.view.state.selection;

      // update selection
      var oldSelection = this.oldSelection;
      this.oldSelection = selection;

      // update cached positions
      var oldPos = this.pos,
        oldPosEnd = this.posEnd;
      this.updatePos();
      var from = selection.from,
        to = selection.to;
      var oldFrom = oldSelection.from,
        oldTo = oldSelection.to;
      if (this.node.type.spec.selectable) {
        var newNodeSelection = selection instanceof prosemirrorState.NodeSelection && selection.from === this.pos;
        var oldNodeSelection = oldSelection instanceof prosemirrorState.NodeSelection && oldSelection.from === this.pos;
        if (newNodeSelection && !oldNodeSelection || oldNodeSelection && !newNodeSelection) {
          return true;
        }
      }
      var movedInToSelection = this.isSelectionInsideNode(from, to) && !this.isSelectionInsideNode(oldFrom, oldTo);
      var movedOutOfSelection = !this.isSelectionInsideNode(from, to) && this.isSelectionInsideNode(oldFrom, oldTo);
      var moveOutFromOldSelection = this.isSelectionInsideNode(from, to, oldPos, oldPosEnd) && !this.isSelectionInsideNode(from, to);
      if (movedInToSelection || movedOutOfSelection || moveOutFromOldSelection) {
        return true;
      }
      return false;
    }
  }, {
    key: "destroy",
    value: function destroy() {
      this.selectionChangeState.unsubscribe(this.onSelectionChange);
      _get(_getPrototypeOf(SelectionBasedNodeView.prototype), "destroy", this).call(this);
    }
  }]);
  return SelectionBasedNodeView;
}(ReactNodeView);

exports.ReactNodeView = ReactNodeView;
exports.SelectionBasedNodeView = SelectionBasedNodeView;
exports.plugin = plugin;
exports.stateKey = stateKey;
