/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

import { $isDecoratorNode, $getNodeByKey, $isLineBreakNode, $isTextNode, $getSelection, $isRangeSelection, $isElementNode, $getNodeByKeyOrThrow, removeFromParent, createEditor, $isRootNode, $getWritableNodeState, $getRoot, RootNode, ElementNode, TextNode, $getState, createState, COLLABORATION_TAG, HISTORIC_TAG, $addUpdateTag, SKIP_SCROLL_INTO_VIEW_TAG, $createParagraphNode, createCommand } from 'lexical';
import { XmlText, XmlElement, Map as Map$1, Doc, typeListToArraySnapshot, Snapshot, isDeleted, XmlHook, ContentString, ContentFormat, emptySnapshot, PermanentUserData, iterateDeletedStructs, snapshot, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, compareRelativePositions, Item, YMapEvent, YTextEvent, YXmlEvent, UndoManager } from 'yjs';
import { $createChildrenArray } from '@lexical/offset';
import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

// Do not require this module directly! Use normal `invariant` calls.

function formatDevErrorMessage(message) {
  throw new Error(message);
}

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

function simpleDiffWithCursor(a, b, cursor) {
  const aLength = a.length;
  const bLength = b.length;
  let left = 0; // number of same characters counting from left
  let right = 0; // number of same characters counting from right
  // Iterate left to the right until we find a changed character
  // First iteration considers the current cursor position
  while (left < aLength && left < bLength && a[left] === b[left] && left < cursor) {
    left++;
  }
  // Iterate right to the left until we find a changed character
  while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) {
    right++;
  }
  // Try to iterate left further to the right without caring about the current cursor position
  while (right + left < aLength && right + left < bLength && a[left] === b[left]) {
    left++;
  }
  return {
    index: left,
    insert: b.slice(left, bLength - right),
    remove: aLength - left - right
  };
}

class CollabDecoratorNode {
  _xmlElem;
  _key;
  _parent;
  _type;
  constructor(xmlElem, parent, type) {
    this._key = '';
    this._xmlElem = xmlElem;
    this._parent = parent;
    this._type = type;
  }
  getPrevNode(nodeMap) {
    if (nodeMap === null) {
      return null;
    }
    const node = nodeMap.get(this._key);
    return $isDecoratorNode(node) ? node : null;
  }
  getNode() {
    const node = $getNodeByKey(this._key);
    return $isDecoratorNode(node) ? node : null;
  }
  getSharedType() {
    return this._xmlElem;
  }
  getType() {
    return this._type;
  }
  getKey() {
    return this._key;
  }
  getSize() {
    return 1;
  }
  getOffset() {
    const collabElementNode = this._parent;
    return collabElementNode.getChildOffset(this);
  }
  syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) {
    const prevLexicalNode = this.getPrevNode(prevNodeMap);
    const xmlElem = this._xmlElem;
    syncPropertiesFromLexical(binding, xmlElem, prevLexicalNode, nextLexicalNode);
  }
  syncPropertiesFromYjs(binding, keysChanged) {
    const lexicalNode = this.getNode();
    if (!(lexicalNode !== null)) {
      formatDevErrorMessage(`syncPropertiesFromYjs: could not find decorator node`);
    }
    const xmlElem = this._xmlElem;
    $syncPropertiesFromYjs(binding, xmlElem, lexicalNode, keysChanged);
  }
  destroy(binding) {
    const collabNodeMap = binding.collabNodeMap;
    if (collabNodeMap.get(this._key) === this) {
      collabNodeMap.delete(this._key);
    }
  }
}
function $createCollabDecoratorNode(xmlElem, parent, type) {
  const collabNode = new CollabDecoratorNode(xmlElem, parent, type);
  xmlElem._collabNode = collabNode;
  return collabNode;
}

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

class CollabLineBreakNode {
  _map;
  _key;
  _parent;
  _type;
  constructor(map, parent) {
    this._key = '';
    this._map = map;
    this._parent = parent;
    this._type = 'linebreak';
  }
  getNode() {
    const node = $getNodeByKey(this._key);
    return $isLineBreakNode(node) ? node : null;
  }
  getKey() {
    return this._key;
  }
  getSharedType() {
    return this._map;
  }
  getType() {
    return this._type;
  }
  getSize() {
    return 1;
  }
  getOffset() {
    const collabElementNode = this._parent;
    return collabElementNode.getChildOffset(this);
  }
  destroy(binding) {
    const collabNodeMap = binding.collabNodeMap;
    if (collabNodeMap.get(this._key) === this) {
      collabNodeMap.delete(this._key);
    }
  }
}
function $createCollabLineBreakNode(map, parent) {
  const collabNode = new CollabLineBreakNode(map, parent);
  map._collabNode = collabNode;
  return collabNode;
}

function $diffTextContentAndApplyDelta(collabNode, key, prevText, nextText) {
  const selection = $getSelection();
  let cursorOffset = nextText.length;
  if ($isRangeSelection(selection) && selection.isCollapsed()) {
    const anchor = selection.anchor;
    if (anchor.key === key) {
      cursorOffset = anchor.offset;
    }
  }
  const diff = simpleDiffWithCursor(prevText, nextText, cursorOffset);
  collabNode.spliceText(diff.index, diff.remove, diff.insert);
}
class CollabTextNode {
  _map;
  _key;
  _parent;
  _text;
  _type;
  _normalized;
  constructor(map, text, parent, type) {
    this._key = '';
    this._map = map;
    this._parent = parent;
    this._text = text;
    this._type = type;
    this._normalized = false;
  }
  getPrevNode(nodeMap) {
    if (nodeMap === null) {
      return null;
    }
    const node = nodeMap.get(this._key);
    return $isTextNode(node) ? node : null;
  }
  getNode() {
    const node = $getNodeByKey(this._key);
    return $isTextNode(node) ? node : null;
  }
  getSharedType() {
    return this._map;
  }
  getType() {
    return this._type;
  }
  getKey() {
    return this._key;
  }
  getSize() {
    return this._text.length + (this._normalized ? 0 : 1);
  }
  getOffset() {
    const collabElementNode = this._parent;
    return collabElementNode.getChildOffset(this);
  }
  spliceText(index, delCount, newText) {
    const collabElementNode = this._parent;
    const xmlText = collabElementNode._xmlText;
    const offset = this.getOffset() + 1 + index;
    if (delCount !== 0) {
      xmlText.delete(offset, delCount);
    }
    if (newText !== '') {
      xmlText.insert(offset, newText);
    }
  }
  syncPropertiesAndTextFromLexical(binding, nextLexicalNode, prevNodeMap) {
    const prevLexicalNode = this.getPrevNode(prevNodeMap);
    const nextText = nextLexicalNode.__text;
    syncPropertiesFromLexical(binding, this._map, prevLexicalNode, nextLexicalNode);
    if (prevLexicalNode !== null) {
      const prevText = prevLexicalNode.__text;
      if (prevText !== nextText) {
        const key = nextLexicalNode.__key;
        $diffTextContentAndApplyDelta(this, key, prevText, nextText);
        this._text = nextText;
      }
    }
  }
  syncPropertiesAndTextFromYjs(binding, keysChanged) {
    const lexicalNode = this.getNode();
    if (!(lexicalNode !== null)) {
      formatDevErrorMessage(`syncPropertiesAndTextFromYjs: could not find decorator node`);
    }
    $syncPropertiesFromYjs(binding, this._map, lexicalNode, keysChanged);
    const collabText = this._text;
    if (lexicalNode.__text !== collabText) {
      lexicalNode.setTextContent(collabText);
    }
  }
  destroy(binding) {
    const collabNodeMap = binding.collabNodeMap;
    if (collabNodeMap.get(this._key) === this) {
      collabNodeMap.delete(this._key);
    }
  }
}
function $createCollabTextNode(map, text, parent, type) {
  const collabNode = new CollabTextNode(map, text, parent, type);
  map._collabNode = collabNode;
  return collabNode;
}

