'use strict';

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

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var _block = require('./block');

var _block2 = _interopRequireDefault(_block);

var _character = require('./character');

var _character2 = _interopRequireDefault(_character);

var _data = require('./data');

var _data2 = _interopRequireDefault(_data);

var _document = require('./document');

var _document2 = _interopRequireDefault(_document);

var _inline = require('./inline');

var _inline2 = _interopRequireDefault(_inline);

var _mark = require('./mark');

var _mark2 = _interopRequireDefault(_mark);

var _selection = require('./selection');

var _selection2 = _interopRequireDefault(_selection);

var _text = require('./text');

var _text2 = _interopRequireDefault(_text);

var _uid = require('../utils/uid');

var _uid2 = _interopRequireDefault(_uid);

var _immutable = require('immutable');

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

/**
 * Transforms.
 *
 * These are pulled out into their own file because they can get complex.
 */

var Transforms = {

  /**
   * Delete everything in a `range`.
   *
   * @param {Selection} range
   * @return {Node} node
   */

  deleteAtRange: function deleteAtRange(range) {
    if (range.isCollapsed) return this;

    var node = this;

    // Make sure the children exist.
    var startKey = range.startKey;
    var startOffset = range.startOffset;
    var endKey = range.endKey;
    var endOffset = range.endOffset;

    node.assertHasDescendant(startKey);
    node.assertHasDescendant(endKey);

    // If the start and end nodes are the same, just remove characters.
    if (startKey == endKey) {
      var text = node.getDescendant(startKey);
      text = text.removeCharacters(startOffset, endOffset);
      node = node.updateDescendant(text);
      return node;
    }

    // Split the blocks and determine the edge boundaries.
    var start = range.moveToStart();
    var end = range.moveToEnd();
    node = node.splitBlockAtRange(start, Infinity);
    node = node.splitBlockAtRange(end, Infinity);

    var startText = node.getDescendant(startKey);
    var startEdgeText = node.getNextText(startKey);

    var endText = node.getNextText(endKey);
    var endEdgeText = node.getDescendant(endKey);

    // Remove the new blocks inside the edges.
    var startEdgeBlock = node.getFurthestBlock(startEdgeText);
    var endEdgeBlock = node.getFurthestBlock(endEdgeText);

    var nodes = node.nodes.takeUntil(function (n) {
      return n == startEdgeBlock;
    }).concat(node.nodes.skipUntil(function (n) {
      return n == endEdgeBlock;
    }).rest());

    node = node.merge({ nodes: nodes });

    // Take the end edge's split text and move it to the start edge.
    var startBlock = node.getFurthestBlock(startText);
    var endChild = node.getFurthestInline(endText) || endText;

    var startNodes = startBlock.nodes.push(endChild);
    startBlock = startBlock.merge({ nodes: startNodes });
    node = node.updateDescendant(startBlock);

    // While the end child is an only child, remove the block it's in.
    var endParent = node.getClosestBlock(endChild);

    while (endParent && endParent.nodes.size == 1) {
      endChild = endParent;
      endParent = node.getClosestBlock(endParent);
    }

    node = node.removeDescendant(endChild);

    // Normalize the adjacent text nodes.
    return node.normalize();
  },


  /**
   * Delete backward `n` characters at a `range`.
   *
   * @param {Selection} range
   * @param {Number} n (optional)
   * @return {Node} node
   */

  deleteBackwardAtRange: function deleteBackwardAtRange(range) {
    var n = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];

    var node = this;
    var _range = range;
    var startKey = _range.startKey;

    // When the range is still expanded, just do a regular delete.

    if (range.isExpanded) return node.deleteAtRange(range);

    // When collapsed at the start of the node, there's nothing to do.
    if (range.isAtStartOf(node)) return node;

    // When collapsed in a void node, remove that node.
    var block = node.getClosestBlock(startKey);
    if (block && block.isVoid) return node.removeDescendant(block);

    var inline = node.getClosestInline(startKey);
    if (inline && inline.isVoid) return node.removeDescendant(inline);

    // When at start of a text node, merge forwards into the next text node.
    var startNode = node.getDescendant(startKey);

    if (range.isAtStartOf(startNode)) {
      var previous = node.getPreviousText(startNode);
      range = range.extendToEndOf(previous);
      range = range.normalize(node);
      return node.deleteAtRange(range);
    }

    // Otherwise, remove `n` characters behind of the cursor.
    range = range.extendBackward(n);
    range = range.normalize(node);
    return node.deleteAtRange(range);
  },


  /**
   * Delete forward `n` characters at a `range`.
   *
   * @param {Selection} range
   * @param {Number} n (optional)
   * @return {Node} node
   */

  deleteForwardAtRange: function deleteForwardAtRange(range) {
    var n = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];

    var node = this;
    var _range2 = range;
    var startKey = _range2.startKey;

    // When the range is still expanded, just do a regular delete.

    if (range.isExpanded) return node.deleteAtRange(range);

    // When collapsed at the end of the node, there's nothing to do.
    if (range.isAtEndOf(node)) return node;

    // When collapsed in a void node, remove that node.
    var block = node.getClosestBlock(startKey);
    if (block && block.isVoid) return node.removeDescendant(block);

    var inline = node.getClosestInline(startKey);
    if (inline && inline.isVoid) return node.removeDescendant(inline);

    // When at end of a text node, merge forwards into the next text node.
    var startNode = node.getDescendant(startKey);
    if (range.isAtEndOf(startNode)) {
      var next = node.getNextText(startNode);
      range = range.extendToStartOf(next);
      range = range.normalize(node);
      return node.deleteAtRange(range);
    }

    // Otherwise, remove `n` characters ahead of the cursor.
    range = range.extendForward(n);
    range = range.normalize(node);
    return node.deleteAtRange(range);
  },


  /**
   * Insert a `fragment` at a `range`.
   *
   * @param {Selection} range
   * @param {List} fragment
   * @return {Node} node
   */

  insertFragmentAtRange: function insertFragmentAtRange(range, fragment) {
    range = range.normalize(this);
    var node = this;

    // If the range is expanded, delete first.
    if (range.isExpanded) {
      node = node.deleteAtRange(range);
      range = range.moveToStart();
    }

    // If the fragment is empty, do nothing.
    if (!fragment.length) return node;

    // Make sure each node in the fragment has a unique key.
    fragment = fragment.mapDescendants(function (child) {
      return child.set('key', (0, _uid2.default)());
    });

    // Split the inlines if need be.
    if (!node.isInlineSplitAtRange(range)) {
      node = node.splitInlineAtRange(range);
    }

    // Determine the start and next children to insert into.
    var _range3 = range;
    var startKey = _range3.startKey;
    var endKey = _range3.endKey;

    var block = node.getClosestBlock(startKey);
    var start = node.getDescendant(startKey);
    var startChild = void 0;
    var nextChild = void 0;

    if (range.isAtStartOf(node)) {
      nextChild = node.getClosestBlock(node.getTextNodes().first());
    }

    if (range.isAtStartOf(block)) {
      nextChild = block.getHighestChild(block.getTextNodes().first());
    } else if (range.isAtStartOf(start)) {
      startChild = block.getHighestChild(block.getPreviousText(start));
      nextChild = block.getNextSibling(startChild);
    } else {
      startChild = block.getHighestChild(start);
      nextChild = block.getNextSibling(startChild);
    }

    // Get the first and last block of the fragment.
    var blocks = fragment.getDeepestBlocks();
    var firstBlock = blocks.first();
    var lastBlock = blocks.last();

    // If the block is empty, merge in the first block's type and data.
    if (block.length == 0) {
      block = block.merge({
        type: firstBlock.type,
        data: firstBlock.data
      });
    }

    // Insert the first blocks nodes into the starting block.
    if (startChild) {
      block = block.insertChildrenAfter(startChild, firstBlock.nodes);
    } else {
      block = block.insertChildrenBefore(nextChild, firstBlock.nodes);
    }

    node = node.updateDescendant(block);

    // If there are no other siblings, that's it.
    if (firstBlock == lastBlock) return node.normalize();

    // Otherwise, remove the fragment's first block's highest solo parent...
    var highestParent = fragment.getHighestOnlyChildParent(firstBlock);
    fragment = fragment.removeDescendant(highestParent || firstBlock);

    // Then, add the inlines after the cursor from the current block to the
    // start of the last block in the fragment.
    if (nextChild) {
      lastBlock = lastBlock.concatChildren(block.getChildrenAfterIncluding(nextChild));
      fragment = fragment.updateDescendant(lastBlock);

      block = block.removeChildrenAfterIncluding(nextChild);
      node = node.updateDescendant(block);
    }

    // Finally, add the fragment's children after the block.
    node = node.insertChildrenAfter(block, fragment.nodes);
    return node.normalize();
  },


  /**
   * Insert text `string` at a `range`.
   *
   * @param {Selection} range
   * @param {String} string
   * @return {Node} node
   */

  insertTextAtRange: function insertTextAtRange(range, string) {
    var node = this;

    // When still expanded, remove the current range first.
    if (range.isExpanded) {
      node = node.deleteAtRange(range);
      range = range.moveToStart();
    }

    // Insert text at the range's offset.
    var _range4 = range;
    var startKey = _range4.startKey;
    var startOffset = _range4.startOffset;

    var text = node.getDescendant(startKey);
    text = text.insertText(string, startOffset);
    node = node.updateDescendant(text);

    return node;
  },


  /**
   * Add a new `mark` to the characters at `range`.
   *
   * @param {Selection} range
   * @param {Mark or String} mark
   * @return {Node} node
   */

  markAtRange: function markAtRange(range, mark) {
    var node = this;

    // Allow for just passing a type for convenience.
    if (typeof mark == 'string') {
      mark = new _mark2.default({ type: mark });
    }

    // When the range is collapsed, do nothing.
    if (range.isCollapsed) return node;

    // Otherwise, find each of the text nodes within the range.
    var startKey = range.startKey;
    var startOffset = range.startOffset;
    var endKey = range.endKey;
    var endOffset = range.endOffset;

    var texts = node.getTextsAtRange(range);

    // Apply the mark to each of the text nodes's matching characters.
    texts = texts.map(function (text) {
      var characters = text.characters.map(function (char, i) {
        if (!isInRange(i, text, range)) return char;
        var marks = char.marks;

        marks = marks.add(mark);
        return char.merge({ marks: marks });
      });

      return text.merge({ characters: characters });
    });

    // Update each of the text nodes.
    texts.forEach(function (text) {
      node = node.updateDescendant(text);
    });

    return node;
  },


  /**
   * Set the `properties` of block nodes in a `range`.
   *
   * @param {Selection} range
   * @param {Object or String} properties
   * @return {Node} node
   */

  setBlockAtRange: function setBlockAtRange(range) {
    var properties = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

    var node = this;

    // Allow for properties to be a string `type` for convenience.
    if (typeof properties == 'string') {
      properties = { type: properties };
    }

    // Update each of the blocks.
    var blocks = node.getBlocksAtRange(range);
    blocks.forEach(function (block) {
      if (properties.data) properties.data = _data2.default.create(properties.data);
      block = block.merge(properties);
      node = node.updateDescendant(block);
    });

    return node.normalize();
  },


  /**
   * Set the `properties` of inline nodes in a `range`.
   *
   * @param {Selection} range
   * @param {Object or String} properties
   * @return {Node} node
   */

  setInlineAtRange: function setInlineAtRange(range) {
    var properties = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

    var node = this;

    // Allow for properties to be a string `type` for convenience.
    if (typeof properties == 'string') {
      properties = { type: properties };
    }

    // Update each of the inlines.
    var inlines = node.getInlinesAtRange(range);
    inlines.forEach(function (inline) {
      if (properties.data) properties.data = _data2.default.create(properties.data);
      inline = inline.merge(properties);
      node = node.updateDescendant(inline);
    });

    return node.normalize();
  },


  /**
   * Split the block nodes at a `range`, to optional `depth`.
   *
   * @param {Selection} range
   * @param {Number} depth (optional)
   * @return {Node} node
   */

  splitBlockAtRange: function splitBlockAtRange(range) {
    var depth = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];

    var node = this;

    // If the range is expanded, remove it first.
    if (range.isExpanded) {
      node = node.deleteAtRange(range);
      range = range.moveToStart();
    }

    // Split the inline nodes at the range.
    node = node.splitInlineAtRange(range);

    // Find the highest inline elements that were split.
    var _range5 = range;
    var startKey = _range5.startKey;

    var firstText = node.getDescendant(startKey);
    var secondText = node.getNextText(startKey);
    var firstChild = node.getFurthestInline(firstText) || firstText;
    var secondChild = node.getFurthestInline(secondText) || secondText;
    var parent = node.getClosestBlock(firstChild);
    var firstChildren = parent.nodes.takeUntil(function (n) {
      return n == firstChild;
    }).push(firstChild);
    var secondChildren = parent.nodes.skipUntil(function (n) {
      return n == secondChild;
    });
    var d = 0;

    // While the parent is a block, split the block nodes.
    while (parent && d < depth) {
      firstChild = parent.merge({ nodes: firstChildren });
      secondChild = _block2.default.create({
        nodes: secondChildren,
        type: parent.type,
        data: parent.data
      });

      firstChildren = _block2.default.createList([firstChild]);
      secondChildren = _block2.default.createList([secondChild]);

      // Add the new children.
      var grandparent = node.getParent(parent);
      var nodes = grandparent.nodes.takeUntil(function (n) {
        return n.key == firstChild.key;
      }).push(firstChild).push(secondChild).concat(grandparent.nodes.skipUntil(function (n) {
        return n.key == firstChild.key;
      }).rest());

      // Update the grandparent.
      node = grandparent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(grandparent.merge({ nodes: nodes }));

      d++;
      parent = node.getClosestBlock(firstChild);
    }

    return node;
  },


  /**
   * Split the inline nodes at a `range`, to optional `depth`.
   *
   * @param {Selection} range
   * @param {Number} depth (optiona)
   * @return {Node} node
   */

  splitInlineAtRange: function splitInlineAtRange(range) {
    var depth = arguments.length <= 1 || arguments[1] === undefined ? Infinity : arguments[1];

    var node = this;

    // If the range is expanded, remove it first.
    if (range.isExpanded) {
      node = node.deleteAtRange(range);
      range = range.moveToStart();
    }

    // First split the text nodes.
    node = node.splitTextAtRange(range);

    // Find the children that were split.
    var _range6 = range;
    var startKey = _range6.startKey;

    var firstChild = node.getDescendant(startKey);
    var secondChild = node.getNextText(firstChild);
    var parent = node.getClosestInline(firstChild);
    var d = 0;

    // While the parent is an inline parent, split the inline nodes.
    while (parent && d < depth) {
      firstChild = parent.merge({ nodes: _inline2.default.createList([firstChild]) });
      secondChild = _inline2.default.create({
        nodes: [secondChild],
        type: parent.type,
        data: parent.data
      });

      // Split the children.
      var grandparent = node.getParent(parent);
      var nodes = grandparent.nodes.takeUntil(function (n) {
        return n.key == firstChild.key;
      }).push(firstChild).push(secondChild).concat(grandparent.nodes.skipUntil(function (n) {
        return n.key == firstChild.key;
      }).rest());

      // Update the grandparent.
      node = grandparent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(grandparent.merge({ nodes: nodes }));

      d++;
      parent = node.getClosestInline(firstChild);
    }

    return node;
  },


  /**
   * Split the text nodes at a `range`.
   *
   * @param {Selection} range
   * @return {Node} node
   */

  splitTextAtRange: function splitTextAtRange(range) {
    var node = this;

    // If the range is expanded, remove it first.
    if (range.isExpanded) {
      node = node.deleteAtRange(range);
      range = range.moveToStart();
    }

    // Split the text node's characters.
    var _range7 = range;
    var startKey = _range7.startKey;
    var startOffset = _range7.startOffset;

    var text = node.getDescendant(startKey);
    var characters = text.characters;

    var firstChars = characters.take(startOffset);
    var secondChars = characters.skip(startOffset);
    var firstChild = text.merge({ characters: firstChars });
    var secondChild = _text2.default.create({ characters: secondChars });

    // Split the text nodes.
    var parent = node.getParent(text);
    var nodes = parent.nodes.takeUntil(function (c) {
      return c.key == firstChild.key;
    }).push(firstChild).push(secondChild).concat(parent.nodes.skipUntil(function (n) {
      return n.key == firstChild.key;
    }).rest());

    // Update the nodes.
    parent = parent.merge({ nodes: nodes });
    node = node.updateDescendant(parent);
    return node;
  },


  /**
   * Remove an existing `mark` to the characters at `range`.
   *
   * @param {Selection} range
   * @param {Mark or String} mark (optional)
   * @return {Node} node
   */

  unmarkAtRange: function unmarkAtRange(range, mark) {
    var node = this;

    // Allow for just passing a type for convenience.
    if (typeof mark == 'string') {
      mark = new _mark2.default({ type: mark });
    }

    // When the range is collapsed, do nothing.
    if (range.isCollapsed) return node;

    // Otherwise, find each of the text nodes within the range.
    var texts = node.getTextsAtRange(range);

    // Apply the mark to each of the text nodes's matching characters.
    texts = texts.map(function (text) {
      var characters = text.characters.map(function (char, i) {
        if (!isInRange(i, text, range)) return char;
        var marks = char.marks;

        marks = mark ? marks.remove(mark) : marks.clear();
        return char.merge({ marks: marks });
      });

      return text.merge({ characters: characters });
    });

    // Update each of the text nodes.
    texts.forEach(function (text) {
      node = node.updateDescendant(text);
    });

    return node;
  },


  /**
   * Unwrap all of the block nodes in a `range` from a block node of `type.`
   *
   * @param {Selection} range
   * @param {String} type (optional)
   * @param {Data or Object} data (optional)
   * @return {Node} node
   */

  unwrapBlockAtRange: function unwrapBlockAtRange(range, type, data) {
    var node = this;

    // Allow for only data.
    if ((typeof type === 'undefined' ? 'undefined' : _typeof(type)) == 'object') {
      data = type;
      type = null;
    }

    // Ensure that data is immutable.
    if (data) data = _data2.default.create(data);

    // Find the closest wrapping blocks of each text node.
    var texts = node.getBlocksAtRange(range);
    var wrappers = texts.reduce(function (memo, text) {
      var match = node.getClosest(text, function (parent) {
        if (parent.kind != 'block') return false;
        if (type && parent.type != type) return false;
        if (data && !parent.data.isSuperset(data)) return false;
        return true;
      });

      if (match) memo = memo.add(match);
      return memo;
    }, new _immutable.Set());

    // Replace each of the wrappers with their child nodes.
    wrappers.forEach(function (wrapper) {
      var parent = node.getParent(wrapper);

      // Replace the wrapper in the parent's nodes with the block.
      var nodes = parent.nodes.takeUntil(function (n) {
        return n == wrapper;
      }).concat(wrapper.nodes).concat(parent.nodes.skipUntil(function (n) {
        return n == wrapper;
      }).rest());

      // Update the parent.
      node = parent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(parent.merge({ nodes: nodes }));
    });

    return node.normalize();
  },


  /**
   * Unwrap the inline nodes in a `range` from an parent inline with `type`.
   *
   * @param {Selection} range
   * @param {String} type (optional)
   * @param {Data} data (optional)
   * @return {Node} node
   */

  unwrapInlineAtRange: function unwrapInlineAtRange(range, type, data) {
    var node = this;
    var blocks = node.getInlinesAtRange(range);

    // Allow for no type.
    if ((typeof type === 'undefined' ? 'undefined' : _typeof(type)) == 'object') {
      data = type;
      type = null;
    }

    // Ensure that data is immutable.
    if (data) data = _data2.default.create(data);

    // Find the closest matching inline wrappers of each text node.
    var texts = this.getTextNodes();
    var wrappers = texts.reduce(function (memo, text) {
      var match = node.getClosest(text, function (parent) {
        if (parent.kind != 'inline') return false;
        if (type && parent.type != type) return false;
        if (data && !parent.data.isSuperset(data)) return false;
        return true;
      });

      if (match) memo = memo.add(match);
      return memo;
    }, new _immutable.Set());

    // Replace each of the wrappers with their child nodes.
    wrappers.forEach(function (wrapper) {
      var parent = node.getParent(wrapper);

      // Replace the wrapper in the parent's nodes with the block.
      var nodes = parent.nodes.takeUntil(function (n) {
        return n == wrapper;
      }).concat(wrapper.nodes).concat(parent.nodes.skipUntil(function (n) {
        return n == wrapper;
      }).rest());

      // Update the parent.
      node = parent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(parent.merge({ nodes: nodes }));
    });

    return node.normalize();
  },


  /**
   * Wrap all of the blocks in a `range` in a new block node of `type`.
   *
   * @param {Selection} range
   * @param {String} type
   * @param {Data} data (optional)
   * @return {Node} node
   */

  wrapBlockAtRange: function wrapBlockAtRange(range, type, data) {
    data = _data2.default.create(data);
    var node = this;

    // Get the block nodes, sorted by depth.
    var blocks = node.getBlocksAtRange(range);
    var sorted = blocks.sort(function (a, b) {
      var da = node.getDepth(a);
      var db = node.getDepth(b);
      if (da == db) return 0;else if (da > db) return -1;else return 1;
    });

    // Get the lowest common siblings, relative to the highest block.
    var highest = sorted.first();
    var depth = node.getDepth(highest);
    var siblings = blocks.reduce(function (memo, block) {
      var sibling = node.getDepth(block) == depth ? block : node.getClosest(block, function (p) {
        return node.getDepth(p) == depth;
      });
      memo = memo.push(sibling);
      return memo;
    }, _block2.default.createList());

    // Wrap the siblings in a new block.
    var wrapper = _block2.default.create({
      nodes: siblings,
      type: type,
      data: data
    });

    // Replace the siblings with the wrapper.
    var first = siblings.first();
    var last = siblings.last();
    var parent = node.getParent(highest);
    var nodes = parent.nodes.takeUntil(function (n) {
      return n == first;
    }).push(wrapper).concat(parent.nodes.skipUntil(function (n) {
      return n == last;
    }).rest());

    // Update the parent.
    node = parent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(parent.merge({ nodes: nodes }));

    return node;
  },


  /**
   * Wrap the text and inline nodes in a `range` with a new inline node.
   *
   * @param {Selection} range
   * @param {String} type
   * @param {Data} data (optional)
   * @return {Node} node
   */

  wrapInlineAtRange: function wrapInlineAtRange(range, type, data) {
    data = _data2.default.create(data);
    var node = this;

    // If collapsed or unset, there's nothing to wrap.
    if (range.isCollapsed || range.isUnset) return node;

    // Split at the start of the range.
    var start = range.moveToStart();
    node = node.splitInlineAtRange(start);

    // Determine the new end of the range, and split there.
    var _range8 = range;
    var startKey = _range8.startKey;
    var startOffset = _range8.startOffset;
    var endKey = _range8.endKey;
    var endOffset = _range8.endOffset;

    var firstNode = node.getDescendant(startKey);
    var nextNode = node.getNextText(startKey);
    var end = startKey != endKey ? range.moveToEnd() : _selection2.default.create({
      anchorKey: nextNode.key,
      anchorOffset: endOffset - startOffset,
      focusKey: nextNode.key,
      focusOffset: endOffset - startOffset
    });

    node = node.splitInlineAtRange(end);

    // Calculate the new range to wrap around.
    var endNode = node.getDescendant(end.anchorKey);
    range = _selection2.default.create({
      anchorKey: nextNode.key,
      anchorOffset: 0,
      focusKey: endNode.key,
      focusOffset: endNode.length
    });

    // Get the furthest inline nodes in the range.
    var texts = node.getTextsAtRange(range);
    var children = texts.map(function (text) {
      return node.getFurthestInline(text) || text;
    });

    // Iterate each of the child nodes, wrapping them.
    children.forEach(function (child) {
      var obj = {};
      obj.nodes = [child];
      obj.type = type;
      if (data) obj.data = data;
      var wrapper = _inline2.default.create(obj);

      // Replace the child in it's parent with the wrapper.
      var parent = node.getParent(child);
      var nodes = parent.nodes.takeUntil(function (n) {
        return n == child;
      }).push(wrapper).concat(parent.nodes.skipUntil(function (n) {
        return n == child;
      }).rest());

      // Update the parent.
      node = parent == node ? node.merge({ nodes: nodes }) : node.updateDescendant(parent.merge({ nodes: nodes }));
    });

    return node;
  }
};

/**
 * Check if an `index` of a `text` node is in a `range`.
 *
 * @param {Number} index
 * @param {Text} text
 * @param {Selection} range
 * @return {Set} characters
 */

function isInRange(index, text, range) {
  var startKey = range.startKey;
  var startOffset = range.startOffset;
  var endKey = range.endKey;
  var endOffset = range.endOffset;

  var matcher = void 0;

  if (text.key == startKey && text.key == endKey) {
    return startOffset <= index && index < endOffset;
  } else if (text.key == startKey) {
    return startOffset <= index;
  } else if (text.key == endKey) {
    return index < endOffset;
  } else {
    return true;
  }
}

/**
 * Export.
 */

exports.default = Transforms;