# src/Bindings/DomBinding/domObserver.js

    import YXmlHook from '../../Types/YXml/YXmlHook.js'
    import {
      iterateUntilUndeleted,
      removeAssociation,
      insertNodeHelper } from './util.js'
    import diff from '../../Util/simpleDiff.js'
    import YXmlFragment from '../../Types/YXml/YXmlFragment.js'

    /**
     * 1. Check if any of the nodes was deleted
     * 2. Iterate over the children.
     *    2.1 If a node exists that is not yet bound to a type, insert a new node
     *    2.2 If _contents.length < dom.childNodes.length, fill the
     *        rest of _content with childNodes
     *    2.3 If a node was moved, delete it and
     *       recreate a new yxml element that is bound to that node.
     *       You can detect that a node was moved because expectedId
     *       !== actualId in the list
     * @private
     */
    function applyChangesFromDom (binding, dom, yxml, _document) {
      if (yxml == null || yxml === false || yxml.constructor === YXmlHook) {
        return
      }
      const y = yxml._y
      const knownChildren = new Set()
      for (let i = dom.childNodes.length - 1; i >= 0; i--) {
        const type = binding.domToType.get(dom.childNodes[i])
        if (type !== undefined && type !== false) {
          knownChildren.add(type)
        }
      }
      // 1. Check if any of the nodes was deleted
      yxml.forEach(function (childType) {
        if (knownChildren.has(childType) === false) {
          childType._delete(y)
          removeAssociation(binding, binding.typeToDom.get(childType), childType)
        }
      })
      // 2. iterate
      const childNodes = dom.childNodes
      const len = childNodes.length
      let prevExpectedType = null
      let expectedType = iterateUntilUndeleted(yxml._start)
      for (let domCnt = 0; domCnt < len; domCnt++) {
        const childNode = childNodes[domCnt]
        const childType = binding.domToType.get(childNode)
        if (childType !== undefined) {
          if (childType === false) {
            // should be ignored or is going to be deleted
            continue
          }
          if (expectedType !== null) {
            if (expectedType !== childType) {
              // 2.3 Not expected node
              if (childType._parent !== yxml) {
                // child was moved from another parent
                // childType is going to be deleted by its previous parent
                removeAssociation(binding, childNode, childType)
              } else {
                // child was moved to a different position.
                childType._delete(y)
                removeAssociation(binding, childNode, childType)
              }
              prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding)
            } else {
              // Found expected node. Continue.
              prevExpectedType = expectedType
              expectedType = iterateUntilUndeleted(expectedType._right)
            }
          } else {
            // 2.2 Fill _content with child nodes
            prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding)
          }
        } else {
          // 2.1 A new node was found
          prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding)
        }
      }
    }

    /**
     * @private
     */
    export default function domObserver (mutations, _document) {
      this._mutualExclude(() => {
        this.type._y.transact(() => {
          let diffChildren = new Set()
          mutations.forEach(mutation => {
            const dom = mutation.target
            const yxml = this.domToType.get(dom)
            if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) {
              // dom element is filtered
              return
            }
            switch (mutation.type) {
              case 'characterData':
                var change = diff(yxml.toString(), dom.nodeValue)
                yxml.delete(change.pos, change.remove)
                yxml.insert(change.pos, change.insert)
                break
              case 'attributes':
                if (yxml.constructor === YXmlFragment) {
                  break
                }
                let name = mutation.attributeName
                let val = dom.getAttribute(name)
                // check if filter accepts attribute
                let attributes = new Map()
                attributes.set(name, val)
                if (yxml.constructor !== YXmlFragment && this.filter(dom.nodeName, attributes).size > 0) {
                  if (yxml.getAttribute(name) !== val) {
                    if (val == null) {
                      yxml.removeAttribute(name)
                    } else {
                      yxml.setAttribute(name, val)
                    }
                  }
                }
                break
              case 'childList':
                diffChildren.add(mutation.target)
                break
            }
          })
          for (let dom of diffChildren) {
            if (dom.yOnChildrenChanged !== undefined) {
              dom.yOnChildrenChanged()
            }
            const yxml = this.domToType.get(dom)
            applyChangesFromDom(this, dom, yxml, _document)
          }
        })
      })
    }