class CollabElementNode {
  _key;
  _children;
  _xmlText;
  _type;
  _parent;
  constructor(xmlText, parent, type) {
    this._key = '';
    this._children = [];
    this._xmlText = xmlText;
    this._type = type;
    this._parent = parent;
  }
  getPrevNode(nodeMap) {
    if (nodeMap === null) {
      return null;
    }
    const node = nodeMap.get(this._key);
    return $isElementNode(node) ? node : null;
  }
  getNode() {
    const node = $getNodeByKey(this._key);
    return $isElementNode(node) ? node : null;
  }
  getSharedType() {
    return this._xmlText;
  }
  getType() {
    return this._type;
  }
  getKey() {
    return this._key;
  }
  isEmpty() {
    return this._children.length === 0;
  }
  getSize() {
    return 1;
  }
  getOffset() {
    const collabElementNode = this._parent;
    if (!(collabElementNode !== null)) {
      formatDevErrorMessage(`getOffset: could not find collab element node`);
    }
    return collabElementNode.getChildOffset(this);
  }
  syncPropertiesFromYjs(binding, keysChanged) {
    const lexicalNode = this.getNode();
    if (!(lexicalNode !== null)) {
      formatDevErrorMessage(`syncPropertiesFromYjs: could not find element node`);
    }
    $syncPropertiesFromYjs(binding, this._xmlText, lexicalNode, keysChanged);
  }
  applyChildrenYjsDelta(binding, deltas) {
    const children = this._children;
    let currIndex = 0;
    let pendingSplitText = null;
    for (let i = 0; i < deltas.length; i++) {
      const delta = deltas[i];
      const insertDelta = delta.insert;
      const deleteDelta = delta.delete;
      if (delta.retain != null) {
        currIndex += delta.retain;
      } else if (typeof deleteDelta === 'number') {
        let deletionSize = deleteDelta;
        while (deletionSize > 0) {
          const {
            node,
            nodeIndex,
            offset,
            length
          } = getPositionFromElementAndOffset(this, currIndex, false);
          if (node instanceof CollabElementNode || node instanceof CollabLineBreakNode || node instanceof CollabDecoratorNode) {
            children.splice(nodeIndex, 1);
            deletionSize -= 1;
          } else if (node instanceof CollabTextNode) {
            const delCount = Math.min(deletionSize, length);
            const prevCollabNode = nodeIndex !== 0 ? children[nodeIndex - 1] : null;
            const nodeSize = node.getSize();
            if (offset === 0 && length === nodeSize) {
              // Text node has been deleted.
              children.splice(nodeIndex, 1);
              // If this was caused by an undo from YJS, there could be dangling text.
              const danglingText = spliceString(node._text, offset, delCount - 1, '');
              if (danglingText.length > 0) {
                if (prevCollabNode instanceof CollabTextNode) {
                  // Merge the text node with previous.
                  prevCollabNode._text += danglingText;
                } else {
                  // No previous text node to merge into, just delete the text.
                  this._xmlText.delete(offset, danglingText.length);
                }
              }
            } else {
              node._text = spliceString(node._text, offset, delCount, '');
            }
            deletionSize -= delCount;
          } else {
            // Can occur due to the deletion from the dangling text heuristic below.
            break;
          }
        }
      } else if (insertDelta != null) {
        if (typeof insertDelta === 'string') {
          const {
            node,
            offset
          } = getPositionFromElementAndOffset(this, currIndex, true);
          if (node instanceof CollabTextNode) {
            node._text = spliceString(node._text, offset, 0, insertDelta);
          } else {
            // TODO: maybe we can improve this by keeping around a redundant
            // text node map, rather than removing all the text nodes, so there
            // never can be dangling text.

            // We have a conflict where there was likely a CollabTextNode and
            // an Lexical TextNode too, but they were removed in a merge. So
            // let's just ignore the text and trigger a removal for it from our
            // shared type.
            this._xmlText.delete(offset, insertDelta.length);
          }
          currIndex += insertDelta.length;
        } else {
          const sharedType = insertDelta;
          const {
            node,
            nodeIndex,
            length
          } = getPositionFromElementAndOffset(this, currIndex, false);
          const collabNode = $getOrInitCollabNodeFromSharedType(binding, sharedType, this);
          if (node instanceof CollabTextNode && length > 0 && length < node._text.length) {
            // Trying to insert in the middle of a text node; split the text.
            const text = node._text;
            const splitIdx = text.length - length;
            node._text = spliceString(text, splitIdx, length, '');
            children.splice(nodeIndex + 1, 0, collabNode);
            // The insert that triggers the text split might not be a text node. Need to keep a
            // reference to the remaining text so that it can be added when we do create one.
            pendingSplitText = spliceString(text, 0, splitIdx, '');
          } else {
            children.splice(nodeIndex, 0, collabNode);
          }
          if (pendingSplitText !== null && collabNode instanceof CollabTextNode) {
            // Found a text node to insert the pending text into.
            collabNode._text = pendingSplitText + collabNode._text;
            pendingSplitText = null;
          }
          currIndex += 1;
        }
      } else {
        throw new Error('Unexpected delta format');
      }
    }
  }
  syncChildrenFromYjs(binding) {
    // Now diff the children of the collab node with that of our existing Lexical node.
    const lexicalNode = this.getNode();
    if (!(lexicalNode !== null)) {
      formatDevErrorMessage(`syncChildrenFromYjs: could not find element node`);
    }
    const key = lexicalNode.__key;
    const prevLexicalChildrenKeys = $createChildrenArray(lexicalNode, null);
    const lexicalChildrenKeysLength = prevLexicalChildrenKeys.length;
    const collabChildren = this._children;
    const collabChildrenLength = collabChildren.length;
    const collabNodeMap = binding.collabNodeMap;
    const visitedKeys = new Set();
    let collabKeys;
    let writableLexicalNode;
    let prevIndex = 0;
    let prevChildNode = null;
    if (collabChildrenLength !== lexicalChildrenKeysLength) {
      writableLexicalNode = lexicalNode.getWritable();
    }
    for (let i = 0; i < collabChildrenLength; i++) {
      const lexicalChildKey = prevLexicalChildrenKeys[prevIndex];
      const childCollabNode = collabChildren[i];
      const collabLexicalChildNode = childCollabNode.getNode();
      const collabKey = childCollabNode._key;
      if (collabLexicalChildNode !== null && lexicalChildKey === collabKey) {
        const childNeedsUpdating = $isTextNode(collabLexicalChildNode);
        // Update
        visitedKeys.add(lexicalChildKey);
        if (childNeedsUpdating) {
          childCollabNode._key = lexicalChildKey;
          if (childCollabNode instanceof CollabElementNode) {
            const xmlText = childCollabNode._xmlText;
            childCollabNode.syncPropertiesFromYjs(binding, null);
            childCollabNode.applyChildrenYjsDelta(binding, xmlText.toDelta());
            childCollabNode.syncChildrenFromYjs(binding);
          } else if (childCollabNode instanceof CollabTextNode) {
            childCollabNode.syncPropertiesAndTextFromYjs(binding, null);
          } else if (childCollabNode instanceof CollabDecoratorNode) {
            childCollabNode.syncPropertiesFromYjs(binding, null);
          } else if (!(childCollabNode instanceof CollabLineBreakNode)) {
            {
              formatDevErrorMessage(`syncChildrenFromYjs: expected text, element, decorator, or linebreak collab node`);
            }
          }
        }
        prevChildNode = collabLexicalChildNode;
        prevIndex++;
      } else {
        if (collabKeys === undefined) {
          collabKeys = new Set();
          for (let s = 0; s < collabChildrenLength; s++) {
            const child = collabChildren[s];
            const childKey = child._key;
            if (childKey !== '') {
              collabKeys.add(childKey);
            }
          }
        }
        if (collabLexicalChildNode !== null && lexicalChildKey !== undefined && !collabKeys.has(lexicalChildKey)) {
          const nodeToRemove = $getNodeByKeyOrThrow(lexicalChildKey);
          removeFromParent(nodeToRemove);
          i--;
          prevIndex++;
          continue;
        }
        writableLexicalNode = lexicalNode.getWritable();
        // Create/Replace
        const lexicalChildNode = createLexicalNodeFromCollabNode(binding, childCollabNode, key);
        const childKey = lexicalChildNode.__key;
        collabNodeMap.set(childKey, childCollabNode);
        if (prevChildNode === null) {
          const nextSibling = writableLexicalNode.getFirstChild();
          writableLexicalNode.__first = childKey;
          if (nextSibling !== null) {
            const writableNextSibling = nextSibling.getWritable();
            writableNextSibling.__prev = childKey;
            lexicalChildNode.__next = writableNextSibling.__key;
          }
        } else {
          const writablePrevChildNode = prevChildNode.getWritable();
          const nextSibling = prevChildNode.getNextSibling();
          writablePrevChildNode.__next = childKey;
          lexicalChildNode.__prev = prevChildNode.__key;
          if (nextSibling !== null) {
            const writableNextSibling = nextSibling.getWritable();
            writableNextSibling.__prev = childKey;
            lexicalChildNode.__next = writableNextSibling.__key;
          }
        }
        if (i === collabChildrenLength - 1) {
          writableLexicalNode.__last = childKey;
        }
        writableLexicalNode.__size++;
        prevChildNode = lexicalChildNode;
      }
    }
    for (let i = 0; i < lexicalChildrenKeysLength; i++) {
      const lexicalChildKey = prevLexicalChildrenKeys[i];
      if (!visitedKeys.has(lexicalChildKey)) {
        // Remove
        const lexicalChildNode = $getNodeByKeyOrThrow(lexicalChildKey);
        const collabNode = binding.collabNodeMap.get(lexicalChildKey);
        if (collabNode !== undefined) {
          collabNode.destroy(binding);
        }
        removeFromParent(lexicalChildNode);
      }
    }
  }
  syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) {
    syncPropertiesFromLexical(binding, this._xmlText, this.getPrevNode(prevNodeMap), nextLexicalNode);
  }
  _syncChildFromLexical(binding, index, key, prevNodeMap, dirtyElements, dirtyLeaves) {
    const childCollabNode = this._children[index];
    // Update
    const nextChildNode = $getNodeByKeyOrThrow(key);
    if (childCollabNode instanceof CollabElementNode && $isElementNode(nextChildNode)) {
      childCollabNode.syncPropertiesFromLexical(binding, nextChildNode, prevNodeMap);
      childCollabNode.syncChildrenFromLexical(binding, nextChildNode, prevNodeMap, dirtyElements, dirtyLeaves);
    } else if (childCollabNode instanceof CollabTextNode && $isTextNode(nextChildNode)) {
      childCollabNode.syncPropertiesAndTextFromLexical(binding, nextChildNode, prevNodeMap);
    } else if (childCollabNode instanceof CollabDecoratorNode && $isDecoratorNode(nextChildNode)) {
      childCollabNode.syncPropertiesFromLexical(binding, nextChildNode, prevNodeMap);
    }
  }
  syncChildrenFromLexical(binding, nextLexicalNode, prevNodeMap, dirtyElements, dirtyLeaves) {
    const prevLexicalNode = this.getPrevNode(prevNodeMap);
    const prevChildren = prevLexicalNode === null ? [] : $createChildrenArray(prevLexicalNode, prevNodeMap);
    const nextChildren = $createChildrenArray(nextLexicalNode, null);
    const prevEndIndex = prevChildren.length - 1;
    const nextEndIndex = nextChildren.length - 1;
    const collabNodeMap = binding.collabNodeMap;
    let prevChildrenSet;
    let nextChildrenSet;
    let prevIndex = 0;
    let nextIndex = 0;
    while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) {
      const prevKey = prevChildren[prevIndex];
      const nextKey = nextChildren[nextIndex];
      if (prevKey === nextKey) {
        // Nove move, create or remove
        this._syncChildFromLexical(binding, nextIndex, nextKey, prevNodeMap, dirtyElements, dirtyLeaves);
        prevIndex++;
        nextIndex++;
      } else {
        if (prevChildrenSet === undefined) {
          prevChildrenSet = new Set(prevChildren);
        }
        if (nextChildrenSet === undefined) {
          nextChildrenSet = new Set(nextChildren);
        }
        const nextHasPrevKey = nextChildrenSet.has(prevKey);
        const prevHasNextKey = prevChildrenSet.has(nextKey);
        if (!nextHasPrevKey) {
          // Remove
          this.splice(binding, nextIndex, 1);
          prevIndex++;
        } else {
          // Create or replace
          const nextChildNode = $getNodeByKeyOrThrow(nextKey);
          const collabNode = $createCollabNodeFromLexicalNode(binding, nextChildNode, this);
          collabNodeMap.set(nextKey, collabNode);
          if (prevHasNextKey) {
            this.splice(binding, nextIndex, 1, collabNode);
            prevIndex++;
            nextIndex++;
          } else {
            this.splice(binding, nextIndex, 0, collabNode);
            nextIndex++;
          }
        }
      }
    }
    const appendNewChildren = prevIndex > prevEndIndex;
    const removeOldChildren = nextIndex > nextEndIndex;
    if (appendNewChildren && !removeOldChildren) {
      for (; nextIndex <= nextEndIndex; ++nextIndex) {
        const key = nextChildren[nextIndex];
        const nextChildNode = $getNodeByKeyOrThrow(key);
        const collabNode = $createCollabNodeFromLexicalNode(binding, nextChildNode, this);
        this.append(collabNode);
        collabNodeMap.set(key, collabNode);
      }
    } else if (removeOldChildren && !appendNewChildren) {
      for (let i = this._children.length - 1; i >= nextIndex; i--) {
        this.splice(binding, i, 1);
      }
    }
  }
  append(collabNode) {
    const xmlText = this._xmlText;
    const children = this._children;
    const lastChild = children[children.length - 1];
    const offset = lastChild !== undefined ? lastChild.getOffset() + lastChild.getSize() : 0;
    if (collabNode instanceof CollabElementNode) {
      xmlText.insertEmbed(offset, collabNode._xmlText);
    } else if (collabNode instanceof CollabTextNode) {
      const map = collabNode._map;
      if (map.parent === null) {
        xmlText.insertEmbed(offset, map);
      }
      xmlText.insert(offset + 1, collabNode._text);
    } else if (collabNode instanceof CollabLineBreakNode) {
      xmlText.insertEmbed(offset, collabNode._map);
    } else if (collabNode instanceof CollabDecoratorNode) {
      xmlText.insertEmbed(offset, collabNode._xmlElem);
    }
    this._children.push(collabNode);
  }
  splice(binding, index, delCount, collabNode) {
    const children = this._children;
    const child = children[index];
    if (child === undefined) {
      if (!(collabNode !== undefined)) {
        formatDevErrorMessage(`splice: could not find collab element node`);
      }
      this.append(collabNode);
      return;
    }
    const offset = child.getOffset();
    if (!(offset !== -1)) {
      formatDevErrorMessage(`splice: expected offset to be greater than zero`);
    }
    const xmlText = this._xmlText;
    if (delCount !== 0) {
      // What if we delete many nodes, don't we need to get all their
      // sizes?
      xmlText.delete(offset, child.getSize());
    }
    if (collabNode instanceof CollabElementNode) {
      xmlText.insertEmbed(offset, collabNode._xmlText);
    } else if (collabNode instanceof CollabTextNode) {
      const map = collabNode._map;
      if (map.parent === null) {
        xmlText.insertEmbed(offset, map);
      }
      xmlText.insert(offset + 1, collabNode._text);
    } else if (collabNode instanceof CollabLineBreakNode) {
      xmlText.insertEmbed(offset, collabNode._map);
    } else if (collabNode instanceof CollabDecoratorNode) {
      xmlText.insertEmbed(offset, collabNode._xmlElem);
    }
    if (delCount !== 0) {
      const childrenToDelete = children.slice(index, index + delCount);
      for (let i = 0; i < childrenToDelete.length; i++) {
        childrenToDelete[i].destroy(binding);
      }
    }
    if (collabNode !== undefined) {
      children.splice(index, delCount, collabNode);
    } else {
      children.splice(index, delCount);
    }
  }
  getChildOffset(collabNode) {
    let offset = 0;
    const children = this._children;
    for (let i = 0; i < children.length; i++) {
      const child = children[i];
      if (child === collabNode) {
        return offset;
      }
      offset += child.getSize();
    }
    return -1;
  }
  destroy(binding) {
    const collabNodeMap = binding.collabNodeMap;
    const children = this._children;
    for (let i = 0; i < children.length; i++) {
      children[i].destroy(binding);
    }
    if (collabNodeMap.get(this._key) === this) {
      collabNodeMap.delete(this._key);
    }
  }
}
function $createCollabElementNode(xmlText, parent, type) {
  const collabNode = new CollabElementNode(xmlText, parent, type);
  xmlText._collabNode = collabNode;
  return collabNode;
}

