
/**
 * @module encoding
 */
/*
 * We use the first five bits in the info flag for determining the type of the struct.
 *
 * 0: GC
 * 1: Item with Deleted content
 * 2: Item with JSON content
 * 3: Item with Binary content
 * 4: Item with String content
 * 5: Item with Embed content (for richtext content)
 * 6: Item with Format content (a formatting marker for richtext content)
 * 7: Item with Type
 */

import {
  findIndexSS,
  getState,
  createID,
  getStateVector,
  readAndApplyDeleteSet,
  writeDeleteSet,
  createDeleteSetFromStructStore,
  transact,
  readItemContent,
  UpdateDecoderV1,
  UpdateDecoderV2,
  UpdateEncoderV1,
  UpdateEncoderV2,
  DSEncoderV2,
  DSDecoderV1,
  DSEncoderV1,
  mergeUpdates,
  mergeUpdatesV2,
  Skip,
  diffUpdateV2,
  convertUpdateFormatV2ToV1,
  fiber,
  DSDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line
  transactIterate,
  debugLog,
  getSourceFromTransaction,
  getAtomicItemHash,
  getItemCleanEnd,
  getItemCleanStart,
  ID,
  splitItem
} from '../internals.js'

import * as encoding from 'lib0/encoding'
import * as decoding from 'lib0/decoding'
import * as binary from 'lib0/binary'
import * as map from 'lib0/map'
import * as math from 'lib0/math'
// @ts-ignore
import { BaseTask } from '@comtstore/iterator-driver'

/**
 * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 * @param {Array<GC|Item>} structs All structs by `client`
 * @param {number} client
 * @param {number} clock write structs starting with `ID(client,clock)`
 *
 * @function
 */
const writeStructs = (encoder, structs, client, clock) => {
  // write first id
  clock = math.max(clock, structs[0].id.clock) // make sure the first id exists
  const startNewStructs = findIndexSS(structs, clock)
  // write # encoded structs
  encoding.writeVarUint(encoder.restEncoder, structs.length - startNewStructs)
  encoder.writeClient(client)

  // 引入拜占庭后，仍然需要initial clock
  encoding.writeVarUint(encoder.restEncoder, clock)

  const firstStruct = structs[startNewStructs]
  // write first struct with an offset
  firstStruct.write(encoder, clock - firstStruct.id.clock)
  for (let i = startNewStructs + 1; i < structs.length; i++) {
    structs[i].write(encoder, 0)
  }
}

/**
 * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 * @param {StructStore} store
 * @param {Map<number,number>} _sm
 *
 * @private
 * @function
 */
export const writeClientsStructs = (encoder, store, _sm) => {
  // we filter all valid _sm entries into sm
  const sm = new Map()
  _sm.forEach((clock, client) => {
    // only write if new structs are available
    if (getState(store, client) > clock) {
      sm.set(client, clock)
    }
  })
  getStateVector(store).forEach((clock, client) => {
    if (!_sm.has(client)) {
      sm.set(client, 0)
    }
  })
  // write # states that were updated
  encoding.writeVarUint(encoder.restEncoder, sm.size)
  // Write items with higher client ids first
  // This heavily improves the conflict algorithm.
  // updates的clients必须从大到小排序
  Array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
    // @ts-ignore
    writeStructs(encoder, store.clients.get(client), client, clock)
  })
}

/**
 * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from.
 * @param {Doc} doc
 * @param {Transaction | null} transaction
 * @return {Map<number, { i: number, refs: Array<Item | GC> }>}
 *
 * @private
 * @function
 */
