
/**
 * @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,
  fiber,
  DSDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line
  transactIterate,
  debugLog,
  getSourceFromTransaction,
  getAtomicItemHash,
  ID,
  splitItem,
  addMerkleTreeLeafNodes,
  convertUpdateFormatV1ToV2,
  diffUpdate
} 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
 * @param {number} numberOfStructs
 * @param {Array<GC|Item>} refs
 * @param {number} client
 * @returns {Array<GC|Item>}
 *
 * @function
 */
export const readStructFromDecoder = (decoder, doc, transaction, numberOfStructs, refs, client) => {
  let clock = decoding.readVarUint(decoder.restDecoder) //读取起始的clock
  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()))

        refs[i] = struct
        // clock += struct.length

        clock = structClock + struct.length
      }
    }
  }
  return refs
}

/**
 * @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()

    // const start = performance.now()
    clientRefs.set(client, { i: 0, refs }) // 设置用户信息
    readStructFromDecoder(decoder, doc, transaction, numberOfStructs, refs, client)
    // 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
 * @returns {boolean | undefined}
 */
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)
  }

  /**
   * @type {boolean}
   */
  let updated = false

  if(id.hash && isExist && mMap.has(`${id.clock}`) && !mMap.get(`${id.clock}`)){
    // 如果有一个带hash的item存在了
    mMap.set(`${id.clock}`, true)
    updated = 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)
    updated = 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)
    updated = 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)
        updated = true
      }
      if(clientMissing.has(`${id.clock}-${id.hash}`) && !clientMissing.get(`${id.clock}-${id.hash}`)){
        clientMissing.set(`${id.clock}-${id.hash}`, true)
        updated = true
      }
    }
  }
  return updated
}

  /**
   * 合法性检查
   * @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
}

/**
 * 拜占庭冲突处理，针对得到的原子items进行拜占庭冲突处理
 * @param {Map<number, Array<Item | GC>>} structsMap 所有client以及相对应的冲突items列表
 * @param {Doc} doc
 * @param {any} transactionOrigin
 */
export const applyByzantineConflicts = (structsMap, doc, transactionOrigin) => {
  transact(doc, transaction => {
    transaction.local = false
    let retry = false
    const store = transaction.doc.store
    const restStructs = integrateConflictItems(transaction, store, structsMap)

    // 合并丢失表
    const pending = store.pendingStructs
    if(pending){
        // 检测过去缺失的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){
          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)
            }
          }
        }
    } else if(restStructs && restStructs.missing){
      const encoder = new UpdateEncoderV1()
      store.pendingStructs = {
        missing: restStructs.missing,
        update: encoder.toUint8Array()
      }
    }
 
    if(restStructs && restStructs.restStructsMap){
      const byzantineStore = doc.byzantineStore
      const pendingStructs = byzantineStore.pendingStructs
      if(pendingStructs){
        pendingStructs.structs = mergeAtomicStructs([pendingStructs.structs, restStructs.restStructsMap])
      } else {
        byzantineStore.pendingStructs = {
          structs: restStructs.restStructsMap
        }  
      }
    }

    if (retry) {
      retryApplyUpdate(transaction, UpdateDecoderV1)
    }
  }, transactionOrigin, true)
}

/**
 * 
 * @param {Transaction} transaction 
 */
export const retryApplyByzantineConflicts = (transaction) => {
  const byzantineStore = transaction.doc.byzantineStore
  if(!byzantineStore.pendingStructs) return
  const structsMap = byzantineStore.pendingStructs?.structs
  byzantineStore.pendingStructs = null
  applyByzantineConflicts(structsMap, transaction.doc, undefined)
}

/**
 * 合并多个原子structs的用户历史列表
 * @param {Array<Map<number, Array<Item | GC>>>} structsMaps 
 */