// Stores mappings between Yjs shared types and the Lexical nodes they were last associated with.
class CollabV2Mapping {
  _nodeMap = new Map();
  _sharedTypeToNodeKeys = new Map();
  _nodeKeyToSharedType = new Map();
  set(sharedType, node) {
    const isArray = node instanceof Array;

    // Clear all existing associations for this key.
    this.delete(sharedType);

    // If nodes were associated with other shared types, remove those associations.
    const nodes = isArray ? node : [node];
    for (const n of nodes) {
      const key = n.getKey();
      if (this._nodeKeyToSharedType.has(key)) {
        const otherSharedType = this._nodeKeyToSharedType.get(key);
        const keyIndex = this._sharedTypeToNodeKeys.get(otherSharedType).indexOf(key);
        if (keyIndex !== -1) {
          this._sharedTypeToNodeKeys.get(otherSharedType).splice(keyIndex, 1);
        }
        this._nodeKeyToSharedType.delete(key);
        this._nodeMap.delete(key);
      }
    }
    if (sharedType instanceof XmlText) {
      if (!isArray) {
        formatDevErrorMessage(`Text nodes must be mapped as an array`);
      }
      if (node.length === 0) {
        return;
      }
      this._sharedTypeToNodeKeys.set(sharedType, node.map(n => n.getKey()));
      for (const n of node) {
        this._nodeMap.set(n.getKey(), n);
        this._nodeKeyToSharedType.set(n.getKey(), sharedType);
      }
    } else {
      if (!!isArray) {
        formatDevErrorMessage(`Element nodes must be mapped as a single node`);
      }
      if (!!$isTextNode(node)) {
        formatDevErrorMessage(`Text nodes must be mapped to XmlText`);
      }
      this._sharedTypeToNodeKeys.set(sharedType, [node.getKey()]);
      this._nodeMap.set(node.getKey(), node);
      this._nodeKeyToSharedType.set(node.getKey(), sharedType);
    }
  }
  get(sharedType) {
    const nodes = this._sharedTypeToNodeKeys.get(sharedType);
    if (nodes === undefined) {
      return undefined;
    }
    if (sharedType instanceof XmlText) {
      const arr = Array.from(nodes.map(nodeKey => this._nodeMap.get(nodeKey)));
      return arr.length > 0 ? arr : undefined;
    }
    return this._nodeMap.get(nodes[0]);
  }
  getSharedType(node) {
    return this._nodeKeyToSharedType.get(node.getKey());
  }
  delete(sharedType) {
    const nodeKeys = this._sharedTypeToNodeKeys.get(sharedType);
    if (nodeKeys === undefined) {
      return;
    }
    for (const nodeKey of nodeKeys) {
      this._nodeMap.delete(nodeKey);
      this._nodeKeyToSharedType.delete(nodeKey);
    }
    this._sharedTypeToNodeKeys.delete(sharedType);
  }
  deleteNode(nodeKey) {
    const sharedType = this._nodeKeyToSharedType.get(nodeKey);
    if (sharedType) {
      this.delete(sharedType);
    }
    this._nodeMap.delete(nodeKey);
  }
  has(sharedType) {
    return this._sharedTypeToNodeKeys.has(sharedType);
  }
  clear() {
    this._nodeMap.clear();
    this._sharedTypeToNodeKeys.clear();
    this._nodeKeyToSharedType.clear();
  }
}

function createBaseBinding(editor, id, doc, docMap, excludedProperties) {
  if (!(doc !== undefined && doc !== null)) {
    formatDevErrorMessage(`createBinding: doc is null or undefined`);
  }
  const binding = {
    clientID: doc.clientID,
    cursors: new Map(),
    cursorsContainer: null,
    doc,
    docMap,
    editor,
    excludedProperties: excludedProperties || new Map(),
    id,
    nodeProperties: new Map()
  };
  initializeNodeProperties(binding);
  return binding;
}
function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
  if (!(doc !== undefined && doc !== null)) {
    formatDevErrorMessage(`createBinding: doc is null or undefined`);
  }
  const rootXmlText = doc.get('root', XmlText);
  const root = $createCollabElementNode(rootXmlText, null, 'root');
  root._key = 'root';
  return {
    ...createBaseBinding(editor, id, doc, docMap, excludedProperties),
    collabNodeMap: new Map(),
    root
  };
}
function createBindingV2__EXPERIMENTAL(editor, id, doc, docMap, options = {}) {
  if (!(doc !== undefined && doc !== null)) {
    formatDevErrorMessage(`createBinding: doc is null or undefined`);
  }
  const {
    excludedProperties,
    rootName = 'root-v2'
  } = options;
  return {
    ...createBaseBinding(editor, id, doc, docMap, excludedProperties),
    mapping: new CollabV2Mapping(),
    root: doc.get(rootName, XmlElement)
  };
}
function isBindingV1(binding) {
  return Object.hasOwn(binding, 'collabNodeMap');
}