export const readClientsStructRefs = (decoder, doc, transaction = null) => {
  /**
   * @type {Map<number, { i: number, refs: Array<Item | GC> }>}
   */
  const clientRefs = map.create() // 更新里所找到的所有的client的ref
  // 获取update数量
  const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder)
  for (let i = 0; i < numOfStateUpdates; i++) {
    // 获取每一次更新的structs数量，每一次更新都和唯一一个client相对应
    const numberOfStructs = decoding.readVarUint(decoder.restDecoder)
    /**
     * @type {Array<GC|Item>}
     */
    const refs = new Array(numberOfStructs) // 存储这次update的信息
    const client = decoder.readClient()

    let clock = decoding.readVarUint(decoder.restDecoder) //读取起始的clock

    // const start = performance.now()
    clientRefs.set(client, { i: 0, refs }) // 设置用户信息
    for (let i = 0; i < numberOfStructs; i++) {
      const info = decoder.readInfo() // 表示struct的信息类型
      switch (binary.BITS5 & info) {
        case 0: { // GC
          const len = decoder.readLen() //读取长度
          refs[i] = new GC(createID(client, clock), len)
          clock += len
          break
        }
        case 10: { // Skip Struct (nothing to apply)
          // @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing.
          const len = decoding.readVarUint(decoder.restDecoder)
          // @ts-ignore
          refs[i] = new Skip(createID(client, clock), len)
          clock += len
          break
        }
        default: { // Item with content
          /**
           * The optimized implementation doesn't use any variables because inlining variables is faster.
           * Below a non-optimized version is shown that implements the basic algorithm with
           * a few comments
           */
          const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
          // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
          // and we read the next string as parentYKey.
          // It indicates how we store/retrieve parent from `y.share`
          // @type {string|null}

          /**
           * @type {number}
           */
          const structClock = decoder.readClock()

          // 在此处需要保证clock是非严格单调递增
          if(!([0, 1].includes(clock - structClock))){
            // 不符合非严格单调递增要求，直接停止
            // 这是一种优化，拜占庭的情况往往比较稀少，我们需要保证得到的update是非严格单调递增
            break
          }

          const struct = new Item(
            // createID(client, clock), 
            createID(client, structClock),
            null, // left
            (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin
            null, // right
            (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
            cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent
            cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
            readItemContent(decoder, info), // item content
            undefined,
            !!transaction ? getSourceFromTransaction(transaction) : 'decoder'
          )

          const haveSignature = decoder.readHaveSignature()
          if(haveSignature){
            struct.signature = decoder.readSignature()
          }

          const haveIntegrateSignature = decoder.readHaveIntegrateSignature()
          if(haveIntegrateSignature){
            struct.integrateSignature = decoder.readIntegrateSignature()
          }
          
          debugLog('readClientsStructRefs- 读取到来自于decoder中的item content:', struct, () => struct.content.getContent())
          debugLog('readClientsStructRefs- 读取到来自于decoder中的signature内容:', struct, () => struct.signature?.map(signature => signature.toString()))
          
          /* A non-optimized implementation of the above algorithm:

          // The item that was originally to the left of this item.
          const origin = (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null
          // The item that was originally to the right of this item.
          const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null
          const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
          const hasParentYKey = cantCopyParentInfo ? decoder.readParentInfo() : false
          // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
          // and we read the next string as parentYKey.
          // It indicates how we store/retrieve parent from `y.share`
          // @type {string|null}
          const parentYKey = cantCopyParentInfo && hasParentYKey ? decoder.readString() : null

          const struct = new Item(
            createID(client, clock),
            null, // leftd
            origin, // origin
            null, // right
            rightOrigin, // right origin
            cantCopyParentInfo && !hasParentYKey ? decoder.readLeftID() : (parentYKey !== null ? doc.get(parentYKey) : null), // parent
            cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
            readItemContent(decoder, info) // item content
          )
          */
          refs[i] = struct
          // clock += struct.length

          clock = structClock + struct.length
        }
      }
    }
    // console.log('time to read: ', performance.now() - start) // @todo remove
  }
  return clientRefs
}

/**
 * 
 * @param {ID | Item | GC} id
 * @param {boolean} isExist 
 * @param {StructStore} store 
 * @param {Map<number, Map<string, boolean>>} missingIDs 
 */
const udpdateMissingIDs = (id, isExist, store, missingIDs) => {
  if(id instanceof Item || id instanceof GC){
    for(let offset = 0 ; offset < id.length ; offset ++){
      udpdateMissingIDs(createID(id.id.client, id.id.clock + offset), isExist, store, missingIDs)
    }
    return
  }

  let mMap = missingIDs.get(id.client)
  if(!mMap){
    mMap = new Map()
    missingIDs.set(id.client, mMap)
  }

  if(id.hash && isExist && mMap.has(`${id.clock}`) && !mMap.get(`${id.clock}`)){
    // 如果有一个带hash的item存在了
    mMap.set(`${id.clock}`, true)
  }

  if(id.hash && isExist && mMap.has(`${id.clock}-${id.hash}`) && !mMap.get(`${id.clock}-${id.hash}`)){
    // 如果有hash并且设置为true并且本地为false，需要设置为true
    mMap.set(`${id.clock}-${id.hash}`, true)
  } else if(id.hash && !isExist && !mMap.has(`${id.clock}-${id.hash}`)){
    // 如果有hash并且设置为false并且本地没有设置
    mMap.set(`${id.clock}-${id.hash}`, false)
  } else if(!id.hash && isExist && mMap.has(`${id.clock}`) && !mMap.get(`${id.clock}`)){
    // 无hash的情况
    mMap.set(`${id.clock}`, true)
  } else if(!id.hash && !isExist && !mMap.has(`${id.clock}`)){
    // 无hash的情况
    mMap.set(`${id.clock}`, false)
  }

  // 如果该元素存在，那么更新pendingStructs内的缺项数据
  if(store.pendingStructs && store.pendingStructs.missing && isExist){
    const pendingMissing = store.pendingStructs.missing
    const clientMissing = pendingMissing.get(id.client)
    if(clientMissing){
      if(clientMissing.has(`${id.clock}`) && !clientMissing.get(`${id.clock}`)){
        clientMissing.set(`${id.clock}`, true)
      }
      if(clientMissing.has(`${id.clock}-${id.hash}`) && !clientMissing.get(`${id.clock}-${id.hash}`)){
        clientMissing.set(`${id.clock}-${id.hash}`, true)
      }
    }
  }
}

  /**
   * 合法性检查
   * @param {Item | GC} stackHead
   */
const checkItemIsLegal = (stackHead) => {
  if(stackHead instanceof Item){
    // 左节点不可以晚于当前节点的id
    if(stackHead.id.client === stackHead.origin?.client){
      if(stackHead.origin.clock >= stackHead.id.clock){
        return false
      }
    }
    // 右节点不可以晚于当前节点的id
    if(stackHead.id.client === stackHead.rightOrigin?.client){
      if(stackHead.rightOrigin.clock >= stackHead.id.clock){
        return false
      }
    }
    // 父节点不可以晚于当前节点的id
    if(stackHead.parent instanceof ID && stackHead.parent.client === stackHead.id.client){
      if(stackHead.parent.clock >= stackHead.id.clock){
        return false
      }
    }
  }
  return true
}

/**
 * 集成冲突stackHead到历史中, stackHead必须在历史中发生冲突
 * @param {Transaction | null} transaction
 * @param {StructStore} store
 * @param {Item | GC} stackHead
 * @param {number} offset 要滚动获取stackhead的头部atomic Item的次数
 * @param {Map<number, Map<string, boolean>> | undefined} missingIDs 丢失的ids对象
 * @param {(item: Item | GC | Skip, offset: number) => void | undefined} addItemToStore
 */
// @ts-ignore
export const integrateConflictItem = (transaction, store, stackHead, offset = stackHead.length, missingIDs = undefined, addItemToStore = undefined) => {
  /**
   * 更新缺失的ids
   * @param {ID | Item | GC} id
   * @param {boolean} isExist
   */
  const _updateMissingIDs = (id, isExist) => {
    if(missingIDs){
      udpdateMissingIDs(id, isExist, store, missingIDs)
    }
  }

  // GC的情况不需要处理
  if(stackHead instanceof GC){
    return
  }

  // 如果是部分发生冲突，就把没有发生冲突的部分放入历史中
  const state = getState(store, stackHead.id.client)
  if(stackHead.id.clock < state && stackHead.id.clock + offset > state){
    const postStackHead = splitItem(transaction, stackHead,  state - stackHead.id.clock)
    if(addItemToStore){
      addItemToStore(postStackHead, 0)
    }
  } else if(stackHead.id.clock >= state){
    if(addItemToStore){
      addItemToStore(stackHead, 0)
    }
  }

  if(stackHead instanceof Item){
    while(offset --){
      // 获取后续的stackHead，原来的stackHead为得到的原子item
      let newStackHead = splitItem(transaction, stackHead, 1)
      // 取得stackhead的第一个原子item
      const stackHeadAtomicItem = stackHead
      // 更新stackHead
      stackHead = newStackHead
      // 获取stackhead的第一个原子item的hash
      const stackHeadAtomicItemHash = getAtomicItemHash(stackHeadAtomicItem)
      // 获取本地的对应用户的历史数组
      const clientStore = store.clients.get(stackHeadAtomicItem.id.client) ?? []
      /**
       * 第一个具有该id.clock的item
       * 这个item一定存在，否则不需要处理id的冲突
       * @type {number}
       */
      let index = findIndexSS(clientStore, stackHeadAtomicItem.id.clock)

      // 1.获取所有的和该id名义冲突的原子item
      // 任何item最多只有一个，要么在item的头部要么在尾部
      /**
       * @type {Array<{
       *    index: number,
       *    item: Item | GC,
       *    offset: number,
       *    hash: string
      * }>}
        */
      const conflictAtomicItems = []

      while(true){
        let struct = clientStore[index]
        if(stackHeadAtomicItem.id.clock >= struct.id.clock &&
            stackHeadAtomicItem.id.clock < struct.id.clock + struct.length){
              if(struct instanceof Skip){
                // 该item为skip元素，需要将skip部分元素进行替换即可，不存在冲突
                if(stackHeadAtomicItem.id.clock - struct.id.clock){
                  // 具有一部分在前，需要在历史中分开
                  struct = getItemCleanStart(transaction, stackHeadAtomicItem.id, store)
                  index ++
                }
                // 分情况讨论
                // 1. skip元素比stackhead短
                if(struct.id.clock + struct.length < stackHead.id.clock + stackHead.length){
                  newStackHead = splitItem(transaction, stackHead, struct.length - 1)
                  clientStore.splice(index, 1, stackHeadAtomicItem, stackHead)
                  stackHead = newStackHead
                  index += 1
                } else if(struct.id.clock + struct.length >= stackHead.id.clock + stackHead.length){
                  // 2. skip元素不比stackhead短
                  if(struct.id.clock + struct.length > stackHead.id.clock + stackHead.length){
                    getItemCleanEnd(transaction, store, createID(stackHead.id.client, stackHead.id.clock + stackHead.length - 1))     
                  }
                  clientStore.splice(index, 1, stackHeadAtomicItem, stackHead)
                  // 不再需要继续比较，stackhead已经遍历完毕了
                  break
                }
            } else {
              // 元素在该item之中，获取offset
              const offset = stackHeadAtomicItem.id.clock - struct.id.clock
              // 加入到冲突元素列表中
              conflictAtomicItems.push({
                index: index,
                item: struct,
                offset,
                hash: getAtomicItemHash(struct, offset)
              })
            }
        } else {
          break
        }
        index ++
      }

      // 2. 比较要插入的原子item的hash值是否在本地已经存在
      let conflictIndex = conflictAtomicItems.findIndex(conflictItemInfo => {
        return conflictItemInfo.hash === stackHeadAtomicItemHash
      })

      if(conflictIndex > -1){
        // 说明该元素已经在本地存在了，不集成, 跳过以下其他步骤
        continue
      }

      // 3. 该元素在本地不存在，需要先插入历史中
      // conflictItemInfo是否可能无序？
      // 拜占庭节点发送的item历史可能无序？ 是的，可能无序。
      // 可以在leafNode中进行节点排序，看似不影响
      // 将该原子item直接放入最后一个元素之后，或者放在第一个之前
      // 或者我们可以找一个最好放的地方，比如offset为0的地方
      // offset为0，说明我们可以把目标item放入该item之前，而不需要动任何聚合item
      // offset为最后一个也可以

      /**
       * @type {boolean} conflictIndex位置的原子item是否是聚合item的头部，要么头部要么尾部
       */
      let isHead = false

      // 获取第一个位于头部或者尾部的冲突原子item
      conflictIndex = conflictAtomicItems.findIndex(conflictItemInfo => {
        if(conflictItemInfo.offset === 0 || conflictItemInfo.offset === conflictItemInfo.item.length - 1){
          isHead = conflictItemInfo.offset === 0
          return true
        }
        return false
      })

      // 优化手段，如果有在边缘的冲突的原子item，那么直接在此处附近加入即可
      if(conflictIndex > -1){
        const conflictItemInfo = conflictAtomicItems[conflictIndex]
        // 插入到历史中
        if(isHead){
          // 插入到该冲突的item之前
          clientStore.splice(conflictItemInfo.index, 0, stackHeadAtomicItem)
        } else {
          // 插入到该冲突的item之后
          if(conflictItemInfo.index === clientStore.length - 1){
            // 是历史的最后一个元素
            clientStore.push(stackHeadAtomicItem)
          } else {
            // 不是历史的最后一个元素
            clientStore.splice(conflictItemInfo.index + 1, 0, stackHeadAtomicItem)
          }
        }

        // stackHead已经通过getMissing检查，不需要再getMissing
        // 直接集成
        if(transaction){
          stackHeadAtomicItem.integrate(transaction, 0)
        }
        // 更新丢失的ids，设置为该item为true，说明该item变为存在
        _updateMissingIDs(stackHeadAtomicItem, true)
        continue
      }

      // 如果没有offset在边界，说明冲突的item只有一个，而且还在聚合item的中间，也就是说，conflictAtomicItems长度为1
      // 会不会出现其他情况？不会
      const conflictAtomicItem = conflictAtomicItems[0]

      // item会不会出现GC的情况？GC也会有id，当前研究不处理GC情况，所有都认为是Item
      // 在第一个获取的元素位置分割成两个
      if(conflictAtomicItem.item instanceof Item){
        clientStore.splice(conflictAtomicItem.index + 1, 0, splitItem(transaction, conflictAtomicItem.item, conflictAtomicItem.offset))
      }
      // 插入到历史中
      clientStore.splice(conflictAtomicItem.index + 1, 0, stackHeadAtomicItem)
      // 直接集成
      if(transaction){
        stackHeadAtomicItem.integrate(transaction, 0)
      }
      // 更新丢失的ids
      _updateMissingIDs(stackHeadAtomicItem, true)
    }
  }
}

/**
 * Resume computing structs generated by struct readers.
 *
 * While there is something to do, we integrate structs in this order
 * 1. top element on stack, if stack is not empty
 * 2. next element from current struct reader (if empty, use next struct reader)
 *
 * If struct causally depends on another struct (ref.missing), we put next reader of
 * `ref.id.client` on top of stack.
 *
 * At some point we find a struct that has no causal dependencies,
 * then we start emptying the stack.
 *
 * It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2)
 * depends on struct3 (from client1). Therefore the max stack size is eqaul to `structReaders.length`.
 *
 * This method is implemented in a way so that we can resume computation if this update
 * causally depends on another update.
 *
 * @param {Transaction} transaction
 * @param {StructStore} store
 * @param {Map<number, { i: number, refs: (GC | Item)[] }>} clientsStructRefs
 * @return { null | { update: Uint8Array, missing: Map<number, Map<string, boolean>> } }
//  * { null | { update: Uint8Array, missing: Map<number,number> } }
 *
 * @private
 * @function
 */
const integrateStructs = (transaction, store, clientsStructRefs) => {
  /**
   * @type {Array<Item | GC>}
   */
  const stack = []
  // sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user.
  let clientsStructRefsIds = Array.from(clientsStructRefs.keys()).sort((a, b) => a - b)
  if (clientsStructRefsIds.length === 0) {
    return null
  }
  const getNextStructTarget = () => {
    if (clientsStructRefsIds.length === 0) {
      return null
    }
    let nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
    while (nextStructsTarget.refs.length === nextStructsTarget.i) {
      clientsStructRefsIds.pop()
      if (clientsStructRefsIds.length > 0) {
        nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
      } else {
        return null
      }
    }
    return nextStructsTarget
  }
  let curStructsTarget = getNextStructTarget()
  if (curStructsTarget === null && stack.length === 0) {
    return null
  }

  /**
   * @type {StructStore}
   */
  const restStructs = new StructStore()
 
  // const missingSV = new Map()
  /**
   * @param {number} client
   * @param {number} clock
   */
  // const updateMissingSv = (client, clock) => {
  //   const mclock = missingSV.get(client)
  //   if (mclock == null || mclock > clock) {
  //     missingSV.set(client, clock)
  //   }
  // }

  /**
   * 缺失的id从store里直接获取，直接更改
   * @type {Map<number, Map<string, boolean>>}
   */
  const missingIDs = new Map()

  /**
   * 更新缺失的ids
   * @param {ID | Item | GC} id
   * @param {boolean} isExist
   */
  const _updateMissingIDs = (id, isExist) => udpdateMissingIDs(id, isExist, store, missingIDs)

  /**
   * @type {GC|Item}
   */
  let stackHead = /** @type {any} */ (curStructsTarget).refs[/** @type {any} */ (curStructsTarget).i++]
  // caching the state because it is used very often
  const state = new Map()

  const addStackToRestSS = () => {
    for (const item of stack) {
      const client = item.id.client
      const unapplicableItems = clientsStructRefs.get(client)
      if (unapplicableItems) {
        // decrement because we weren't able to apply previous operation
        unapplicableItems.i--
        restStructs.clients.set(client, unapplicableItems.refs.slice(unapplicableItems.i))
        clientsStructRefs.delete(client)
        unapplicableItems.i = 0
        unapplicableItems.refs = []
      } else {
        // item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue
        restStructs.clients.set(client, [item])
      }
      // remove client from clientsStructRefsIds to prevent users from applying the same update again
      clientsStructRefsIds = clientsStructRefsIds.filter(c => c !== client)
    }
    stack.length = 0
  }

  /**
   * @param {number} offset 要滚动获取stackhead的头部atomic Item的次数
   */
  const _integrateConflictItem = (offset) => integrateConflictItem(transaction, store, stackHead, offset, missingIDs)

  // iterate over all struct readers until we are done
  while (true) {
    if (stackHead.constructor !== Skip) {
      // stackHead需要进行规则检查后才能应用，如果不符合规则，说明是不合法的
      // 不合法的item不能加入
      if(checkItemIsLegal(stackHead)){
        const localClock = map.setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client))
        let offset = localClock - stackHead.id.clock
        if (offset < 0) {
          // update from the same client is missing
          // 该client的历史中存在依赖缺失
          stack.push(stackHead)
          // updateMissingSv(stackHead.id.client, stackHead.id.clock - 1)
          // 缺失项需要记录下来，此处缺失项并没有历史hash的要求
          for(let i = 0 ; i < offset ; i ++){
            _updateMissingIDs(createID(stackHead.id.client, localClock + i), false)
          }
          // hid a dead wall, add all items from stack to restSS
          addStackToRestSS()
        } else {
          const len = stackHead.length
          // 先把stackHead不需要比较的部分删掉
          // 这是因为我们真正要getmissing的对象不对
          if(offset < len){
            if(stackHead instanceof GC){
              stackHead.id.clock += offset
              stackHead.length = len - offset
            } else {
              stackHead = splitItem(transaction, stackHead, offset)
            }
          }

          /**
           * @type {ID | null}
           */
          const missing = stackHead.getMissing(transaction, store)
          if (missing !== null) {
            // 先放入栈，需要先去找丢失的依赖
            stack.push(stackHead)
            // get the struct reader that has the missing struct
            /**
             * @type {{ refs: Array<GC|Item>, i: number }}
             */
            const structRefs = clientsStructRefs.get(/** @type {number} */ (missing.client)) || { refs: [], i: 0 }

            if (structRefs.refs.length === structRefs.i) {
              // This update message causally depends on another update message that doesn't exist yet
              // updateMissingSv(/** @type {number} */ (missing.client), getState(store, missing))
              _updateMissingIDs(missing, false)
              addStackToRestSS()
            } else {
              stackHead = structRefs.refs[structRefs.i++]
              continue
            }
          } else if (offset === 0 || offset < len) {
            // all fine, apply the stackhead
            stackHead.integrate(transaction, 0)
            _updateMissingIDs(stackHead, true)
            state.set(stackHead.id.client, stackHead.id.clock + stackHead.length)
          }
        }
      }
    }
    // iterate to next stackHead
    if (stack.length > 0) {
      stackHead = /** @type {GC|Item} */ (stack.pop())
    } else if (curStructsTarget !== null && curStructsTarget.i < curStructsTarget.refs.length) {
      stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
    } else {
      curStructsTarget = getNextStructTarget()
      if (curStructsTarget === null) {
        // we are done!
        break
      } else {
        stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
      }
    }
  }
  if (restStructs.clients.size > 0) {
    const encoder = new UpdateEncoderV2()
    writeClientsStructs(encoder, restStructs, new Map())
    // write empty deleteset
    // writeDeleteSet(encoder, new DeleteSet())
    encoding.writeVarUint(encoder.restEncoder, 0) // => no need for an extra function call, just write 0 deletes
    // return { missing: missingSV, update: encoder.toUint8Array() }
    return { missing: missingIDs, update: encoder.toUint8Array() }
  }
  return null
}

