"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.shouldSplitSelectedNodeOnNodeInsertion = exports.safeInsert = exports.normaliseNestedLayout = exports.insertSelectedItem = exports.LookDirection = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _model = require("@atlaskit/editor-prosemirror/model");
var _state = require("@atlaskit/editor-prosemirror/state");
var _transform = require("@atlaskit/editor-prosemirror/transform");
var _utils = require("@atlaskit/editor-prosemirror/utils");
var _selection = require("../selection");
var _utils2 = require("../utils");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var LookDirection = exports.LookDirection = /*#__PURE__*/function (LookDirection) {
  LookDirection["Before"] = "before";
  LookDirection["After"] = "after";
  return LookDirection;
}({});
var normaliseNestedLayout = exports.normaliseNestedLayout = function normaliseNestedLayout(_ref, node) {
  var selection = _ref.selection,
    doc = _ref.doc;
  if (selection.$from.depth > 1) {
    if (node.attrs.layout && node.attrs.layout !== 'default') {
      return node.type.createChecked(_objectSpread(_objectSpread({}, node.attrs), {}, {
        layout: 'default'
      }), node.content, node.marks);
    }

    // If its a breakout layout, we can remove the mark
    // Since default isn't a valid breakout mode.
    var breakoutMark = doc.type.schema.marks.breakout;
    if (breakoutMark && breakoutMark.isInSet(node.marks)) {
      var newMarks = breakoutMark.removeFromSet(node.marks);
      return node.type.createChecked(node.attrs, node.content, newMarks);
    }
  }
  return node;
};
var isLastChild = function isLastChild($pos, doc) {
  return doc.resolve($pos.after()).node().lastChild === $pos.node();
};
var isFirstChild = function isFirstChild($pos, doc) {
  return doc.resolve($pos.before()).node().firstChild === $pos.node();
};
var nodeIsInsideAList = function nodeIsInsideAList(tr) {
  var nodes = tr.doc.type.schema.nodes;
  return (0, _utils.hasParentNodeOfType)([nodes.orderedList, nodes.bulletList])(tr.selection);
};
var selectionIsInsideAPanel = function selectionIsInsideAPanel(tr) {
  var nodes = tr.doc.type.schema.nodes;
  return (0, _utils.hasParentNodeOfType)(nodes.panel)(tr.selection);
};
var selectionIsInNestedList = function selectionIsInNestedList(tr) {
  var nodes = tr.doc.type.schema.nodes;
  var parentListNode = (0, _utils.findParentNodeOfType)([nodes.orderedList, nodes.bulletList])(tr.selection);
  if (!parentListNode) {
    return false;
  }
  return (0, _utils2.isListItemNode)(tr.doc.resolve(parentListNode.pos).parent);
};
var insertBeforeOrAfter = function insertBeforeOrAfter(tr, lookDirection, $parentPos, $proposedPosition, content) {
  /**
   * This block caters for the first item in a parent with the cursor being at the very start
   * or the last item with the cursor being at the very end
   *
   * e.g.
   * ul
   *  li {<>}Scenario one
   *  li
   *  li Scenario two{<>}
   */

  if (isFirstChild($proposedPosition, tr.doc) && lookDirection === LookDirection.Before || isLastChild($proposedPosition, tr.doc) && lookDirection === LookDirection.After) {
    return tr.insert($parentPos[lookDirection](), content);
  }
  return tr.insert($proposedPosition[lookDirection](), content);
};