const baseExcludedProperties = new Set(['__key', '__parent', '__next', '__prev', '__state']);
const elementExcludedProperties = new Set(['__first', '__last', '__size']);
const rootExcludedProperties = new Set(['__cachedText']);
const textExcludedProperties = new Set(['__text']);
function isExcludedProperty(name, node, binding) {
  if (baseExcludedProperties.has(name) || typeof node[name] === 'function') {
    return true;
  }
  if ($isTextNode(node)) {
    if (textExcludedProperties.has(name)) {
      return true;
    }
  } else if ($isElementNode(node)) {
    if (elementExcludedProperties.has(name) || $isRootNode(node) && rootExcludedProperties.has(name)) {
      return true;
    }
  }
  const nodeKlass = node.constructor;
  const excludedProperties = binding.excludedProperties.get(nodeKlass);
  return excludedProperties != null && excludedProperties.has(name);
}
function initializeNodeProperties(binding) {
  const {
    editor,
    nodeProperties
  } = binding;
  editor.update(() => {
    editor._nodes.forEach(nodeInfo => {
      const node = new nodeInfo.klass();
      const defaultProperties = {};
      for (const [property, value] of Object.entries(node)) {
        if (!isExcludedProperty(property, node, binding)) {
          defaultProperties[property] = value;
        }
      }
      nodeProperties.set(node.__type, Object.freeze(defaultProperties));
    });
  });
}
function getDefaultNodeProperties(node, binding) {
  const type = node.__type;
  const {
    nodeProperties
  } = binding;
  const properties = nodeProperties.get(type);
  if (!(properties !== undefined)) {
    formatDevErrorMessage(`Node properties for ${type} not initialized for sync`);
  }
  return properties;
}
function $createCollabNodeFromLexicalNode(binding, lexicalNode, parent) {
  const nodeType = lexicalNode.__type;
  let collabNode;
  if ($isElementNode(lexicalNode)) {
    const xmlText = new XmlText();
    collabNode = $createCollabElementNode(xmlText, parent, nodeType);
    collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
    collabNode.syncChildrenFromLexical(binding, lexicalNode, null, null, null);
  } else if ($isTextNode(lexicalNode)) {
    // TODO create a token text node for token, segmented nodes.
    const map = new Map$1();
    collabNode = $createCollabTextNode(map, lexicalNode.__text, parent, nodeType);
    collabNode.syncPropertiesAndTextFromLexical(binding, lexicalNode, null);
  } else if ($isLineBreakNode(lexicalNode)) {
    const map = new Map$1();
    map.set('__type', 'linebreak');
    collabNode = $createCollabLineBreakNode(map, parent);
  } else if ($isDecoratorNode(lexicalNode)) {
    const xmlElem = new XmlElement();
    collabNode = $createCollabDecoratorNode(xmlElem, parent, nodeType);
    collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
  } else {
    {
      formatDevErrorMessage(`Expected text, element, decorator, or linebreak node`);
    }
  }
  collabNode._key = lexicalNode.__key;
  return collabNode;
}
function getNodeTypeFromSharedType(sharedType) {
  const type = sharedTypeGet(sharedType, '__type');
  if (!(typeof type === 'string' || typeof type === 'undefined')) {
    formatDevErrorMessage(`Expected shared type to include type attribute`);
  }
  return type;
}
function $getOrInitCollabNodeFromSharedType(binding, sharedType, parent) {
  const collabNode = sharedType._collabNode;
  if (collabNode === undefined) {
    const registeredNodes = binding.editor._nodes;
    const type = getNodeTypeFromSharedType(sharedType);
    if (!(typeof type === 'string')) {
      formatDevErrorMessage(`Expected shared type to include type attribute`);
    }
    const nodeInfo = registeredNodes.get(type);
    if (!(nodeInfo !== undefined)) {
      formatDevErrorMessage(`Node ${type} is not registered`);
    }
    const sharedParent = sharedType.parent;
    const targetParent = parent === undefined && sharedParent !== null ? $getOrInitCollabNodeFromSharedType(binding, sharedParent) : parent || null;
    if (!(targetParent instanceof CollabElementNode)) {
      formatDevErrorMessage(`Expected parent to be a collab element node`);
    }
    if (sharedType instanceof XmlText) {
      return $createCollabElementNode(sharedType, targetParent, type);
    } else if (sharedType instanceof Map$1) {
      if (type === 'linebreak') {
        return $createCollabLineBreakNode(sharedType, targetParent);
      }
      return $createCollabTextNode(sharedType, '', targetParent, type);
    } else if (sharedType instanceof XmlElement) {
      return $createCollabDecoratorNode(sharedType, targetParent, type);
    }
  }
  return collabNode;
}
function createLexicalNodeFromCollabNode(binding, collabNode, parentKey) {
  const type = collabNode.getType();
  const registeredNodes = binding.editor._nodes;
  const nodeInfo = registeredNodes.get(type);
  if (!(nodeInfo !== undefined)) {
    formatDevErrorMessage(`Node ${type} is not registered`);
  }
  const lexicalNode = new nodeInfo.klass();
  lexicalNode.__parent = parentKey;
  collabNode._key = lexicalNode.__key;
  if (collabNode instanceof CollabElementNode) {
    const xmlText = collabNode._xmlText;
    collabNode.syncPropertiesFromYjs(binding, null);
    collabNode.applyChildrenYjsDelta(binding, xmlText.toDelta());
    collabNode.syncChildrenFromYjs(binding);
  } else if (collabNode instanceof CollabTextNode) {
    collabNode.syncPropertiesAndTextFromYjs(binding, null);
  } else if (collabNode instanceof CollabDecoratorNode) {
    collabNode.syncPropertiesFromYjs(binding, null);
  }
  binding.collabNodeMap.set(lexicalNode.__key, collabNode);
  return lexicalNode;
}
function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
  const properties = keysChanged === null ? sharedType instanceof Map$1 ? Array.from(sharedType.keys()) : sharedType instanceof XmlText || sharedType instanceof XmlElement ? Object.keys(sharedType.getAttributes()) : Object.keys(sharedType) : Array.from(keysChanged);
  let writableNode;
  for (let i = 0; i < properties.length; i++) {
    const property = properties[i];
    if (isExcludedProperty(property, lexicalNode, binding)) {
      if (property === '__state' && isBindingV1(binding)) {
        if (!writableNode) {
          writableNode = lexicalNode.getWritable();
        }
        $syncNodeStateToLexical(sharedType, writableNode);
      }
      continue;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const prevValue = lexicalNode[property];
    let nextValue = sharedTypeGet(sharedType, property);
    if (prevValue !== nextValue) {
      if (nextValue instanceof Doc) {
        const yjsDocMap = binding.docMap;
        if (prevValue instanceof Doc) {
          yjsDocMap.delete(prevValue.guid);
        }
        const nestedEditor = createEditor();
        const key = nextValue.guid;
        nestedEditor._key = key;
        yjsDocMap.set(key, nextValue);
        nextValue = nestedEditor;
      }
      if (writableNode === undefined) {
        writableNode = lexicalNode.getWritable();
      }

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      writableNode[property] = nextValue;
    }
  }
}
function sharedTypeGet(sharedType, property) {
  if (sharedType instanceof Map$1) {
    return sharedType.get(property);
  } else if (sharedType instanceof XmlText || sharedType instanceof XmlElement) {
    return sharedType.getAttribute(property);
  } else {
    return sharedType[property];
  }
}
function sharedTypeSet(sharedType, property, nextValue) {
  if (sharedType instanceof Map$1) {
    sharedType.set(property, nextValue);
  } else {
    sharedType.setAttribute(property, nextValue);
  }
}
function $syncNodeStateToLexical(sharedType, lexicalNode) {
  const existingState = sharedTypeGet(sharedType, '__state');
  if (!(existingState instanceof Map$1)) {
    return;
  }
  // This should only called when creating the node initially,
  // incremental updates to state come in through YMapEvent
  // with the __state as the target.
  $getWritableNodeState(lexicalNode).updateFromJSON(existingState.toJSON());
}
function syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
  const nextState = nextLexicalNode.__state;
  const existingState = sharedTypeGet(sharedType, '__state');
  if (!nextState) {
    return;
  }
  const [unknown, known] = nextState.getInternalState();
  const prevState = prevLexicalNode && prevLexicalNode.__state;
  const stateMap = existingState instanceof Map$1 ? existingState : new Map$1();
  if (prevState === nextState) {
    return;
  }
  const [prevUnknown, prevKnown] = prevState && stateMap.doc ? prevState.getInternalState() : [undefined, new Map()];
  if (unknown) {
    for (const [k, v] of Object.entries(unknown)) {
      if (prevUnknown && v !== prevUnknown[k]) {
        stateMap.set(k, v);
      }
    }
  }
  for (const [stateConfig, v] of known) {
    if (prevKnown.get(stateConfig) !== v) {
      stateMap.set(stateConfig.key, stateConfig.unparse(v));
    }
  }
  if (!existingState) {
    sharedTypeSet(sharedType, '__state', stateMap);
  }
}
function syncPropertiesFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
  const properties = Object.keys(getDefaultNodeProperties(nextLexicalNode, binding));
  const EditorClass = binding.editor.constructor;
  syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode);
  for (let i = 0; i < properties.length; i++) {
    const property = properties[i];
    const prevValue =
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    prevLexicalNode === null ? undefined : prevLexicalNode[property];
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let nextValue = nextLexicalNode[property];
    if (prevValue !== nextValue) {
      if (nextValue instanceof EditorClass) {
        const yjsDocMap = binding.docMap;
        let prevDoc;
        if (prevValue instanceof EditorClass) {
          const prevKey = prevValue._key;
          prevDoc = yjsDocMap.get(prevKey);
          yjsDocMap.delete(prevKey);
        }

        // If we already have a document, use it.
        const doc = prevDoc || new Doc();
        const key = doc.guid;
        nextValue._key = key;
        yjsDocMap.set(key, doc);
        nextValue = doc;
        // Mark the node dirty as we've assigned a new key to it
        binding.editor.update(() => {
          nextLexicalNode.markDirty();
        });
      }
      sharedTypeSet(sharedType, property, nextValue);
    }
  }
}
function spliceString(str, index, delCount, newText) {
  return str.slice(0, index) + newText + str.slice(index + delCount);
}
function getPositionFromElementAndOffset(node, offset, boundaryIsEdge) {
  let index = 0;
  let i = 0;
  const children = node._children;
  const childrenLength = children.length;
  for (; i < childrenLength; i++) {
    const child = children[i];
    const childOffset = index;
    const size = child.getSize();
    index += size;
    const exceedsBoundary = boundaryIsEdge ? index >= offset : index > offset;
    if (exceedsBoundary && child instanceof CollabTextNode) {
      let textOffset = offset - childOffset - 1;
      if (textOffset < 0) {
        textOffset = 0;
      }
      const diffLength = index - offset;
      return {
        length: diffLength,
        node: child,
        nodeIndex: i,
        offset: textOffset
      };
    }
    if (index > offset) {
      return {
        length: 0,
        node: child,
        nodeIndex: i,
        offset: childOffset
      };
    } else if (i === childrenLength - 1) {
      return {
        length: 0,
        node: null,
        nodeIndex: i + 1,
        offset: childOffset + 1
      };
    }
  }
  return {
    length: 0,
    node: null,
    nodeIndex: 0,
    offset: 0
  };
}
function doesSelectionNeedRecovering(selection) {
  const anchor = selection.anchor;
  const focus = selection.focus;
  let recoveryNeeded = false;
  try {
    const anchorNode = anchor.getNode();
    const focusNode = focus.getNode();
    if (
    // We might have removed a node that no longer exists
    !anchorNode.isAttached() || !focusNode.isAttached() ||
    // If we've split a node, then the offset might not be right
    $isTextNode(anchorNode) && anchor.offset > anchorNode.getTextContentSize() || $isTextNode(focusNode) && focus.offset > focusNode.getTextContentSize()) {
      recoveryNeeded = true;
    }
  } catch (_e) {
    // Sometimes checking nor a node via getNode might trigger
    // an error, so we need recovery then too.
    recoveryNeeded = true;
  }
  return recoveryNeeded;
}
function syncWithTransaction(binding, fn) {
  binding.doc.transact(fn, binding);
}
function $moveSelectionToPreviousNode(anchorNodeKey, currentEditorState) {
  const anchorNode = currentEditorState._nodeMap.get(anchorNodeKey);
  if (!anchorNode) {
    $getRoot().selectStart();
    return;
  }
  // Get previous node
  const prevNodeKey = anchorNode.__prev;
  let prevNode = null;
  if (prevNodeKey) {
    prevNode = $getNodeByKey(prevNodeKey);
  }

  // If previous node not found, get parent node
  if (prevNode === null && anchorNode.__parent !== null) {
    prevNode = $getNodeByKey(anchorNode.__parent);
  }
  if (prevNode === null) {
    $getRoot().selectStart();
    return;
  }
  if (prevNode !== null && prevNode.isAttached()) {
    prevNode.selectEnd();
    return;
  } else {
    // If the found node is also deleted, select the next one
    $moveSelectionToPreviousNode(prevNode.__key, currentEditorState);
  }
}