export const mergeAtomicStructs = (structsMaps) => {
  if(structsMaps.length === 1){
    return structsMaps[0]
  }
  /**
   * @type {Map<number, Map<string, Item | GC>>}
   */
  let combinedStructs = new Map()
  for(let i = 0 ; i < structsMaps.length ; i ++){
    let currentStructsMap = structsMaps[i]
    for(let [client, structs] of currentStructsMap){
      let clientStructsMap = combinedStructs.get(client)
      if(!clientStructsMap){
        clientStructsMap = new Map()
        combinedStructs.set(client, clientStructsMap)
      }
      structs.forEach(struct => {
        const hash = getAtomicItemHash(struct)
        if(!clientStructsMap?.get(hash)){
          clientStructsMap?.set(hash, struct)
        }
      })
    }
  }

  /**
   * @type {Map<number, Array<Item | GC>>}
   */
  let resultStructs = new Map()
  for(let [client, structsMap] of combinedStructs){
    /**
     * @type {Array<Item | GC>}
     */
    const structs = []
    for(let struct of structsMap.values()){
      structs.push(struct)
    }
    resultStructs.set(client, structs)
  }

  return resultStructs
}

/**
 * 
 * @param {Transaction} transaction
 * @param {StructStore} store
 * @param {Map<number, Array<Item | GC>>} structsMap 
 */