/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {Map<number, { i: number, refs: (GC | Item)[] }>} clientsStructRefs
* @return { Generator<null | { update: Uint8Array, missing: Map<number, Map<string, boolean>> }> }
//  * { null | { update: Uint8Array, missing: Map<number,number> } }
*
* @private
* @function
*/
const integrateStructsIterate = function*(transaction, store, clientsStructRefs){
 /**
  * @type {Array<Item | GC>}
  */
 const stack = []
 // sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user.
 let clientsStructRefsIds = Array.from(clientsStructRefs.keys()).sort((a, b) => a - b)
 if (clientsStructRefsIds.length === 0) {
   return null
 }
 const getNextStructTarget = () => {
   if (clientsStructRefsIds.length === 0) {
     return null
   }
   let nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
   while (nextStructsTarget.refs.length === nextStructsTarget.i) {
     clientsStructRefsIds.pop()
     if (clientsStructRefsIds.length > 0) {
       nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
     } else {
       return null
     }
   }
   return nextStructsTarget
 }
 let curStructsTarget = getNextStructTarget()
 if (curStructsTarget === null && stack.length === 0) {
   return null
 }

 /**
  * @type {StructStore}
  */
 const restStructs = new StructStore()

 // const missingSV = new Map()
 /**
  * @param {number} client
  * @param {number} clock
  */
 // const updateMissingSv = (client, clock) => {
 //   const mclock = missingSV.get(client)
 //   if (mclock == null || mclock > clock) {
 //     missingSV.set(client, clock)
 //   }
 // }

 /**
  * 缺失的id从store里直接获取，直接更改
  * @type {Map<number, Map<string, boolean>>}
  */
 const missingIDs = new Map()

 /**
  * 更新缺失的ids
  * @param {ID | Item | GC} id
  * @param {boolean} isExist
  */
 const _updateMissingIDs = (id, isExist) => udpdateMissingIDs(id, isExist, store, missingIDs)

 /**
  * @type {GC|Item}
  */
 let stackHead = /** @type {any} */ (curStructsTarget).refs[/** @type {any} */ (curStructsTarget).i++]
 // caching the state because it is used very often
 const state = new Map()

 const addStackToRestSS = () => {
   for (const item of stack) {
     const client = item.id.client
     const unapplicableItems = clientsStructRefs.get(client)
     if (unapplicableItems) {
       // decrement because we weren't able to apply previous operation
       unapplicableItems.i--
       restStructs.clients.set(client, unapplicableItems.refs.slice(unapplicableItems.i))
       clientsStructRefs.delete(client)
       unapplicableItems.i = 0
       unapplicableItems.refs = []
     } else {
       // item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue
       restStructs.clients.set(client, [item])
     }
     // remove client from clientsStructRefsIds to prevent users from applying the same update again
     clientsStructRefsIds = clientsStructRefsIds.filter(c => c !== client)
   }
   stack.length = 0
 }

 /**
  * @param {number} offset 要滚动获取stackhead的头部atomic Item的次数
  */
 const _integrateConflictItem = (offset) => integrateConflictItem(transaction, store, stackHead, offset, missingIDs)

  // 集成阶段的副作用任务队列
  /**
   * @type {Array<()=>void>}
  */
  const sideEffectQueue = []

 // iterate over all struct readers until we are done
 while (true) {
   if (stackHead.constructor !== Skip) {
     // stackHead需要进行规则检查后才能应用，如果不符合规则，说明是不合法的
     // 不合法的item不能加入
     if(checkItemIsLegal(stackHead)){
       const localClock = map.setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client))
       let offset = localClock - stackHead.id.clock
       if (offset < 0) {
         // update from the same client is missing
         // 该client的历史中存在依赖缺失
         stack.push(stackHead)
         // updateMissingSv(stackHead.id.client, stackHead.id.clock - 1)
         // 缺失项需要记录下来，此处缺失项并没有历史hash的要求
         for(let i = 0 ; i < offset ; i ++){
           _updateMissingIDs(createID(stackHead.id.client, localClock + i), false)
         }
         // hid a dead wall, add all items from stack to restSS
         addStackToRestSS()
       } else {
         const len = stackHead.length
         // 先把stackHead不需要比较的部分删掉
         // 这是因为我们真正要getmissing的对象不对
         if(offset < len){
           if(stackHead instanceof GC){
             stackHead.id.clock += offset
             stackHead.length = len - offset
           } else {
             stackHead = splitItem(transaction, stackHead, offset)
           }
         }

         /**
          * @type {ID | null}
          */
         const missing = stackHead.getMissing(transaction, store)
         if (missing !== null) {
           // 先放入栈，需要先去找丢失的依赖
           stack.push(stackHead)
           // get the struct reader that has the missing struct
           /**
            * @type {{ refs: Array<GC|Item>, i: number }}
            */
           const structRefs = clientsStructRefs.get(/** @type {number} */ (missing.client)) || { refs: [], i: 0 }

           if (structRefs.refs.length === structRefs.i) {
             // This update message causally depends on another update message that doesn't exist yet
             // updateMissingSv(/** @type {number} */ (missing.client), getState(store, missing))
             _updateMissingIDs(missing, false)
             addStackToRestSS()
           } else {
             stackHead = structRefs.refs[structRefs.i++]
             continue
           }
         } else if (offset === 0 || offset < len) {
           // all fine, apply the stackhead
           const sideEffectTask =stackHead.integrate(transaction, 0)
           if(sideEffectTask){
             sideEffectQueue.push(sideEffectTask)
           }
           _updateMissingIDs(stackHead, true)
           state.set(stackHead.id.client, stackHead.id.clock + stackHead.length)
           yield null
         }
       }
     }
   }
   // iterate to next stackHead
   if (stack.length > 0) {
     stackHead = /** @type {GC|Item} */ (stack.pop())
   } else if (curStructsTarget !== null && curStructsTarget.i < curStructsTarget.refs.length) {
     stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
   } else {
     curStructsTarget = getNextStructTarget()
     if (curStructsTarget === null) {
       // we are done!
       break
     } else {
       stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
     }
   }
 }

  // 执行所有的副作用任务
  while(sideEffectQueue.length){
    const sideEffectTask = sideEffectQueue.pop()
    if(sideEffectTask) sideEffectTask()
  }

 if (restStructs.clients.size > 0) {
   const encoder = new UpdateEncoderV2()
   writeClientsStructs(encoder, restStructs, new Map())
   // write empty deleteset
   // writeDeleteSet(encoder, new DeleteSet())
   encoding.writeVarUint(encoder.restEncoder, 0) // => no need for an extra function call, just write 0 deletes
   // return { missing: missingSV, update: encoder.toUint8Array() }
   return { missing: missingIDs, update: encoder.toUint8Array() }
 }
 return null
}