// https://docs.yjs.dev/api/shared-types/y.xmlelement
// "Define a top-level type; Note that the nodeName is always "undefined""
const isRootElement = el => el.nodeName === 'UNDEFINED';
const $createOrUpdateNodeFromYElement = (el, binding, keysChanged, childListChanged, snapshot, prevSnapshot, computeYChange) => {
  let node = binding.mapping.get(el);
  if (node && keysChanged && keysChanged.size === 0 && !childListChanged) {
    return node;
  }
  const type = isRootElement(el) ? RootNode.getType() : el.nodeName;
  const registeredNodes = binding.editor._nodes;
  const nodeInfo = registeredNodes.get(type);
  if (nodeInfo === undefined) {
    throw new Error(`$createOrUpdateNodeFromYElement: Node ${type} is not registered`);
  }
  if (!node) {
    node = new nodeInfo.klass();
    keysChanged = null;
    childListChanged = true;
  }
  if (childListChanged && node instanceof ElementNode) {
    const children = [];
    const $createChildren = childType => {
      if (childType instanceof XmlElement) {
        const n = $createOrUpdateNodeFromYElement(childType, binding, new Set(), false, snapshot, prevSnapshot, computeYChange);
        if (n !== null) {
          children.push(n);
        }
      } else if (childType instanceof XmlText) {
        const ns = $createOrUpdateTextNodesFromYText(childType, binding, snapshot, prevSnapshot, computeYChange);
        if (ns !== null) {
          ns.forEach(textchild => {
            if (textchild !== null) {
              children.push(textchild);
            }
          });
        }
      } else {
        {
          formatDevErrorMessage(`XmlHook is not supported`);
        }
      }
    };
    if (snapshot === undefined || prevSnapshot === undefined) {
      el.toArray().forEach($createChildren);
    } else {
      typeListToArraySnapshot(el, new Snapshot(prevSnapshot.ds, snapshot.sv)).filter(childType => !childType._item.deleted || isItemVisible(childType._item, snapshot) || isItemVisible(childType._item, prevSnapshot)).forEach($createChildren);
    }
    $spliceChildren(node, children);
  }

  // TODO(collab-v2): typing for XmlElement generic
  const attrs = el.getAttributes(snapshot);
  if (!isRootElement(el) && snapshot !== undefined) {
    if (!isItemVisible(el._item, snapshot)) {
      attrs[stateKeyToAttrKey('ychange')] = computeYChange ? computeYChange('removed', el._item.id) : {
        type: 'removed'
      };
    } else if (!isItemVisible(el._item, prevSnapshot)) {
      attrs[stateKeyToAttrKey('ychange')] = computeYChange ? computeYChange('added', el._item.id) : {
        type: 'added'
      };
    }
  }
  const properties = {
    ...getDefaultNodeProperties(node, binding)
  };
  const state = {};
  for (const k in attrs) {
    if (k.startsWith(STATE_KEY_PREFIX)) {
      state[attrKeyToStateKey(k)] = attrs[k];
    } else {
      properties[k] = attrs[k];
    }
  }
  $syncPropertiesFromYjs(binding, properties, node, keysChanged);
  if (!keysChanged) {
    $getWritableNodeState(node).updateFromJSON(state);
  } else {
    const stateKeysChanged = Object.keys(state).filter(k => keysChanged.has(stateKeyToAttrKey(k)));
    if (stateKeysChanged.length > 0) {
      const writableState = $getWritableNodeState(node);
      for (const k of stateKeysChanged) {
        writableState.updateFromUnknown(k, state[k]);
      }
    }
  }
  const latestNode = node.getLatest();
  binding.mapping.set(el, latestNode);
  return latestNode;
};
const $spliceChildren = (node, nextChildren) => {
  const prevChildren = node.getChildren();
  const prevChildrenKeySet = new Set(prevChildren.map(child => child.getKey()));
  const nextChildrenKeySet = new Set(nextChildren.map(child => child.getKey()));
  const prevEndIndex = prevChildren.length - 1;
  const nextEndIndex = nextChildren.length - 1;
  let prevIndex = 0;
  let nextIndex = 0;
  while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) {
    const prevKey = prevChildren[prevIndex].getKey();
    const nextKey = nextChildren[nextIndex].getKey();
    if (prevKey === nextKey) {
      prevIndex++;
      nextIndex++;
      continue;
    }
    const nextHasPrevKey = nextChildrenKeySet.has(prevKey);
    const prevHasNextKey = prevChildrenKeySet.has(nextKey);
    if (!nextHasPrevKey) {
      // If removing the last node, insert remaining new nodes immediately, otherwise if the node
      // cannot be empty, it will remove itself from its parent.
      if (nextIndex === 0 && node.getChildrenSize() === 1) {
        node.splice(nextIndex, 1, nextChildren.slice(nextIndex));
        return;
      }
      // Remove
      node.splice(nextIndex, 1, []);
      prevIndex++;
      continue;
    }

    // Create or replace
    const nextChildNode = nextChildren[nextIndex];
    if (prevHasNextKey) {
      node.splice(nextIndex, 1, [nextChildNode]);
      prevIndex++;
      nextIndex++;
    } else {
      node.splice(nextIndex, 0, [nextChildNode]);
      nextIndex++;
    }
  }
  const appendNewChildren = prevIndex > prevEndIndex;
  const removeOldChildren = nextIndex > nextEndIndex;
  if (appendNewChildren && !removeOldChildren) {
    node.append(...nextChildren.slice(nextIndex));
  } else if (removeOldChildren && !appendNewChildren) {
    node.splice(nextChildren.length, node.getChildrenSize() - nextChildren.length, []);
  }
};
const isItemVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : snapshot.sv.has(item.id.client) && snapshot.sv.get(item.id.client) > item.id.clock && !isDeleted(snapshot.ds, item.id);
const $createOrUpdateTextNodesFromYText = (text, binding, snapshot, prevSnapshot, computeYChange) => {
  const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);

  // Use existing text nodes if the count and types all align, otherwise throw out the existing
  // nodes and create new ones.
  let nodes = binding.mapping.get(text) ?? [];
  const nodeTypes = deltas.map(delta => delta.attributes.t ?? TextNode.getType());
  const canReuseNodes = nodes.length === nodeTypes.length && nodes.every((node, i) => node.getType() === nodeTypes[i]);
  if (!canReuseNodes) {
    const registeredNodes = binding.editor._nodes;
    nodes = nodeTypes.map(type => {
      const nodeInfo = registeredNodes.get(type);
      if (nodeInfo === undefined) {
        throw new Error(`$createTextNodesFromYText: Node ${type} is not registered`);
      }
      const node = new nodeInfo.klass();
      if (!$isTextNode(node)) {
        throw new Error(`$createTextNodesFromYText: Node ${type} is not a TextNode`);
      }
      return node;
    });
  }

  // Sync text, properties and state to the text nodes.
  for (let i = 0; i < deltas.length; i++) {
    const node = nodes[i];
    const delta = deltas[i];
    const {
      attributes,
      insert
    } = delta;
    if (node.__text !== insert) {
      node.setTextContent(insert);
    }
    const properties = {
      ...getDefaultNodeProperties(node, binding),
      ...attributes.p
    };
    const state = Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)).map(([k, v]) => [attrKeyToStateKey(k), v]));
    $syncPropertiesFromYjs(binding, properties, node, null);
    $getWritableNodeState(node).updateFromJSON(state);
  }
  const latestNodes = nodes.map(node => node.getLatest());
  binding.mapping.set(text, latestNodes);
  return latestNodes;
};
const $createTypeFromTextNodes = (nodes, binding) => {
  const type = new XmlText();
  $updateYText(type, nodes, binding);
  return type;
};
const createTypeFromElementNode = (node, binding) => {
  const type = new XmlElement(node.getType());
  const attrs = {
    ...propertiesToAttributes(node, binding),
    ...stateToAttributes(node)
  };
  for (const key in attrs) {
    const val = attrs[key];
    if (val !== null) {
      // TODO(collab-v2): typing for XmlElement generic
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      type.setAttribute(key, val);
    }
  }
  if (!(node instanceof ElementNode)) {
    return type;
  }
  type.insert(0, normalizeNodeContent(node).map(n => $createTypeFromTextOrElementNode(n, binding)));
  binding.mapping.set(type, node);
  return type;
};
const $createTypeFromTextOrElementNode = (node, meta) => node instanceof Array ? $createTypeFromTextNodes(node, meta) : createTypeFromElementNode(node, meta);
const isObject = val => typeof val === 'object' && val != null;
const equalAttrs = (pattrs, yattrs) => {
  const keys = Object.keys(pattrs).filter(key => pattrs[key] !== null);
  if (yattrs == null) {
    return keys.length === 0;
  }
  let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] !== null).length;
  for (let i = 0; i < keys.length && eq; i++) {
    const key = keys[i];
    const l = pattrs[key];
    const r = yattrs[key];
    eq = key === 'ychange' || l === r || isObject(l) && isObject(r) && equalAttrs(l, r);
  }
  return eq;
};
const normalizeNodeContent = node => {
  if (!(node instanceof ElementNode)) {
    return [];
  }
  const c = node.getChildren();
  const res = [];
  for (let i = 0; i < c.length; i++) {
    const n = c[i];
    if ($isTextNode(n)) {
      const textNodes = [];
      for (let maybeTextNode = c[i]; i < c.length && $isTextNode(maybeTextNode); maybeTextNode = c[++i]) {
        textNodes.push(maybeTextNode);
      }
      i--;
      res.push(textNodes);
    } else {
      res.push(n);
    }
  }
  return res;
};
const equalYTextLText = (ytext, ltexts, binding) => {
  const deltas = toDelta(ytext);
  return deltas.length === ltexts.length && deltas.every((d, i) => {
    const ltext = ltexts[i];
    const type = d.attributes.t ?? TextNode.getType();
    const propertyAttrs = d.attributes.p ?? {};
    const stateAttrs = Object.fromEntries(Object.entries(d.attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)));
    return d.insert === ltext.getTextContent() && type === ltext.getType() && equalAttrs(propertyAttrs, propertiesToAttributes(ltext, binding)) && equalAttrs(stateAttrs, stateToAttributes(ltext));
  });
};
const equalYTypePNode = (ytype, lnode, binding) => {
  if (ytype instanceof XmlElement && !(lnode instanceof Array) && matchNodeName(ytype, lnode)) {
    const normalizedContent = normalizeNodeContent(lnode);
    return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), {
      ...propertiesToAttributes(lnode, binding),
      ...stateToAttributes(lnode)
    }) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i], binding));
  }
  return ytype instanceof XmlText && lnode instanceof Array && equalYTextLText(ytype, lnode, binding);
};
const mappedIdentity = (mapped, lcontent) => mapped === lcontent || mapped instanceof Array && lcontent instanceof Array && mapped.length === lcontent.length && mapped.every((a, i) => lcontent[i] === a);
const computeChildEqualityFactor = (ytype, lnode, binding) => {
  const yChildren = ytype.toArray();
  const pChildren = normalizeNodeContent(lnode);
  const pChildCnt = pChildren.length;
  const yChildCnt = yChildren.length;
  const minCnt = Math.min(yChildCnt, pChildCnt);
  let left = 0;
  let right = 0;
  let foundMappedChild = false;
  for (; left < minCnt; left++) {
    const leftY = yChildren[left];
    const leftP = pChildren[left];
    if (leftY instanceof XmlHook) {
      break;
    } else if (mappedIdentity(binding.mapping.get(leftY), leftP)) {
      foundMappedChild = true; // definite (good) match!
    } else if (!equalYTypePNode(leftY, leftP, binding)) {
      break;
    }
  }
  for (; left + right < minCnt; right++) {
    const rightY = yChildren[yChildCnt - right - 1];
    const rightP = pChildren[pChildCnt - right - 1];
    if (rightY instanceof XmlHook) {
      break;
    } else if (mappedIdentity(binding.mapping.get(rightY), rightP)) {
      foundMappedChild = true;
    } else if (!equalYTypePNode(rightY, rightP, binding)) {
      break;
    }
  }
  return {
    equalityFactor: left + right,
    foundMappedChild
  };
};
const ytextTrans = ytext => {
  let str = '';
  let n = ytext._start;
  const nAttrs = {};
  while (n !== null) {
    if (!n.deleted) {
      if (n.countable && n.content instanceof ContentString) {
        str += n.content.str;
      } else if (n.content instanceof ContentFormat) {
        nAttrs[n.content.key] = null;
      }
    }
    n = n.right;
  }
  return {
    nAttrs,
    str
  };
};
const $updateYText = (ytext, ltexts, binding) => {
  binding.mapping.set(ytext, ltexts);
  const {
    nAttrs,
    str
  } = ytextTrans(ytext);
  const content = ltexts.map((node, i) => {
    const nodeType = node.getType();
    let p = propertiesToAttributes(node, binding);
    if (Object.keys(p).length === 0) {
      p = null;
    }
    return {
      attributes: Object.assign({}, nAttrs, {
        ...(nodeType !== TextNode.getType() && {
          t: nodeType
        }),
        p,
        ...stateToAttributes(node),
        ...(i > 0 && {
          i
        }) // Prevent Yjs from merging text nodes itself.
      }),
      insert: node.getTextContent(),
      nodeKey: node.getKey()
    };
  });
  const nextText = content.map(c => c.insert).join('');
  const selection = $getSelection();
  let cursorOffset;
  if ($isRangeSelection(selection) && selection.isCollapsed()) {
    cursorOffset = 0;
    for (const c of content) {
      if (c.nodeKey === selection.anchor.key) {
        cursorOffset += selection.anchor.offset;
        break;
      }
      cursorOffset += c.insert.length;
    }
  } else {
    cursorOffset = nextText.length;
  }
  const {
    insert,
    remove,
    index
  } = simpleDiffWithCursor(str, nextText, cursorOffset);
  ytext.delete(index, remove);
  ytext.insert(index, insert);
  ytext.applyDelta(content.map(c => ({
    attributes: c.attributes,
    retain: c.insert.length
  })));
};
const toDelta = (ytext, snapshot, prevSnapshot, computeYChange) => {
  return ytext.toDelta(snapshot, prevSnapshot, computeYChange).map(delta => {
    const attributes = delta.attributes ?? {};
    if ('ychange' in attributes) {
      attributes[stateKeyToAttrKey('ychange')] = attributes.ychange;
      delete attributes.ychange;
    }
    return {
      ...delta,
      attributes
    };
  });
};
const propertiesToAttributes = (node, meta) => {
  const defaultProperties = getDefaultNodeProperties(node, meta);
  const attrs = {};
  Object.entries(defaultProperties).forEach(([property, defaultValue]) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const value = node[property];
    if (value !== defaultValue) {
      attrs[property] = value;
    }
  });
  return attrs;
};
const STATE_KEY_PREFIX = 's_';
const stateKeyToAttrKey = key => `s_${key}`;
const attrKeyToStateKey = key => {
  if (!key.startsWith(STATE_KEY_PREFIX)) {
    throw new Error(`Invalid state key: ${key}`);
  }
  return key.slice(STATE_KEY_PREFIX.length);
};
const stateToAttributes = node => {
  const state = node.__state;
  if (!state) {
    return {};
  }
  const [unknown = {}, known] = state.getInternalState();
  const attrs = {};
  for (const [k, v] of Object.entries(unknown)) {
    attrs[stateKeyToAttrKey(k)] = v;
  }
  for (const [stateConfig, v] of known) {
    attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
  }
  return attrs;
};
const $updateYFragment = (y, yDomFragment, node, binding, dirtyElements) => {
  if (yDomFragment instanceof XmlElement && yDomFragment.nodeName !== node.getType() && !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())) {
    throw new Error('node name mismatch!');
  }
  binding.mapping.set(yDomFragment, node);
  // update attributes
  if (yDomFragment instanceof XmlElement) {
    const yDomAttrs = yDomFragment.getAttributes();
    const lexicalAttrs = {
      ...propertiesToAttributes(node, binding),
      ...stateToAttributes(node)
    };
    for (const key in lexicalAttrs) {
      if (lexicalAttrs[key] != null) {
        const isEqual = yDomAttrs[key] === lexicalAttrs[key] ||
        // deep equality check so we don't sync properties/state with object values every update
        isObject(yDomAttrs[key]) && isObject(lexicalAttrs[key]) && equalAttrs(yDomAttrs[key], lexicalAttrs[key]);
        if (!isEqual && key !== 'ychange') {
          // TODO(collab-v2): typing for XmlElement generic
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          yDomFragment.setAttribute(key, lexicalAttrs[key]);
        }
      } else {
        yDomFragment.removeAttribute(key);
      }
    }
    // remove all keys that are no longer in pAttrs
    for (const key in yDomAttrs) {
      if (lexicalAttrs[key] === undefined) {
        yDomFragment.removeAttribute(key);
      }
    }
  }
  // update children
  const lChildren = normalizeNodeContent(node);
  const lChildCnt = lChildren.length;
  const yChildren = yDomFragment.toArray();
  const yChildCnt = yChildren.length;
  const minCnt = Math.min(lChildCnt, yChildCnt);
  let left = 0;
  let right = 0;
  // find number of matching elements from left
  for (; left < minCnt; left++) {
    const leftY = yChildren[left];
    const leftL = lChildren[left];
    if (leftY instanceof XmlHook) {
      break;
    } else if (mappedIdentity(binding.mapping.get(leftY), leftL)) {
      if (leftL instanceof ElementNode && dirtyElements.has(leftL.getKey())) {
        $updateYFragment(y, leftY, leftL, binding, dirtyElements);
      }
    } else if (equalYTypePNode(leftY, leftL, binding)) {
      // update mapping
      binding.mapping.set(leftY, leftL);
    } else {
      break;
    }
  }
  // find number of matching elements from right
  for (; right + left < minCnt; right++) {
    const rightY = yChildren[yChildCnt - right - 1];
    const rightL = lChildren[lChildCnt - right - 1];
    if (rightY instanceof XmlHook) {
      break;
    } else if (mappedIdentity(binding.mapping.get(rightY), rightL)) {
      if (rightL instanceof ElementNode && dirtyElements.has(rightL.getKey())) {
        $updateYFragment(y, rightY, rightL, binding, dirtyElements);
      }
    } else if (equalYTypePNode(rightY, rightL, binding)) {
      // update mapping
      binding.mapping.set(rightY, rightL);
    } else {
      break;
    }
  }
  // try to compare and update
  while (yChildCnt - left - right > 0 && lChildCnt - left - right > 0) {
    const leftY = yChildren[left];
    const leftL = lChildren[left];
    const rightY = yChildren[yChildCnt - right - 1];
    const rightL = lChildren[lChildCnt - right - 1];
    if (leftY instanceof XmlText && leftL instanceof Array) {
      if (!equalYTextLText(leftY, leftL, binding)) {
        $updateYText(leftY, leftL, binding);
      }
      left += 1;
    } else {
      let updateLeft = leftY instanceof XmlElement && matchNodeName(leftY, leftL);
      let updateRight = rightY instanceof XmlElement && matchNodeName(rightY, rightL);
      if (updateLeft && updateRight) {
        // decide which which element to update
        const equalityLeft = computeChildEqualityFactor(leftY, leftL, binding);
        const equalityRight = computeChildEqualityFactor(rightY, rightL, binding);
        if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
          updateRight = false;
        } else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
          updateLeft = false;
        } else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
          updateLeft = false;
        } else {
          updateRight = false;
        }
      }
      if (updateLeft) {
        $updateYFragment(y, leftY, leftL, binding, dirtyElements);
        left += 1;
      } else if (updateRight) {
        $updateYFragment(y, rightY, rightL, binding, dirtyElements);
        right += 1;
      } else {
        binding.mapping.delete(yDomFragment.get(left));
        yDomFragment.delete(left, 1);
        yDomFragment.insert(left, [$createTypeFromTextOrElementNode(leftL, binding)]);
        left += 1;
      }
    }
  }
  const yDelLen = yChildCnt - left - right;
  if (yChildCnt === 1 && lChildCnt === 0 && yChildren[0] instanceof XmlText) {
    binding.mapping.delete(yChildren[0]);
    // Edge case handling https://github.com/yjs/y-prosemirror/issues/108
    // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
    yChildren[0].delete(0, yChildren[0].length);
  } else if (yDelLen > 0) {
    yDomFragment.slice(left, left + yDelLen).forEach(type => binding.mapping.delete(type));
    yDomFragment.delete(left, yDelLen);
  }
  if (left + right < lChildCnt) {
    const ins = [];
    for (let i = left; i < lChildCnt - right; i++) {
      ins.push($createTypeFromTextOrElementNode(lChildren[i], binding));
    }
    yDomFragment.insert(left, ins);
  }
};
const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();

