import clamp from 'lodash/clamp';
import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
import { isEmptyParagraph } from './editor-core-utils';
export var getStepRange = function getStepRange(transaction) {
  var from = -1;
  var to = -1;
  transaction.mapping.maps.forEach(function (stepMap, index) {
    stepMap.forEach(function (oldStart, oldEnd) {
      var newStart = transaction.mapping.slice(index).map(oldStart, -1);
      var newEnd = transaction.mapping.slice(index).map(oldEnd);
      var docSize = transaction.doc.content.size;
      from = clamp(newStart < from || from === -1 ? newStart : from, 0, docSize);
      to = clamp(newEnd > to || to === -1 ? newEnd : to, 0, docSize);
    });
  });
  if (from !== -1) {
    return {
      from: from,
      to: to
    };
  }
  return null;
};

// Checks to see if the parent node is the document, ie not contained within another entity
export function hasDocAsParent($anchor) {
  return $anchor.depth === 1;
}

/**
 * Checks if a node looks like an empty document
 */
export function isEmptyDocument(node) {
  var nodeChild = node.content.firstChild;
  if (node.childCount !== 1 || !nodeChild) {
    return false;
  }
  return isEmptyParagraph(nodeChild);
}
export function bracketTyped(state) {
  var selection = state.selection;
  var _ref = selection,
    $cursor = _ref.$cursor,
    $anchor = _ref.$anchor;
  if (!$cursor) {
    return false;
  }
  var node = $cursor.nodeBefore;
  if (!node) {
    return false;
  }
  if (node.type.name === 'text' && node.text === '{') {
    var paragraphNode = $anchor.node();
    return paragraphNode.marks.length === 0 && hasDocAsParent($anchor);
  }
  return false;
}
export function nodesBetweenChanged(tr, f, startPos) {
  var stepRange = getStepRange(tr);
  if (!stepRange) {
    return;
  }
  tr.doc.nodesBetween(stepRange.from, stepRange.to, f, startPos);
}

/**
 * Returns false if node contains only empty inline nodes and hardBreaks.
 */
export function hasVisibleContent(node) {
  var isInlineNodeHasVisibleContent = function isInlineNodeHasVisibleContent(inlineNode) {
    return inlineNode.isText ? !!inlineNode.textContent.trim() : inlineNode.type.name !== 'hardBreak';
  };
  if (node.isInline) {
    return isInlineNodeHasVisibleContent(node);
  } else if (node.isBlock && (node.isLeaf || node.isAtom)) {
    return true;
  } else if (!node.childCount) {
    return false;
  }
  for (var _index = 0; _index < node.childCount; _index++) {
    var child = node.child(_index);
    var invisibleNodeTypes = ['paragraph', 'text', 'hardBreak'];
    if (!invisibleNodeTypes.includes(child.type.name) || hasVisibleContent(child)) {
      return true;
    }
  }
  return false;
}
export var isSelectionEndOfParagraph = function isSelectionEndOfParagraph(state) {
  return state.selection.$to.parent.type === state.schema.nodes.paragraph && state.selection.$to.pos === state.doc.resolve(state.selection.$to.pos).end();
};
function getChangedNodesIn(_ref2) {
  var tr = _ref2.tr,
    doc = _ref2.doc;
  var nodes = [];
  var stepRange = getStepRange(tr);
  if (!stepRange) {
    return nodes;
  }
  var from = Math.min(doc.nodeSize - 2, stepRange.from);
  var to = Math.min(doc.nodeSize - 2, stepRange.to);
  doc.nodesBetween(from, to, function (node, pos) {
    nodes.push({
      node: node,
      pos: pos
    });
  });
  return nodes;
}
export function getChangedNodes(tr) {
  return getChangedNodesIn({
    tr: tr,
    doc: tr.doc
  });
}
// When document first load in Confluence, initially it is an empty document
// and Collab service triggers a transaction to replace the empty document with the real document that should be rendered.
// isReplaceDocumentOperation is checking if the transaction is the one that replace the empty document with the real document
export var isReplaceDocOperation = function isReplaceDocOperation(transactions, oldState) {
  return transactions.some(function (tr) {
    if (tr.getMeta('replaceDocument')) {
      return true;
    }
    var hasStepReplacingEntireDocument = tr.steps.some(function (step) {
      if (!(step instanceof ReplaceStep)) {
        return false;
      }
      var isStepReplacingFromDocStart = step.from === 0;
      var isStepReplacingUntilTheEndOfDocument = step.to === oldState.doc.content.size;
      if (!isStepReplacingFromDocStart || !isStepReplacingUntilTheEndOfDocument) {
        return false;
      }
      return true;
    });
    return hasStepReplacingEntireDocument;
  });
};

/**
 * Compares two ProseMirror documents for equality, ignoring attributes
 * which don't affect the document structure.
 *
 * This is almost a copy of the .eq() PM function - tweaked to ignore attrs
 *
 * @param doc1 PMNode
 * @param doc2 PMNode
 * @param attributesToIgnore Specific array of attribute keys to ignore - defaults to ignoring all
 * @returns boolean
 */
export function areNodesEqualIgnoreAttrs(node1, node2, attributesToIgnore) {
  if (node1.isText) {
    return node1.eq(node2);
  }

  // If no attributes to ignore, compare all attributes
  if (!attributesToIgnore || attributesToIgnore.length === 0) {
    return node1 === node2 || node1.hasMarkup(node2.type, node1.attrs, node2.marks) && areFragmentsEqual(node1.content, node2.content);
  }

  // Build attrs to compare by excluding ignored attributes
  var attrsToCompare = node2.attrs;
  var ignoreSet = new Set(attributesToIgnore);
  for (var key in node2.attrs) {
    if (ignoreSet.has(key)) {
      attrsToCompare[key] = node1.attrs[key];
    }
  }
  return node1 === node2 || node1.hasMarkup(node2.type, attrsToCompare, node2.marks) && areFragmentsEqual(node1.content, node2.content, attributesToIgnore);
}
function areFragmentsEqual(frag1, frag2, attributesToIgnore) {
  if (frag1.content.length !== frag2.content.length) {
    return false;
  }
  var childrenEqual = true;
  frag1.content.forEach(function (child, i) {
    var otherChild = frag2.child(i);
    if (child === otherChild || otherChild && areNodesEqualIgnoreAttrs(child, otherChild, attributesToIgnore)) {
      return;
    }
    childrenEqual = false;
  });
  return childrenEqual;
}