/**
 * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 * @param {Transaction} transaction
 *
 * @private
 * @function
 */
export const writeStructsFromTransaction = (encoder, transaction) => writeClientsStructs(encoder, transaction.doc.store, transaction.beforeState)

/**
 * Read and apply a document update.
 *
 * This function has the same effect as `applyUpdate` but accepts an decoder.
 *
 * @param {decoding.Decoder} decoder
 * @param {Doc} ydoc
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 * @param {UpdateDecoderV1 | UpdateDecoderV2} [structDecoder]
 *
 * @function
 */
export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = new UpdateDecoderV2(decoder)) => {
  transact(ydoc, transaction => {
    // force that transaction.local is set to non-local
    transaction.local = false
    let retry = false
    const doc = transaction.doc
    const store = doc.store
    // let start = performance.now()
    const ss = readClientsStructRefs(structDecoder, doc, transaction)
    // console.log('time to read structs: ', performance.now() - start) // @todo remove
    // start = performance.now()
    // console.log('time to merge: ', performance.now() - start) // @todo remove
    // start = performance.now()
    const restStructs = integrateStructs(transaction, store, ss)
    const pending = store.pendingStructs
    if (pending) {
      // check if we can apply something
      // for (const [client, clock] of pending.missing) {
      //   if (clock < getState(store, client)) {
      //     retry = true
      //     break
      //   }
      // }

      // 检测过去缺失的ids现在是不是已经存在了，如果已经存在了，那么需要重新执行一遍
      for(let [_, mMap] of pending.missing){
        for(let [key, isExist] of mMap){
          if(isExist){
            if(!retry) retry = true
            // 存在某些item的依赖从false转为true，可以重新集成
            mMap.delete(key)
          }
        }
      }

      // 合并丢失表
      if (restStructs) {
        // // merge restStructs into store.pending
        // for (const [client, clock] of restStructs.missing) {
        //   const mclock = pending.missing.get(client)
        //   if (mclock == null || mclock > clock) {
        //     pending.missing.set(client, clock)
        //   }
        // }
        for(let [client, mMap] of restStructs.missing){
          let clientMissing = pending.missing.get(client)
          if(!clientMissing){
            clientMissing = new Map()
            pending.missing.set(client, clientMissing)
          }

          for(let [key, isExist] of mMap){
            clientMissing.set(key, isExist)
          }
        }

        // 合并需要执行的updates
        pending.update = mergeUpdatesV2([pending.update, restStructs.update])
      }
    } else {
      store.pendingStructs = restStructs
    }
    // console.log('time to integrate: ', performance.now() - start) // @todo remove
    // start = performance.now()
    const dsRest = readAndApplyDeleteSet(structDecoder, transaction, store)
    if (store.pendingDs) {
      // @todo we could make a lower-bound state-vector check as we do above
      const pendingDSUpdate = new UpdateDecoderV1(decoding.createDecoder(store.pendingDs))
      decoding.readVarUint(pendingDSUpdate.restDecoder) // read 0 structs, because we only encode deletes in pendingdsupdate
      const dsRest2 = readAndApplyDeleteSet(pendingDSUpdate, transaction, store)
      if (dsRest && dsRest2) {
        // case 1: ds1 != null && ds2 != null
        store.pendingDs = mergeUpdatesV2([dsRest, dsRest2])
      } else {
        // case 2: ds1 != null
        // case 3: ds2 != null
        // case 4: ds1 == null && ds2 == null
        store.pendingDs = dsRest || dsRest2
      }
    } else {
      // Either dsRest == null && pendingDs == null OR dsRest != null
      store.pendingDs = dsRest
    }
    // console.log('time to cleanup: ', performance.now() - start) // @todo remove
    // start = performance.now()

    // console.log('time to resume delete readers: ', performance.now() - start) // @todo remove
    // start = performance.now()
    if (retry) {
      const update = /** @type {{update: Uint8Array}} */ (store.pendingStructs).update
      store.pendingStructs = null
      applyUpdateV2(
        transaction.doc, 
        update, 
        undefined,
         structDecoder instanceof UpdateDecoderV1 ? UpdateDecoderV1 : UpdateDecoderV2
      )
    }
  }, transactionOrigin, false)
}