const STATE_KEY = 'ychange';

// eslint-disable-next-line @typescript-eslint/no-explicit-any

const ychangeState = createState(STATE_KEY, {
  isEqual: (a, b) => a === b,
  parse: value => value ?? null
});
function $getYChangeState(node) {
  return $getState(node, ychangeState);
}

// Not exposing $setState because it should only be created by SyncV2.ts.

/**
 * Replaces the editor content with a view that compares the state between two given snapshots.
 * Any added or removed nodes between the two snapshots will have {@link YChange} attached to them.
 *
 * @param binding Yjs binding
 * @param snapshot Ending snapshot state (default: current state of the Yjs document)
 * @param prevSnapshot Starting snapshot state (default: empty snapshot)
 */
const renderSnapshot__EXPERIMENTAL = (binding, snapshot$1 = snapshot(binding.doc), prevSnapshot = emptySnapshot) => {
  // The document that contains the full history of this document.
  const {
    doc
  } = binding;
  if (!!doc.gc) {
    formatDevErrorMessage(`GC must be disabled to render snapshot`);
  }
  doc.transact(transaction => {
    // Before rendering, we are going to sanitize ops and split deleted ops
    // if they were deleted by seperate users.
    const pud = new PermanentUserData(doc);
    if (pud) {
      pud.dss.forEach(ds => {
        iterateDeletedStructs(transaction, ds, _item => {});
      });
    }
    const computeYChange = (type, id) => {
      const user = type === 'added' ? pud.getUserByClientId(id.client) : pud.getUserByDeletedId(id);
      return {
        id,
        type,
        user: user ?? null
      };
    };
    binding.mapping.clear();
    binding.editor.update(() => {
      $getRoot().clear();
      $createOrUpdateNodeFromYElement(binding.root, binding, null, true, snapshot$1, prevSnapshot, computeYChange);
    });
  }, binding);
};

function createRelativePosition(point, binding) {
  const collabNodeMap = binding.collabNodeMap;
  const collabNode = collabNodeMap.get(point.key);
  if (collabNode === undefined) {
    return null;
  }
  let offset = point.offset;
  let sharedType = collabNode.getSharedType();
  if (collabNode instanceof CollabTextNode) {
    sharedType = collabNode._parent._xmlText;
    const currentOffset = collabNode.getOffset();
    if (currentOffset === -1) {
      return null;
    }
    offset = currentOffset + 1 + offset;
  } else if (collabNode instanceof CollabElementNode && point.type === 'element') {
    const parent = point.getNode();
    if (!$isElementNode(parent)) {
      formatDevErrorMessage(`Element point must be an element node`);
    }
    let accumulatedOffset = 0;
    let i = 0;
    let node = parent.getFirstChild();
    while (node !== null && i++ < offset) {
      if ($isTextNode(node)) {
        accumulatedOffset += node.getTextContentSize() + 1;
      } else {
        accumulatedOffset++;
      }
      node = node.getNextSibling();
    }
    offset = accumulatedOffset;
  }
  return createRelativePositionFromTypeIndex(sharedType, offset);
}
function createRelativePositionV2(point, binding) {
  const {
    mapping
  } = binding;
  const {
    offset
  } = point;
  const node = point.getNode();
  const yType = mapping.getSharedType(node);
  if (yType === undefined) {
    return null;
  }
  if (point.type === 'text') {
    if (!$isTextNode(node)) {
      formatDevErrorMessage(`Text point must be a text node`);
    }
    let prevSibling = node.getPreviousSibling();
    let adjustedOffset = offset;
    while ($isTextNode(prevSibling)) {
      adjustedOffset += prevSibling.getTextContentSize();
      prevSibling = prevSibling.getPreviousSibling();
    }
    return createRelativePositionFromTypeIndex(yType, adjustedOffset);
  } else if (point.type === 'element') {
    if (!$isElementNode(node)) {
      formatDevErrorMessage(`Element point must be an element node`);
    }
    let i = 0;
    let child = node.getFirstChild();
    while (child !== null && i < offset) {
      if ($isTextNode(child)) {
        let nextSibling = child.getNextSibling();
        while ($isTextNode(nextSibling)) {
          nextSibling = nextSibling.getNextSibling();
        }
      }
      i++;
      child = child.getNextSibling();
    }
    return createRelativePositionFromTypeIndex(yType, i);
  }
  return null;
}
function createAbsolutePosition(relativePosition, binding) {
  return createAbsolutePositionFromRelativePosition(relativePosition, binding.doc);
}
function shouldUpdatePosition(currentPos, pos) {
  if (currentPos == null) {
    if (pos != null) {
      return true;
    }
  } else if (pos == null || !compareRelativePositions(currentPos, pos)) {
    return true;
  }
  return false;
}
function createCursor(name, color) {
  return {
    color: color,
    name: name,
    selection: null
  };
}
function destroySelection(binding, selection) {
  const cursorsContainer = binding.cursorsContainer;
  if (cursorsContainer !== null) {
    const selections = selection.selections;
    const selectionsLength = selections.length;
    for (let i = 0; i < selectionsLength; i++) {
      cursorsContainer.removeChild(selections[i]);
    }
  }
}
function destroyCursor(binding, cursor) {
  const selection = cursor.selection;
  if (selection !== null) {
    destroySelection(binding, selection);
  }
}
function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, theme = {}) {
  const color = cursor.color;
  const caret = document.createElement('span');
  if (theme.cursor) {
    caret.className = theme.cursor;
    caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;`;
    caret.style.setProperty('--lexical-cursor-color', color);
  } else {
    caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;width:1px;background-color:${color};z-index:10;`;
  }
  const name = document.createElement('span');
  name.textContent = cursor.name;
  if (theme.cursorName) {
    name.className = theme.cursorName;
  } else {
    name.style.cssText = `position:absolute;left:-2px;top:-16px;background-color:${color};color:#fff;line-height:12px;font-size:12px;padding:2px;font-family:Arial;font-weight:bold;white-space:nowrap;`;
  }
  caret.appendChild(name);
  return {
    anchor: {
      key: anchorKey,
      offset: anchorOffset
    },
    caret,
    color,
    focus: {
      key: focusKey,
      offset: focusOffset
    },
    name,
    selections: []
  };
}
function updateCursor(binding, cursor, nextSelection, nodeMap, theme = {}) {
  const editor = binding.editor;
  const rootElement = editor.getRootElement();
  const cursorsContainer = binding.cursorsContainer;
  if (cursorsContainer === null || rootElement === null) {
    return;
  }
  const cursorsContainerOffsetParent = cursorsContainer.offsetParent;
  if (cursorsContainerOffsetParent === null) {
    return;
  }
  const containerRect = cursorsContainerOffsetParent.getBoundingClientRect();
  const prevSelection = cursor.selection;
  if (nextSelection === null) {
    if (prevSelection === null) {
      return;
    } else {
      cursor.selection = null;
      destroySelection(binding, prevSelection);
      return;
    }
  } else {
    cursor.selection = nextSelection;
  }
  const caret = nextSelection.caret;
  const color = nextSelection.color;
  const selections = nextSelection.selections;
  const anchor = nextSelection.anchor;
  const focus = nextSelection.focus;
  const anchorKey = anchor.key;
  const focusKey = focus.key;
  const anchorNode = nodeMap.get(anchorKey);
  const focusNode = nodeMap.get(focusKey);
  if (anchorNode == null || focusNode == null) {
    return;
  }
  let selectionRects;

  // In the case of a collapsed selection on a linebreak, we need
  // to improvise as the browser will return nothing here as <br>
  // apparently take up no visual space :/
  // This won't work in all cases, but it's better than just showing
  // nothing all the time.
  if (anchorNode === focusNode && $isLineBreakNode(anchorNode)) {
    const brRect = editor.getElementByKey(anchorKey).getBoundingClientRect();
    selectionRects = [brRect];
  } else {
    const range = createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset);
    if (range === null) {
      return;
    }
    selectionRects = createRectsFromDOMRange(editor, range);
  }
  const selectionsLength = selections.length;
  const selectionRectsLength = selectionRects.length;
  for (let i = 0; i < selectionRectsLength; i++) {
    const selectionRect = selectionRects[i];
    let selection = selections[i];
    if (selection === undefined) {
      selection = document.createElement('span');
      selections[i] = selection;
      const selectionBg = document.createElement('span');
      if (theme.selectionBg) {
        selectionBg.className = theme.selectionBg;
      }
      selection.appendChild(selectionBg);
      cursorsContainer.appendChild(selection);
    }
    const top = selectionRect.top - containerRect.top;
    const left = selectionRect.left - containerRect.left;
    const posStyle = `position:absolute;top:${top}px;left:${left}px;height:${selectionRect.height}px;width:${selectionRect.width}px;pointer-events:none;`;
    if (theme.selection) {
      selection.className = theme.selection;
      selection.style.cssText = posStyle;
      selection.style.setProperty('--lexical-cursor-color', color);
      selection.firstChild.style.cssText = `position:absolute;left:0;top:0;width:100%;height:100%;`;
    } else {
      selection.style.cssText = posStyle;
      selection.firstChild.style.cssText = `${posStyle}left:0;top:0;background-color:${color};opacity:0.3;z-index:5;`;
    }
    if (i === selectionRectsLength - 1) {
      if (caret.parentNode !== selection) {
        selection.appendChild(caret);
      }
    }
  }
  for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) {
    const selection = selections[i];
    cursorsContainer.removeChild(selection);
    selections.pop();
  }
}
/**
 * @deprecated Use `$getAnchorAndFocusForUserState` instead.
 */