// FIXME: A more sustainable and configurable way to choose when to split
var shouldSplit = function shouldSplit(nodeType, schemaNodes) {
  return [schemaNodes.bulletList, schemaNodes.orderedList, schemaNodes.panel].includes(nodeType);
};
var safeInsert = exports.safeInsert = function safeInsert(content, position) {
  return function (tr) {
    var _tr$selection$$from$n;
    var nodes = tr.doc.type.schema.nodes;
    var whitelist = [nodes.rule, nodes.mediaSingle];
    if (content instanceof _model.Fragment || !whitelist.includes(content.type)) {
      return null;
    }

    // Check for selection
    if (!tr.selection.empty || (0, _utils.isNodeSelection)(tr.selection)) {
      // NOT IMPLEMENTED
      return null;
    }
    var $from = tr.selection.$from;
    var $insertPos = position ? tr.doc.resolve(position) : (0, _utils.isNodeSelection)(tr.selection) ? tr.doc.resolve($from.pos + 1) : $from;
    var lookDirection;
    var insertPosEnd = $insertPos.end();
    var insertPosStart = $insertPos.start();

    // When parent node is an empty paragraph,
    // check the empty paragraph is the first or last node of its parent.
    if ((0, _utils2.isEmptyParagraph)($insertPos.parent)) {
      if (isLastChild($insertPos, tr.doc)) {
        lookDirection = LookDirection.After;
      } else if (isFirstChild($insertPos, tr.doc)) {
        lookDirection = LookDirection.Before;
      }
    } else {
      if ($insertPos.pos === insertPosEnd) {
        lookDirection = LookDirection.After;
      } else if ($insertPos.pos === insertPosStart) {
        lookDirection = LookDirection.Before;
      }
    }
    var grandParentNodeType = (_tr$selection$$from$n = tr.selection.$from.node(-1)) === null || _tr$selection$$from$n === void 0 ? void 0 : _tr$selection$$from$n.type;
    var parentNodeType = tr.selection.$from.parent.type;

    // if there is no direction, and cannot split for this particular node
    var noDirectionAndShouldNotSplit = !lookDirection && !shouldSplitSelectedNodeOnNodeInsertion({
      parentNodeType: parentNodeType,
      grandParentNodeType: grandParentNodeType,
      content: content
    });
    var ruleNodeInANestedListNode = content.type === nodes.rule && selectionIsInNestedList(tr);
    var nonRuleNodeInListNode = !(content.type === nodes.rule) && nodeIsInsideAList(tr);
    if (ruleNodeInANestedListNode || noDirectionAndShouldNotSplit && nonRuleNodeInListNode || noDirectionAndShouldNotSplit && !nodeIsInsideAList(tr)) {
      // node to be inserted is an invalid child of selection so insert below selected node
      return (0, _utils.safeInsert)(content, tr.selection.from)(tr);
    }

    // if node is a rule and that is a flat list splitting and not at the end of a list
    var _tr$selection = tr.selection,
      from = _tr$selection.from,
      to = _tr$selection.to;
    var ruleTypeInAList = content.type === nodes.rule && nodeIsInsideAList(tr);
    if (ruleTypeInAList && !($insertPos.pos === insertPosEnd)) {
      return tr.replaceRange(from, to, new _model.Slice(_model.Fragment.from(nodes.rule.createChecked()), 0, 0));
    }
    if (!lookDirection) {
      // fallback to consumer for now
      return null;
    }

    // Replace empty paragraph
    if ((0, _utils2.isEmptyParagraph)($insertPos.parent) && (0, _utils.canInsert)(tr.doc.resolve($insertPos[lookDirection]()), content)) {
      return finaliseInsert(tr.replaceWith($insertPos.before(), $insertPos.after(), content), -1);
    }
    var $proposedPosition = $insertPos;
    while ($proposedPosition.depth > 0) {
      var $parentPos = tr.doc.resolve($proposedPosition[lookDirection]());
      var parentNode = $parentPos.node();

      // Insert at position (before or after target pos)
      if ((0, _utils.canInsert)($proposedPosition, content)) {
        return finaliseInsert(tr.insert($proposedPosition.pos, content), content.nodeSize);
      }

      // If we can't insert, and we think we should split, we fallback to consumer for now
      if (shouldSplit(parentNode.type, tr.doc.type.schema.nodes)) {
        var nextTr = finaliseInsert(insertBeforeOrAfter(tr, lookDirection, $parentPos, $proposedPosition, content), content.nodeSize);

        // Move selection to the closest text node, otherwise it defaults to the whatever the lookDirection is set to above
        if ([nodes.orderedList, nodes.bulletList].includes(parentNode.type) && nextTr) {
          return nextTr.setSelection(_state.TextSelection.between(nextTr.selection.$from, nextTr.selection.$from));
        } else {
          return nextTr;
        }
      }

      // Can not insert into current parent, step up one parent
      $proposedPosition = $parentPos;
    }
    return finaliseInsert(tr.insert($proposedPosition.pos, content), content.nodeSize);
  };
};
var finaliseInsert = function finaliseInsert(tr, nodeLength) {
  var lastStep = tr.steps[tr.steps.length - 1];
  if (!(lastStep instanceof _transform.ReplaceStep || lastStep instanceof _transform.ReplaceAroundStep)) {
    return null;
  }

  // Place gap cursor after the newly inserted node
  var gapCursorPos = lastStep.to + lastStep.slice.openStart + nodeLength;
  return tr.setSelection(new _selection.GapCursorSelection(tr.doc.resolve(gapCursorPos), _selection.Side.RIGHT)).scrollIntoView();
};