/**
 * Read and apply a document update.
 *
 * This function has the same effect as `applyUpdate` but accepts an decoder.
 *
 * @param {decoding.Decoder} decoder
 * @param {Doc} ydoc
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 * @param {UpdateDecoderV1 | UpdateDecoderV2} [structDecoder]
 *
 * @function
 */
export const readUpdateV2Iterate = function*(decoder, ydoc, transactionOrigin, structDecoder = new UpdateDecoderV2(decoder)){
  const g = transactIterate(ydoc, function*(/** @type {Transaction} */ transaction){
    // force that transaction.local is set to non-local
    transaction.local = false
    let retry = false
    const doc = transaction.doc
    const store = doc.store
    // let start = performance.now()
    const ss = readClientsStructRefs(structDecoder, doc, transaction)
    // console.log('time to read structs: ', performance.now() - start) // @todo remove
    // start = performance.now()
    // console.log('time to merge: ', performance.now() - start) // @todo remove
    // start = performance.now()
    let restStructs
    const g = integrateStructsIterate(transaction, store, ss)
    while(true){
      const res = g.next()
      if(!res.done){
        yield res
      } else {
        restStructs = res.value
        break
      }
    }
    const pending = store.pendingStructs
    if (pending) {
      // check if we can apply something
      // for (const [client, clock] of pending.missing) {
      //   if (clock < getState(store, client)) {
      //     retry = true
      //     break
      //   }
      // }

      // 检测过去缺失的ids现在是不是已经存在了，如果已经存在了，那么需要重新执行一遍
      for(let [_, mMap] of pending.missing){
        for(let [key, isExist] of mMap){
          if(isExist){
            if(!retry) retry = true
            // 存在某些item的依赖从false转为true，可以重新集成
            mMap.delete(key)
          }
        }
      }

      // 合并丢失表
      if (restStructs) {
        // // merge restStructs into store.pending
        // for (const [client, clock] of restStructs.missing) {
        //   const mclock = pending.missing.get(client)
        //   if (mclock == null || mclock > clock) {
        //     pending.missing.set(client, clock)
        //   }
        // }
        for(let [client, mMap] of restStructs.missing){
          let clientMissing = pending.missing.get(client)
          if(!clientMissing){
            clientMissing = new Map()
            pending.missing.set(client, clientMissing)
          }

          for(let [key, isExist] of mMap){
            clientMissing.set(key, isExist)
          }
        }

        // 合并需要执行的updates
        pending.update = mergeUpdatesV2([pending.update, restStructs.update])
      }
    } else {
      store.pendingStructs = restStructs
    }
    // console.log('time to integrate: ', performance.now() - start) // @todo remove
    // start = performance.now()
    const dsRest = readAndApplyDeleteSet(structDecoder, transaction, store)
    if (store.pendingDs) {
      // @todo we could make a lower-bound state-vector check as we do above
      const pendingDSUpdate = new UpdateDecoderV1(decoding.createDecoder(store.pendingDs))
      decoding.readVarUint(pendingDSUpdate.restDecoder) // read 0 structs, because we only encode deletes in pendingdsupdate
      const dsRest2 = readAndApplyDeleteSet(pendingDSUpdate, transaction, store)
      if (dsRest && dsRest2) {
        // case 1: ds1 != null && ds2 != null
        store.pendingDs = mergeUpdatesV2([dsRest, dsRest2])
      } else {
        // case 2: ds1 != null
        // case 3: ds2 != null
        // case 4: ds1 == null && ds2 == null
        store.pendingDs = dsRest || dsRest2
      }
    } else {
      // Either dsRest == null && pendingDs == null OR dsRest != null
      store.pendingDs = dsRest
    }
    // console.log('time to cleanup: ', performance.now() - start) // @todo remove
    // start = performance.now()

    // console.log('time to resume delete readers: ', performance.now() - start) // @todo remove
    // start = performance.now()
    if (retry) {
      const update = /** @type {{update: Uint8Array}} */ (store.pendingStructs).update
      store.pendingStructs = null
      applyUpdateV2(
        transaction.doc, 
        update, 
        undefined,
         structDecoder instanceof UpdateDecoderV1 ? UpdateDecoderV1 : UpdateDecoderV2
      )
    }
  }, transactionOrigin, false)
  while(true){
    const res = g.next()
    if(!res.done){
      yield res
    } else break
  }
}