function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
  const {
    anchorPos,
    focusPos
  } = userState;
  let anchorCollabNode = null;
  let anchorOffset = 0;
  let focusCollabNode = null;
  let focusOffset = 0;
  if (anchorPos !== null && focusPos !== null) {
    const anchorAbsPos = createAbsolutePosition(anchorPos, binding);
    const focusAbsPos = createAbsolutePosition(focusPos, binding);
    if (anchorAbsPos !== null && focusAbsPos !== null) {
      [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
      [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
    }
  }
  return {
    anchorCollabNode,
    anchorOffset,
    focusCollabNode,
    focusOffset
  };
}
function $getAnchorAndFocusForUserState(binding, userState) {
  const {
    anchorPos,
    focusPos
  } = userState;
  const anchorAbsPos = anchorPos ? createAbsolutePosition(anchorPos, binding) : null;
  const focusAbsPos = focusPos ? createAbsolutePosition(focusPos, binding) : null;
  if (anchorAbsPos === null || focusAbsPos === null) {
    return {
      anchorKey: null,
      anchorOffset: 0,
      focusKey: null,
      focusOffset: 0
    };
  }
  if (isBindingV1(binding)) {
    const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
    const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
    return {
      anchorKey: anchorCollabNode !== null ? anchorCollabNode.getKey() : null,
      anchorOffset,
      focusKey: focusCollabNode !== null ? focusCollabNode.getKey() : null,
      focusOffset
    };
  }
  let [anchorNode, anchorOffset] = $getNodeAndOffsetV2(binding.mapping, anchorAbsPos);
  let [focusNode, focusOffset] = $getNodeAndOffsetV2(binding.mapping, focusAbsPos);
  // For a non-collapsed selection, if the start of the selection is as the end of a text node,
  // move it to the beginning of the next text node (if one exists).
  if (focusNode && anchorNode && (focusNode !== anchorNode || focusOffset !== anchorOffset)) {
    const isBackwards = focusNode.isBefore(anchorNode);
    const startNode = isBackwards ? focusNode : anchorNode;
    const startOffset = isBackwards ? focusOffset : anchorOffset;
    if ($isTextNode(startNode) && $isTextNode(startNode.getNextSibling()) && startOffset === startNode.getTextContentSize()) {
      if (isBackwards) {
        focusNode = startNode.getNextSibling();
        focusOffset = 0;
      } else {
        anchorNode = startNode.getNextSibling();
        anchorOffset = 0;
      }
    }
  }
  return {
    anchorKey: anchorNode !== null ? anchorNode.getKey() : null,
    anchorOffset,
    focusKey: focusNode !== null ? focusNode.getKey() : null,
    focusOffset
  };
}
function $syncLocalCursorPosition(binding, provider) {
  const awareness = provider.awareness;
  const localState = awareness.getLocalState();
  if (localState === null) {
    return;
  }
  const {
    anchorKey,
    anchorOffset,
    focusKey,
    focusOffset
  } = $getAnchorAndFocusForUserState(binding, localState);
  if (anchorKey !== null && focusKey !== null) {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return;
    }
    $setPoint(selection.anchor, anchorKey, anchorOffset);
    $setPoint(selection.focus, focusKey, focusOffset);
  }
}
function $setPoint(point, key, offset) {
  if (point.key !== key || point.offset !== offset) {
    let anchorNode = $getNodeByKey(key);
    if (anchorNode !== null && !$isElementNode(anchorNode) && !$isTextNode(anchorNode)) {
      const parent = anchorNode.getParentOrThrow();
      key = parent.getKey();
      offset = anchorNode.getIndexWithinParent();
      anchorNode = parent;
    }
    point.set(key, offset, $isElementNode(anchorNode) ? 'element' : 'text');
  }
}
function getCollabNodeAndOffset(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sharedType, offset) {
  const collabNode = sharedType._collabNode;
  if (collabNode === undefined) {
    return [null, 0];
  }
  if (collabNode instanceof CollabElementNode) {
    const {
      node,
      offset: collabNodeOffset
    } = getPositionFromElementAndOffset(collabNode, offset, true);
    if (node === null) {
      return [collabNode, 0];
    } else {
      return [node, collabNodeOffset];
    }
  }
  return [null, 0];
}
function $getNodeAndOffsetV2(mapping, absolutePosition) {
  const yType = absolutePosition.type;
  const yOffset = absolutePosition.index;
  if (yType instanceof XmlElement) {
    const node = mapping.get(yType);
    if (node === undefined) {
      return [null, 0];
    }
    if (!$isElementNode(node)) {
      return [node, yOffset];
    }
    let remainingYOffset = yOffset;
    let lexicalOffset = 0;
    const children = node.getChildren();
    while (remainingYOffset > 0 && lexicalOffset < children.length) {
      const child = children[lexicalOffset];
      remainingYOffset -= 1;
      lexicalOffset += 1;
      if ($isTextNode(child)) {
        while (lexicalOffset < children.length && $isTextNode(children[lexicalOffset])) {
          lexicalOffset += 1;
        }
      }
    }
    return [node, lexicalOffset];
  } else {
    const nodes = mapping.get(yType);
    if (nodes === undefined) {
      return [null, 0];
    }
    let i = 0;
    let adjustedOffset = yOffset;
    while (adjustedOffset > nodes[i].getTextContentSize() && i + 1 < nodes.length) {
      adjustedOffset -= nodes[i].getTextContentSize();
      i++;
    }
    const textNode = nodes[i];
    return [textNode, Math.min(adjustedOffset, textNode.getTextContentSize())];
  }
}
function getAwarenessStatesDefault(_binding, provider) {
  return provider.awareness.getStates();
}
function syncCursorPositions(binding, provider, options) {
  const {
    getAwarenessStates = getAwarenessStatesDefault
  } = options ?? {};
  const awarenessStates = Array.from(getAwarenessStates(binding, provider));
  const localClientID = binding.clientID;
  const cursors = binding.cursors;
  const editor = binding.editor;
  const collabTheme = editor._config.theme.collaboration;
  const nodeMap = editor._editorState._nodeMap;
  const visitedClientIDs = new Set();
  for (let i = 0; i < awarenessStates.length; i++) {
    const awarenessState = awarenessStates[i];
    const [clientID, awareness] = awarenessState;
    if (clientID !== 0 && clientID !== localClientID) {
      visitedClientIDs.add(clientID);
      const {
        name,
        color,
        focusing
      } = awareness;
      let selection = null;
      let cursor = cursors.get(clientID);
      if (cursor === undefined) {
        cursor = createCursor(name, color);
        cursors.set(clientID, cursor);
      }
      if (focusing) {
        const {
          anchorKey,
          anchorOffset,
          focusKey,
          focusOffset
        } = editor.read(() => $getAnchorAndFocusForUserState(binding, awareness));
        if (anchorKey !== null && focusKey !== null) {
          selection = cursor.selection;
          if (selection === null) {
            selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, collabTheme);
          } else {
            const anchor = selection.anchor;
            const focus = selection.focus;
            anchor.key = anchorKey;
            anchor.offset = anchorOffset;
            focus.key = focusKey;
            focus.offset = focusOffset;
          }
        }
      }
      updateCursor(binding, cursor, selection, nodeMap, collabTheme);
    }
  }
  const allClientIDs = Array.from(cursors.keys());
  for (let i = 0; i < allClientIDs.length; i++) {
    const clientID = allClientIDs[i];
    if (!visitedClientIDs.has(clientID)) {
      const cursor = cursors.get(clientID);
      if (cursor !== undefined) {
        destroyCursor(binding, cursor);
        cursors.delete(clientID);
      }
    }
  }
}
function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelection) {
  const awareness = provider.awareness;
  const localState = awareness.getLocalState();
  if (localState === null) {
    return;
  }
  const {
    anchorPos: currentAnchorPos,
    focusPos: currentFocusPos,
    name,
    color,
    focusing,
    awarenessData
  } = localState;
  let anchorPos = null;
  let focusPos = null;
  if (nextSelection === null || currentAnchorPos !== null && !nextSelection.is(prevSelection)) {
    if (prevSelection === null) {
      return;
    }
  }
  if ($isRangeSelection(nextSelection)) {
    if (isBindingV1(binding)) {
      anchorPos = createRelativePosition(nextSelection.anchor, binding);
      focusPos = createRelativePosition(nextSelection.focus, binding);
    } else {
      anchorPos = createRelativePositionV2(nextSelection.anchor, binding);
      focusPos = createRelativePositionV2(nextSelection.focus, binding);
    }
  }
  if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) {
    awareness.setLocalState({
      ...localState,
      anchorPos,
      awarenessData,
      color,
      focusPos,
      focusing,
      name
    });
  }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function $syncStateEvent(binding, event) {
  const {
    target
  } = event;
  if (!(target._item && target._item.parentSub === '__state' && getNodeTypeFromSharedType(target) === undefined && (target.parent instanceof XmlText || target.parent instanceof XmlElement || target.parent instanceof Map$1))) {
    // TODO there might be a case to handle in here when a YMap
    // is used as a value  of __state? It would probably be desirable
    // to mark the node as dirty when that happens.
    return false;
  }
  const collabNode = $getOrInitCollabNodeFromSharedType(binding, target.parent);
  const node = collabNode.getNode();
  if (node) {
    const state = $getWritableNodeState(node.getWritable());
    for (const k of event.keysChanged) {
      state.updateFromUnknown(k, target.get(k));
    }
  }
  return true;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function $syncEvent(binding, event) {
  if (event instanceof YMapEvent && $syncStateEvent(binding, event)) {
    return;
  }
  const {
    target
  } = event;
  const collabNode = $getOrInitCollabNodeFromSharedType(binding, target);
  if (collabNode instanceof CollabElementNode && event instanceof YTextEvent) {
    // @ts-expect-error We need to access the private childListChanged property of the class
    const {
      keysChanged,
      childListChanged,
      delta
    } = event;

    // Update
    if (keysChanged.size > 0) {
      collabNode.syncPropertiesFromYjs(binding, keysChanged);
    }
    if (childListChanged) {
      collabNode.applyChildrenYjsDelta(binding, delta);
      collabNode.syncChildrenFromYjs(binding);
    }
  } else if (collabNode instanceof CollabTextNode && event instanceof YMapEvent) {
    const {
      keysChanged
    } = event;

    // Update
    if (keysChanged.size > 0) {
      collabNode.syncPropertiesAndTextFromYjs(binding, keysChanged);
    }
  } else if (collabNode instanceof CollabDecoratorNode && event instanceof YXmlEvent) {
    const {
      attributesChanged
    } = event;

    // Update
    if (attributesChanged.size > 0) {
      collabNode.syncPropertiesFromYjs(binding, attributesChanged);
    }
  } else {
    {
      formatDevErrorMessage(`Expected text, element, or decorator event`);
    }
  }
}
function syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, syncCursorPositionsFn = syncCursorPositions) {
  const editor = binding.editor;
  const currentEditorState = editor._editorState;

  // This line precompute the delta before editor update. The reason is
  // delta is computed when it is accessed. Note that this can only be
  // safely computed during the event call. If it is accessed after event
  // call it might result in unexpected behavior.
  // https://github.com/yjs/yjs/blob/00ef472d68545cb260abd35c2de4b3b78719c9e4/src/utils/YEvent.js#L132
  events.forEach(event => event.delta);
  editor.update(() => {
    for (let i = 0; i < events.length; i++) {
      const event = events[i];
      $syncEvent(binding, event);
    }
    $syncCursorFromYjs(currentEditorState, binding, provider);
    if (!isFromUndoManger) {
      // If it is an external change, we don't want the current scroll position to get changed
      // since the user might've intentionally scrolled somewhere else in the document.
      $addUpdateTag(SKIP_SCROLL_INTO_VIEW_TAG);
    }
  }, {
    onUpdate: () => {
      syncCursorPositionsFn(binding, provider);
      editor.update(() => $ensureEditorNotEmpty());
    },
    skipTransforms: true,
    tag: isFromUndoManger ? HISTORIC_TAG : COLLABORATION_TAG
  });
}
function $syncCursorFromYjs(editorState, binding, provider) {
  const selection = $getSelection();
  if ($isRangeSelection(selection)) {
    if (doesSelectionNeedRecovering(selection)) {
      const prevSelection = editorState._selection;
      if ($isRangeSelection(prevSelection)) {
        $syncLocalCursorPosition(binding, provider);
        if (doesSelectionNeedRecovering(selection)) {
          // If the selected node is deleted, move the selection to the previous or parent node.
          const anchorNodeKey = selection.anchor.key;
          $moveSelectionToPreviousNode(anchorNodeKey, editorState);
        }
      }
      syncLexicalSelectionToYjs(binding, provider, prevSelection, $getSelection());
    } else {
      $syncLocalCursorPosition(binding, provider);
    }
  }
}
function $handleNormalizationMergeConflicts(binding, normalizedNodes) {
  // We handle the merge operations here
  const normalizedNodesKeys = Array.from(normalizedNodes);
  const collabNodeMap = binding.collabNodeMap;
  const mergedNodes = [];
  const removedNodes = [];
  for (let i = 0; i < normalizedNodesKeys.length; i++) {
    const nodeKey = normalizedNodesKeys[i];
    const lexicalNode = $getNodeByKey(nodeKey);
    const collabNode = collabNodeMap.get(nodeKey);
    if (collabNode instanceof CollabTextNode) {
      if ($isTextNode(lexicalNode)) {
        // We mutate the text collab nodes after removing
        // all the dead nodes first, otherwise offsets break.
        mergedNodes.push([collabNode, lexicalNode.__text]);
      } else {
        const offset = collabNode.getOffset();
        if (offset === -1) {
          continue;
        }
        const parent = collabNode._parent;
        collabNode._normalized = true;
        parent._xmlText.delete(offset, 1);
        removedNodes.push(collabNode);
      }
    }
  }
  for (let i = 0; i < removedNodes.length; i++) {
    const collabNode = removedNodes[i];
    const nodeKey = collabNode.getKey();
    collabNodeMap.delete(nodeKey);
    const parentChildren = collabNode._parent._children;
    const index = parentChildren.indexOf(collabNode);
    parentChildren.splice(index, 1);
  }
  for (let i = 0; i < mergedNodes.length; i++) {
    const [collabNode, text] = mergedNodes[i];
    collabNode._text = text;
  }
}