/**
 * Method extracted from typeahead plugin to be shared with the element browser on handling element insertion.
 */
var insertSelectedItem = exports.insertSelectedItem = function insertSelectedItem(maybeNode) {
  var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  return function (state, tr, start) {
    if (!maybeNode) {
      return tr;
    }
    var isInputFragment = maybeNode instanceof _model.Fragment;
    var node;
    try {
      node = maybeNode instanceof _model.Node || isInputFragment ? maybeNode : typeof maybeNode === 'string' ? state.schema.text(maybeNode) : _model.Node.fromJSON(state.schema, maybeNode);
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error(e);
      return tr;
    }
    if (node instanceof _model.Node && node.isText) {
      tr = tr.replaceWith(start, start, node);

      /**
       *
       * Replacing a type ahead query mark with a block node.
       *
       */
    } else if (node instanceof _model.Node && node.isBlock) {
      /**
       *
       * Rule has unique insertion behaviour
       * so using this safeInsert function in order to handle specific cases in flat list vs nested list
       * instead of a generic pmSafeInsert (i.e appending at the end)
       *
       */

      var selectionInsideAPanel = selectionIsInsideAPanel(tr);
      if (node.type.name === 'rule' && !selectionInsideAPanel &&
      // ED-17438 If the selection is not an empty paragraph we want to use pmSafeInsert
      // This fixes a bug where if a rule was inserted using safeInsert and the selection
      // was an empty paragraph it would not be inserted
      !(0, _utils2.isEmptyParagraph)(tr.selection.$from.parent)) {
        var _safeInsert;
        tr = (_safeInsert = safeInsert(node, tr.selection.from)(tr)) !== null && _safeInsert !== void 0 ? _safeInsert : tr;
      } else {
        tr = (0, _utils.safeInsert)(normaliseNestedLayout(state, node), undefined, true)(tr);
      }

      /**
       *
       * Replacing a type ahead query mark with an inline node.
       *
       */
    } else if (node instanceof _model.Node && node.isInline || isInputFragment) {
      var fragment = isInputFragment ? node : _model.Fragment.fromArray([node, state.schema.text(' ')]);
      tr = tr.replaceWith(start, start, fragment);
      if (opts.selectInlineNode) {
        // Select inserted node
        tr = tr.setSelection(_state.NodeSelection.create(tr.doc, start));
      } else {
        // Placing cursor after node + space.
        tr = tr.setSelection(_state.Selection.near(tr.doc.resolve(start + fragment.size)));
      }
    }
    return tr;
  };
};

/**
 * ED-14584: Util to check if the destination node is a paragraph & the
 * content being inserted is a valid child of the grandparent node.
 * In this case, the destination node should split
 */
var shouldSplitSelectedNodeOnNodeInsertion = exports.shouldSplitSelectedNodeOnNodeInsertion = function shouldSplitSelectedNodeOnNodeInsertion(_ref2) {
  var parentNodeType = _ref2.parentNodeType,
    grandParentNodeType = _ref2.grandParentNodeType,
    content = _ref2.content;
  if (parentNodeType.name === 'doc' || parentNodeType.name === 'paragraph' && grandParentNodeType.validContent(_model.Fragment.from(content))) {
    return true;
  }
  return false;
};