/**
 * Read and apply a document update.
 *
 * This function has the same effect as `applyUpdate` but accepts an decoder.
 *
 * @param {decoding.Decoder} decoder
 * @param {Doc} ydoc
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 *
 * @function
 */
export const readUpdate = (decoder, ydoc, transactionOrigin) => readUpdateV2(decoder, ydoc, transactionOrigin, new UpdateDecoderV1(decoder))

/**
 * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
 *
 * This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
 *
 * @param {Doc} ydoc
 * @param {Uint8Array} update
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
 *
 * @function
 */
export const applyUpdateAsync = (ydoc, update, transactionOrigin, YDecoder = UpdateDecoderV2) => {
  const decoder = decoding.createDecoder(update)
  const priority = fiber.getPriority()
  // @ts-ignore
  fiber.taskDriver.addTask(new BaseTask({
    iter: readUpdateV2Iterate(decoder, ydoc, transactionOrigin, new YDecoder(decoder)),
    priority
  }, '' + priority))
}

/**
 * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
 *
 * This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
 *
 * @param {Doc} ydoc
 * @param {Uint8Array} update
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
 *
 * @function
 */
export const applyUpdateV2 = (ydoc, update, transactionOrigin, YDecoder = UpdateDecoderV2) => {
  const decoder = decoding.createDecoder(update)
  readUpdateV2(decoder, ydoc, transactionOrigin, new YDecoder(decoder))  
}