// If there was a collision on the top level paragraph
// we need to re-add a paragraph. To ensure this insertion properly syncs with other clients,
// it must be placed outside of the update block above that has tags 'collaboration' or 'historic'.
function $ensureEditorNotEmpty() {
  if ($getRoot().getChildrenSize() === 0) {
    $getRoot().append($createParagraphNode());
  }
}
function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorState, dirtyElements, dirtyLeaves, normalizedNodes, tags) {
  syncWithTransaction(binding, () => {
    currEditorState.read(() => {
      // We check if the update has come from a origin where the origin
      // was the collaboration binding previously. This can help us
      // prevent unnecessarily re-diffing and possible re-applying
      // the same change editor state again. For example, if a user
      // types a character and we get it, we don't want to then insert
      // the same character again. The exception to this heuristic is
      // when we need to handle normalization merge conflicts.
      if (tags.has(COLLABORATION_TAG) || tags.has(HISTORIC_TAG)) {
        if (normalizedNodes.size > 0) {
          $handleNormalizationMergeConflicts(binding, normalizedNodes);
        }
        return;
      }
      if (dirtyElements.has('root')) {
        const prevNodeMap = prevEditorState._nodeMap;
        const nextLexicalRoot = $getRoot();
        const collabRoot = binding.root;
        collabRoot.syncPropertiesFromLexical(binding, nextLexicalRoot, prevNodeMap);
        collabRoot.syncChildrenFromLexical(binding, nextLexicalRoot, prevNodeMap, dirtyElements, dirtyLeaves);
      }
      const selection = $getSelection();
      const prevSelection = prevEditorState._selection;
      syncLexicalSelectionToYjs(binding, provider, prevSelection, selection);
    });
  });
}
function $syncEventV2(binding, event) {
  const {
    target
  } = event;
  if (target instanceof XmlElement && event instanceof YXmlEvent) {
    $createOrUpdateNodeFromYElement(target, binding, event.attributesChanged,
    // @ts-expect-error childListChanged is private
    event.childListChanged);
  } else if (target instanceof XmlText && event instanceof YTextEvent) {
    const parent = target.parent;
    if (parent instanceof XmlElement) {
      // Need to sync via parent element in order to attach new next nodes.
      $createOrUpdateNodeFromYElement(parent, binding, new Set(), true);
    } else {
      {
        formatDevErrorMessage(`Expected XmlElement parent for XmlText`);
      }
    }
  } else {
    {
      formatDevErrorMessage(`Expected xml or text event`);
    }
  }
}
function syncYjsChangesToLexicalV2__EXPERIMENTAL(binding, provider, events, transaction, isFromUndoManger) {
  const editor = binding.editor;
  const editorState = editor._editorState;

  // Remove deleted nodes from the mapping
  iterateDeletedStructs(transaction, transaction.deleteSet, struct => {
    if (struct.constructor === Item) {
      const content = struct.content;
      const type = content.type;
      if (type) {
        binding.mapping.delete(type);
      }
    }
  });

  // This line precompute the delta before editor update. The reason is
  // delta is computed when it is accessed. Note that this can only be
  // safely computed during the event call. If it is accessed after event
  // call it might result in unexpected behavior.
  // https://github.com/yjs/yjs/blob/00ef472d68545cb260abd35c2de4b3b78719c9e4/src/utils/YEvent.js#L132
  events.forEach(event => event.delta);
  editor.update(() => {
    for (let i = 0; i < events.length; i++) {
      const event = events[i];
      $syncEventV2(binding, event);
    }
    $syncCursorFromYjs(editorState, binding, provider);
    if (!isFromUndoManger) {
      // If it is an external change, we don't want the current scroll position to get changed
      // since the user might've intentionally scrolled somewhere else in the document.
      $addUpdateTag(SKIP_SCROLL_INTO_VIEW_TAG);
    }
  }, {
    // Need any text node normalization to be synchronously updated back to Yjs, otherwise the
    // binding.mapping will get out of sync.
    discrete: true,
    onUpdate: () => {
      syncCursorPositions(binding, provider);
      editor.update(() => $ensureEditorNotEmpty());
    },
    skipTransforms: true,
    tag: isFromUndoManger ? HISTORIC_TAG : COLLABORATION_TAG
  });
}
function syncYjsStateToLexicalV2__EXPERIMENTAL(binding, provider) {
  binding.mapping.clear();
  const editor = binding.editor;
  editor.update(() => {
    $getRoot().clear();
    $createOrUpdateNodeFromYElement(binding.root, binding, null, true);
    $addUpdateTag(COLLABORATION_TAG);
  }, {
    // Need any text node normalization to be synchronously updated back to Yjs, otherwise the
    // binding.mapping will get out of sync.
    discrete: true,
    onUpdate: () => {
      syncCursorPositions(binding, provider);
      editor.update(() => $ensureEditorNotEmpty());
    },
    skipTransforms: true,
    tag: COLLABORATION_TAG
  });
}
function syncLexicalUpdateToYjsV2__EXPERIMENTAL(binding, provider, prevEditorState, currEditorState, dirtyElements, normalizedNodes, tags) {
  const isFromYjs = tags.has(COLLABORATION_TAG) || tags.has(HISTORIC_TAG);
  if (isFromYjs && normalizedNodes.size === 0) {
    return;
  }

  // Nodes are normalized synchronously (`discrete: true` above), so the mapping may now be
  // incorrect for these nodes, as they point to `getLatest` which is mutable within an update.
  normalizedNodes.forEach(nodeKey => {
    binding.mapping.deleteNode(nodeKey);
  });
  syncWithTransaction(binding, () => {
    currEditorState.read(() => {
      if (dirtyElements.has('root')) {
        $updateYFragment(binding.doc, binding.root, $getRoot(), binding, new Set(dirtyElements.keys()));
      }
      const selection = $getSelection();
      const prevSelection = prevEditorState._selection;
      syncLexicalSelectionToYjs(binding, provider, prevSelection, selection);
    });
  });
}

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

const CONNECTED_COMMAND = createCommand('CONNECTED_COMMAND');
const TOGGLE_CONNECT_COMMAND = createCommand('TOGGLE_CONNECT_COMMAND');
const DIFF_VERSIONS_COMMAND__EXPERIMENTAL = createCommand('DIFF_VERSIONS_COMMAND');
const CLEAR_DIFF_VERSIONS_COMMAND__EXPERIMENTAL = createCommand('CLEAR_DIFF_VERSIONS_COMMAND');
function createUndoManager(binding, root) {
  return new UndoManager(root, {
    trackedOrigins: new Set([binding, null])
  });
}
function initLocalState(provider, name, color, focusing, awarenessData) {
  provider.awareness.setLocalState({
    anchorPos: null,
    awarenessData,
    color,
    focusPos: null,
    focusing: focusing,
    name
  });
}
function setLocalStateFocus(provider, name, color, focusing, awarenessData) {
  const {
    awareness
  } = provider;
  let localState = awareness.getLocalState();
  if (localState === null) {
    localState = {
      anchorPos: null,
      awarenessData,
      color,
      focusPos: null,
      focusing: focusing,
      name
    };
  }
  localState.focusing = focusing;
  awareness.setLocalState(localState);
}

export { $getYChangeState, CLEAR_DIFF_VERSIONS_COMMAND__EXPERIMENTAL, CONNECTED_COMMAND, DIFF_VERSIONS_COMMAND__EXPERIMENTAL, TOGGLE_CONNECT_COMMAND, createBinding, createBindingV2__EXPERIMENTAL, createUndoManager, getAnchorAndFocusCollabNodesForUserState, initLocalState, renderSnapshot__EXPERIMENTAL, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncLexicalUpdateToYjsV2__EXPERIMENTAL, syncYjsChangesToLexical, syncYjsChangesToLexicalV2__EXPERIMENTAL, syncYjsStateToLexicalV2__EXPERIMENTAL };