export const integrateConflictItems = (transaction, store, structsMap) => {
  /**
   * @type {Map<number, { i: number, refs: (GC | Item)[] }>}
   */
  const clientsStructRefs = new Map()
  for(let [ client, structs ] of structsMap){
    clientsStructRefs.set(client, {
      refs: structs,
      i: 0
    })
  }

  /**
   * @type {Array<Item | GC>}
   */
  const stack = []
  let clientStructsIds = Array.from(clientsStructRefs.keys()).sort((a, b) => a - b)
  if(clientStructsIds.length === 0) return null

  const getNextStructTarget = () => {
    if(clientStructsIds.length === 0) return null
    let nextStructsTarget = clientsStructRefs.get(clientStructsIds[clientStructsIds.length - 1])
    while(nextStructsTarget?.refs.length === nextStructsTarget?.i){
      clientStructsIds.pop()
      if(clientStructsIds.length > 0){
        nextStructsTarget = clientsStructRefs.get(clientStructsIds[clientStructsIds.length - 1])
      } else return null
    }
    return nextStructsTarget
  }

  let curStructsTarget = getNextStructTarget()
  if(curStructsTarget === null && stack.length === 0){
    return null
  }

  const restStructs = new StructStore()

  /**
   * @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)

  let stackHead = curStructsTarget?.refs[curStructsTarget.i ++]

  const state = new Map()

  const addStackToRestSS = () => {
    for(const item of stack){
      const client = item.id.client
      let restClientStructStore = restStructs.clients.get(client)
      if(!restClientStructStore){
        restClientStructStore = new Array()
        restStructs.clients.set(client, restClientStructStore)
      }
      restClientStructStore.push(item)
    }
    stack.length = 0
  }

  while(true){
    if(stackHead && !(stackHead instanceof GC) && stackHead.length === 1 && checkItemIsLegal(stackHead)){
      // @ts-ignore
      const localClock = map.setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client))
      let offset = localClock - stackHead.id.clock
      if(offset > 0){
        // 只有这时才会产生冲突
        // 1.如果没有将其加入默克尔树，就将stackHead加入默克尔树
        // 为什么需要提早执行？如果该item本地目前无法执行，那么就需要先加入默克尔树，这是为了保证调制不需要考虑本地是否支持集成
        // 如果因为本地目前无法集成而导致调制失败，那么会不停调制，这样没有任何意义
        if(!stackHead.merkleTreeLeafNodes){
          stackHead.merkleTreeLeafNodes = addMerkleTreeLeafNodes(transaction.doc.byzantineStore, stackHead, transaction)
        }
        // 2.缺失检查
        const missing = stackHead.getMissing(transaction, store)
        if(missing !== null){
          stack.push(stackHead)
          // 历史存在缺失, 历史缺失可能在完成所有冲突处理后被解除
          const missingClientStructs = clientsStructRefs.get(missing.client) || { refs: [], i: 0 }
          if(missingClientStructs.refs.length === missingClientStructs.i){
            // 该依赖当前丢失的现状无法改变，当前stackHead无法集成的现状无法改变
            _updateMissingIDs(missing, false)
            addStackToRestSS()
          } else {
            stackHead = missingClientStructs.refs[missingClientStructs.i ++]
            continue
          }
        } else {
          const clientStore = store.clients.get(stackHead.id.client) ?? []
          // 历史没有发生缺失，可以开始集成
          const index = findIndexSS(clientStore, stackHead.id.clock)
          const localStruct = clientStore[index]
          // 目前拜占庭不处理gc情况
          if(localStruct instanceof Item){
            const offset = stackHead.id.clock - localStruct.id.clock
            if(offset === 0){
              if(localStruct.length > 1){
                clientStore.splice(index + 1, 0, splitItem(transaction, localStruct, offset + 1))
              }
            } else {
              clientStore.splice(index + 1, 0, splitItem(transaction, localStruct, offset))
            }
            clientStore.splice(index + 1, 0, stackHead)
            stackHead.integrate(transaction, 0, undefined, false)
            _updateMissingIDs(stackHead, true)
          }
        }
      }
    }
    if(stack.length > 0){
      stackHead = stack.pop()
    } else if(curStructsTarget && curStructsTarget.refs.length > curStructsTarget.i){
      stackHead = curStructsTarget.refs[curStructsTarget.i ++]
    } else {
      curStructsTarget = getNextStructTarget()
      if(!curStructsTarget){ break } else {
        stackHead = curStructsTarget.refs[curStructsTarget.i ++]
      }
    }
  }

  if(restStructs.clients.size > 0){
    return {
      missing: missingIDs,
      restStructsMap: restStructs.clients
    }
  }
  return null
} 

/**
 * 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
  }

  // 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 > 0 && 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 UpdateEncoderV1()
    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
 }

  // 集成阶段的副作用任务队列
  /**
   * @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 > 0 && 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 UpdateEncoderV1()
   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
        // @ts-ignore
        pending.update = mergeUpdates([pending.update, restStructs.update].filter(update => 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 = mergeUpdates([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) {
      retryApplyUpdate(transaction, structDecoder instanceof UpdateDecoderV1 ? UpdateDecoderV1 : UpdateDecoderV2)
    }
  }, transactionOrigin, false)
}

/**
 * 
 * @param {Transaction} transaction 
 * @param {any} structDecoderConstructor
 */
export const retryApplyUpdate = (transaction, structDecoderConstructor = UpdateDecoderV1) => {
  const store = transaction.doc.store
  if(store.pendingStructs){
    const update = /** @type {{update: Uint8Array}} */ (store.pendingStructs).update
    store.pendingStructs = null
    applyUpdateV2(
      transaction.doc, 
      update, 
      undefined,
      structDecoderConstructor
    )
  }
  retryApplyByzantineConflicts(transaction)
}

/**
 * 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 = mergeUpdates([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 = mergeUpdates([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
      // )
      retryApplyUpdate(transaction, 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 && doc.store.pendingStructs.update) {
    updates.push(diffUpdate(doc.store.pendingStructs.update, encodedTargetStateVector))
  }
  if (updates.length > 1) {
    if (encoder.constructor === UpdateEncoderV1) {
      // return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update)))
      return mergeUpdates(updates)
    } else if (encoder.constructor === UpdateEncoderV2) {
      // return mergeUpdatesV2(updates)
      return mergeUpdatesV2(updates.map((update) => convertUpdateFormatV1ToV2(update)))
    }
  }
  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())