/**
 * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
 *
 * This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
 *
 * @param {Doc} ydoc
 * @param {Uint8Array} update
 * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
 *
 * @function
 */
export const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateAsync(ydoc, update, transactionOrigin, UpdateDecoderV1)

/**
 * Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will
 * only write the operations that are missing.
 *
 * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 * @param {Doc} doc
 * @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs
 *
 * @function
 */
export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) => {
  writeClientsStructs(encoder, doc.store, targetStateVector)
  writeDeleteSet(encoder, createDeleteSetFromStructStore(doc.store))
}

/**
 * Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
 * only write the operations that are missing.
 *
 * Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
 *
 * @param {Doc} doc
 * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
 * @param {UpdateEncoderV1 | UpdateEncoderV2} [encoder]
 * @return {Uint8Array}
 *
 * @function
 */
export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8Array([0]), encoder = new UpdateEncoderV2()) => {
  const targetStateVector = decodeStateVector(encodedTargetStateVector)
  writeStateAsUpdate(encoder, doc, targetStateVector)
  const updates = [encoder.toUint8Array()]
  // also add the pending updates (if there are any)
  if (doc.store.pendingDs) {
    updates.push(doc.store.pendingDs)
  }
  if (doc.store.pendingStructs) {
    updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector))
  }
  if (updates.length > 1) {
    if (encoder.constructor === UpdateEncoderV1) {
      return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update)))
    } else if (encoder.constructor === UpdateEncoderV2) {
      return mergeUpdatesV2(updates)
    }
  }
  return updates[0]
}

