updateAsSiblingsChild = (
  blockMap: BlockMap,
  key: string,
  position: SiblingInsertPosition,
): BlockMap => {
  verifyTree(blockMap);
  const block = blockMap.get(key);
  invariant(block != null, 'block must exist in block map');
  const newParentKey =
    position === 'previous'
      ? block.getPrevSiblingKey()
      : block.getNextSiblingKey();
  invariant(newParentKey != null, 'sibling is null');
  const newParent = blockMap.get(newParentKey);
  invariant(
    newParent !== null && newParent.getText() === '',
    'parent must be a valid node',
  );
  let newBlockMap = blockMap;
  switch (position) {
    case 'next':
      newBlockMap = updateParentChild(newBlockMap, newParentKey, key, 0);
      const prevSibling = block.getPrevSiblingKey();
      if (prevSibling != null) {
        newBlockMap = updateSibling(newBlockMap, prevSibling, newParentKey);
      } else {
        newBlockMap = newBlockMap.set(
          newParentKey,
          newBlockMap.get(newParentKey).merge({prevSibling: null}),
        );
      }
      // we also need to flip the order of the sibling & block in the ordered map
      // for this case
      newBlockMap = newBlockMap
        .takeUntil(block => block.getKey() === key)
        .concat(
          Immutable.OrderedMap([
            [newParentKey, newBlockMap.get(newParentKey)],
            [key, newBlockMap.get(key)],
          ]),
        )
        .concat(
          newBlockMap
            .skipUntil(block => block.getKey() === newParentKey)
            .slice(1),
        );
      break;
    case 'previous':
      newBlockMap = updateParentChild(
        newBlockMap,
        newParentKey,
        key,
        newParent.getChildKeys().count(),
      );
      const nextSibling = block.getNextSiblingKey();
      if (nextSibling != null) {
        newBlockMap = updateSibling(newBlockMap, newParentKey, nextSibling);
      } else {
        newBlockMap = newBlockMap.set(
          newParentKey,
          newBlockMap.get(newParentKey).merge({nextSibling: null}),
        );
      }
      break;
  }
  // remove the node as a child of its current parent
  const parentKey = block.getParentKey();
  if (parentKey != null) {
    const parent = newBlockMap.get(parentKey);
    newBlockMap = newBlockMap.set(
      parentKey,
      parent.merge({
        children: parent
          .getChildKeys()
          .delete(parent.getChildKeys().indexOf(key)),
      }),
    );
  }
  verifyTree(newBlockMap);
  return newBlockMap;
}
This is a utility method that abstracts the operation of adding a node as the child
of its previous or next sibling.

The previous (or next) sibling must be a valid parent node.

This operation respects the tree data invariants - it expects and returns a
valid tree.