/**
 * Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
 * only write the operations that are missing.
 *
 * Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
 *
 * @param {Doc} doc
 * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
 * @return {Uint8Array}
 *
 * @function
 */
export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => encodeStateAsUpdateV2(doc, encodedTargetStateVector, new UpdateEncoderV1())

/**
 * Read state vector from Decoder and return as Map
 *
 * @param {DSDecoderV1 | DSDecoderV2} decoder
 * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
 *
 * @function
 */
export const readStateVector = decoder => {
  const ss = new Map()
  const ssLength = decoding.readVarUint(decoder.restDecoder)
  for (let i = 0; i < ssLength; i++) {
    const client = decoding.readVarUint(decoder.restDecoder)
    const clock = decoding.readVarUint(decoder.restDecoder)
    ss.set(client, clock)
  }
  return ss
}

/**
 * Read decodedState and return State as Map.
 *
 * @param {Uint8Array} decodedState
 * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
 *
 * @function
 */
// export const decodeStateVectorV2 = decodedState => readStateVector(new DSDecoderV2(decoding.createDecoder(decodedState)))

/**
 * Read decodedState and return State as Map.
 *
 * @param {Uint8Array} decodedState
 * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
 *
 * @function
 */
export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(decoding.createDecoder(decodedState)))

/**
 * @param {DSEncoderV1 | DSEncoderV2} encoder
 * @param {Map<number,number>} sv
 * @function
 */
export const writeStateVector = (encoder, sv) => {
  encoding.writeVarUint(encoder.restEncoder, sv.size)
  Array.from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
    encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping
    encoding.writeVarUint(encoder.restEncoder, clock)
  })
  return encoder
}

/**
 * @param {DSEncoderV1 | DSEncoderV2} encoder
 * @param {Doc} doc
 *
 * @function
 */
export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store))

/**
 * Encode State as Uint8Array.
 *
 * @param {Doc|Map<number,number>} doc
 * @param {DSEncoderV1 | DSEncoderV2} [encoder]
 * @return {Uint8Array}
 *
 * @function
 */
export const encodeStateVectorV2 = (doc, encoder = new DSEncoderV2()) => {
  if (doc instanceof Map) {
    writeStateVector(encoder, doc)
  } else {
    writeDocumentStateVector(encoder, doc)
  }
  return encoder.toUint8Array()
}

/**
 * Encode State as Uint8Array.
 *
 * @param {Doc|Map<number,number>} doc
 * @return {Uint8Array}
 *
 * @function
 */
export const encodeStateVector = doc => encodeStateVectorV2(doc, new DSEncoderV1())
