
import {
  GC,
  getState,
  AbstractStruct,
  replaceStruct,
  addStruct,
  addToDeleteSet,
  findRootTypeKey,
  compareIDs,
  getItem,
  getItemCleanEnd,
  getItemCleanStart,
  readContentDeleted,
  readContentBinary,
  readContentJSON,
  readContentAny,
  readContentString,
  readContentEmbed,
  readContentDoc,
  createID,
  readContentFormat,
  readContentType,
  addChangedTypeToTransaction,
  isDeleted,
  Signature,
  DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
  debugLog,
  fiber,
  IntegrateSignature,
  DeleteSignature,
  getSignatureLevelConfig,
  transact,
  removeFromDeleteSet,
  addMerkleTreeLeafNodes,
  MerkleTreeLeafNode,
  getAtomicItemHash,
  getAllAtomicItemHash,
  masterFind,
} from '../internals.js'

import * as error from 'lib0/error'
import * as binary from 'lib0/binary'
// @ts-ignore
import { BaseTask } from '@comtstore/iterator-driver'
import { checkAddAuth, checkDeleteAuth, checkReadAuth } from '../utils/Auth.js'

/**
 * @todo This should return several items
 *
 * @param {StructStore} store
 * @param {ID} id
 * @return {{item:Item, diff:number}}
 */
export const followRedone = (store, id) => {
  /**
   * @type {ID|null}
   */
  let nextID = id
  let diff = 0
  let item
  do {
    if (diff > 0) {
      nextID = createID(nextID.client, nextID.clock + diff)
    }
    item = getItem(store, nextID)
    diff = nextID.clock - item.id.clock
    nextID = item.redone
  } while (nextID !== null && item instanceof Item)
  return {
    item, diff
  }
}

/**
 * Make sure that neither item nor any of its parents is ever deleted.
 *
 * This property does not persist when storing it into a database or when
 * sending it to other peers
 *
 * @param {Item|null} item
 * @param {boolean} keep
 */
export const keepItem = (item, keep) => {
  while (item !== null && item.keep !== keep) {
    item.keep = keep
    item = /** @type {AbstractType<any>} */ (item.parent)._item
  }
}

/**
 * Split leftItem into two items
 * @param {Transaction | null} transaction
 * @param {Item} leftItem
 * @param {number} diff
 * @return {Item}
 *
 * @function
 * @private
 */
export const splitItem = (transaction, leftItem, diff) => {
  // create rightItem
  const { client, clock } = leftItem.id
  const rightItem = new Item(
    createID(client, clock + diff),
    leftItem,
    createID(client, clock + diff - 1, getAtomicItemHash(leftItem, diff - 1)),
    leftItem.right,
    leftItem.rightOrigin,
    leftItem.parent,
    leftItem.parentSub,
    leftItem.content.splice(diff),
    leftItem.signature?.splice(diff),
    leftItem.source,
    leftItem.integrateSignature,
    leftItem.deleteSignature?.splice(diff)
  )
  
  if(leftItem.merkleTreeLeafNodes){
    rightItem.merkleTreeLeafNodes = leftItem.merkleTreeLeafNodes.splice(diff)
    // 当历史块被分割，就需要替换struct源
    rightItem.merkleTreeLeafNodes.forEach(leafNode => leafNode.replaceTargetStruct(leftItem, rightItem))
  }

  if (leftItem.deleted) {
    rightItem.markDeleted()
  }

  if(leftItem.hidden){
    rightItem.hidden = true
  }

  if (leftItem.keep) {
    rightItem.keep = true
  }
  if (leftItem.redone !== null) {
    rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff)
  }

  // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
  leftItem.right = rightItem
  // update right
  if (rightItem.right !== null) {
    rightItem.right.left = rightItem
  }
  // right is more specific.
  if(transaction){
    transaction._mergeStructs.push(rightItem)
  }
  // update parent._map
  if (rightItem.parentSub !== null && rightItem.right === null) {
    /** @type {AbstractType<any>} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
  }

  debugLog('splititem运行 ', leftItem, rightItem, diff)

  leftItem.length = diff
  return rightItem
}

/**
 * Redoes the effect of this operation.
 *
 * @param {Transaction} transaction The Yjs instance.
 * @param {Item} item
 * @param {Set<Item>} redoitems
 * @param {DeleteSet} itemsToDelete
 * @param {boolean} ignoreRemoteMapChanges
 *
 * @return {Item|null}
 *
 * @private
 */
export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges) => {
  const doc = transaction.doc
  const store = doc.store
  const ownClientID = doc.clientID
  const redone = item.redone
  if (redone !== null) {
    return getItemCleanStart(transaction, redone)
  }
  let parentItem = /** @type {AbstractType<any>} */ (item.parent)._item
  /**
   * @type {Item|null}
   */
  let left = null
  /**
   * @type {Item|null}
   */
  let right
  // make sure that parent is redone
  if (parentItem !== null && parentItem.deleted === true) {
    // try to undo parent if it will be undone anyway
    if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges) === null)) {
      return null
    }
    while (parentItem.redone !== null) {
      parentItem = getItemCleanStart(transaction, parentItem.redone)
    }
  }
  const parentType = parentItem === null ? /** @type {AbstractType<any>} */ (item.parent) : /** @type {ContentType} */ (parentItem.content).type

  if (item.parentSub === null) {
    // Is an array item. Insert at the old position
    left = item.left
    right = item
    // find next cloned_redo items
    while (left !== null) {
      /**
       * @type {Item|null}
       */
      let leftTrace = left
      // trace redone until parent matches
      while (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item !== parentItem) {
        leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
      }
      if (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item === parentItem) {
        left = leftTrace
        break
      }
      left = left.left
    }
    while (right !== null) {
      /**
       * @type {Item|null}
       */
      let rightTrace = right
      // trace redone until parent matches
      while (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item !== parentItem) {
        rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
      }
      if (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item === parentItem) {
        right = rightTrace
        break
      }
      right = right.right
    }
  } else {
    right = null
    if (item.right && !ignoreRemoteMapChanges) {
      left = item
      // Iterate right while right is in itemsToDelete
      // If it is intended to delete right while item is redone, we can expect that item should replace right.
      while (left !== null && left.right !== null && isDeleted(itemsToDelete, left.right.id)) {
        left = left.right
      }
      // follow redone
      // trace redone until parent matches
      while (left !== null && left.redone !== null) {
        left = getItemCleanStart(transaction, left.redone)
      }
      if (left && left.right !== null) {
        // It is not possible to redo this item because it conflicts with a
        // change from another client
        return null
      }
    } else {
      left = parentType._map.get(item.parentSub) || null
    }
  }
  const nextClock = getState(store, ownClientID)
  const nextId = createID(ownClientID, nextClock)
  const redoneItem = new Item(
    nextId,
    left, left && left.lastId,
    right, right && right.id,
    parentType,
    item.parentSub,
    item.content.copy(),
    undefined,
    getSourceFromTransaction(transaction)
  )
  item.redone = nextId
  keepItem(redoneItem, true)
  redoneItem.integrate(transaction, 0)
  return redoneItem
}

/**
 * 
 * @param {Transaction} transaction 
 */
export const getSourceFromTransaction = (transaction) => {
  if(transaction.local) return 'local'
  if(transaction.origin === 'level') return 'level'
  return 'decoder'
}

/**
 * Abstract class that represents any content.
 */
export class Item extends AbstractStruct {
  /**
   * @param {ID} id
   * @param {Item | null} left
   * @param {ID | null} origin
   * @param {Item | null} right
   * @param {ID | null} rightOrigin
   * @param {AbstractType<any>|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
   * @param {string | null} parentSub
   * @param {AbstractContent} content
   * @param {Array<Signature> | null | undefined} signature
   * @param {string} source
   * @param {IntegrateSignature | null | undefined} integrateSignature
   * @param {Array<DeleteSignature | null> | null | undefined} deleteSignature
   */
  constructor (id, left, origin, right, rightOrigin, parent, parentSub, content, signature = null, source = 'local', integrateSignature = null, deleteSignature = null) {
    super(id, content.getLength())
    /**
     * The item that was originally to the left of this item.
     * @type {ID | null}
     */
    this.origin = origin
    /**
     * The item that is currently to the left of this item.
     * @type {Item | null}
     */
    this.left = left
    /**
     * The item that is currently to the right of this item.
     * @type {Item | null}
     */
    this.right = right
    /**
     * The item that was originally to the right of this item.
     * @type {ID | null}
     */
    this.rightOrigin = rightOrigin
    /**
     * @type {AbstractType<any>|ID|null}
     */
    this.parent = parent
    /**
     * If the parent refers to this item with some kind of key (e.g. YMap, the
     * key is specified here. The key is then used to refer to the list in which
     * to insert this item. If `parentSub = null` type._start is the list in
     * which to insert to. Otherwise it is `parent._map`.
     * @type {String | null}
     */
    this.parentSub = parentSub
    /**
     * If this type's effect is redone this type refers to the type that undid
     * this operation.
     * @type {ID | null}
     */
    this.redone = null
    /**
     * @type {AbstractContent}
     */
    this.content = content
    /**
     * bit1: keep
     * bit2: countable
     * bit3: deleted
     * bit4: mark - mark node as fast-search-marker
     * @type {number} byte
     */
    this.info = this.content.isCountable() ? binary.BIT2 : 0

    // item是否隐藏
    this.hidden = false

    // 每一个item都有签名
    this.signature = signature

    this.integrateSignature = integrateSignature

    /**
     * @type {Array<DeleteSignature | null> | null}
     */
    this.deleteSignature = deleteSignature

    this.source = source

    /**
     * @type {Array<MerkleTreeLeafNode> | null}
     */
    // 绑定的叶子节点列表
    this.merkleTreeLeafNodes = null

    /**
     * 冲突链前item
     * @type {Item | null | undefined}
     */
    this.nextConflict= null

    // 初始化item的hash，该hash是该item的第一个原子Item的hash
    this.id.hash = getAtomicItemHash(this)
  }

  /**
   * This is used to mark the item as an indexed fast-search marker
   *
   * @type {boolean}
   */
  set marker (isMarked) {
    if (((this.info & binary.BIT4) > 0) !== isMarked) {
      this.info ^= binary.BIT4
    }
  }

  get marker () {
    return (this.info & binary.BIT4) > 0
  }

  /**
   * If true, do not garbage collect this Item.
   */
  get keep () {
    return (this.info & binary.BIT1) > 0
  }

  set keep (doKeep) {
    if (this.keep !== doKeep) {
      this.info ^= binary.BIT1
    }
  }

  get countable () {
    return (this.info & binary.BIT2) > 0
  }

  /**
   * Whether this item was deleted or not.
   * @type {Boolean}
   */
  get deleted () {
    return (this.info & binary.BIT3) > 0
  }

  set deleted (doDelete) {
    if (this.deleted !== doDelete) {
      this.info ^= binary.BIT3
    }
  }

  markDeleted () {
    this.info |= binary.BIT3
  }

  /**
   * Return the creator clientID of the missing op or define missing items and return null.
   * 返回缺失的依赖的ID
   * @param {Transaction} transaction
   * @param {StructStore} store
   * @return {null | ID}
   */
  getMissing (transaction, store) {
    // 在integrateStruct时进行了规则检查，保证了所有的来自于相同clientId的相关item都必须在当前item出现前出现
    // 在此处需要处理其他相关item是其他clientID时的情况
    // origin、rightOrigin、parent都有指定的item和hash，即使item的limport已经达到了要求
    // 但是hash不对，那么依然无法应用, masterFind用于带hash进行精确查找
    if (this.origin && 
      (this.origin.clock >= getState(store, this.origin.client) ||
       !masterFind(store, this.origin)
      )
      ) {
      // return this.origin.client
      return this.origin
    }
    if (this.rightOrigin && 
      (this.rightOrigin.clock >= getState(store, this.rightOrigin.client) ||
      !masterFind(store, this.rightOrigin)
      )) {
      // return this.rightOrigin.client
      return this.rightOrigin
    }
    if (this.parent && this.parent.constructor === ID &&
      (this.parent.clock >= getState(store, this.parent.client) ||
      !masterFind(store, this.parent)
      )) {
      // return this.parent.client
      return this.parent
    }

    // We have all missing ids, now find the items
    if (this.origin) {
      this.left = getItemCleanEnd(transaction, store, this.origin)
      this.origin = this.left.lastId
    }
    if (this.rightOrigin) {
      this.right = getItemCleanStart(transaction, this.rightOrigin)
      this.rightOrigin = this.right.id
    }
    if ((this.left && this.left.constructor === GC) || (this.right && this.right.constructor === GC)) {
      this.parent = null
    }
    // only set parent if this shouldn't be garbage collected
    if (!this.parent) {
      if (this.left && this.left.constructor === Item) {
        this.parent = this.left.parent
        this.parentSub = this.left.parentSub
      }
      if (this.right && this.right.constructor === Item) {
        this.parent = this.right.parent
        this.parentSub = this.right.parentSub
      }
    } else if (this.parent.constructor === ID) {
      const parentItem = masterFind(store, this.parent)
      if(parentItem){
        if (parentItem.constructor === GC) {
          this.parent = null
        } else {
          // @ts-ignore
          this.parent = /** @type {ContentType} */ (parentItem.content).type
        }
      }
    }
    return null
  }

  /**
   * 
   * @returns { string | Array<number> | undefined }
   */
  getParentSignal(){
    const parent = /** @type {AbstractType<any>} */ (this.parent)
    if (parent._item !== undefined) {
      const parentItem = parent._item
      if (parentItem === null) {
        // parent type on y._map
        // find the correct key
        const ykey = findRootTypeKey(parent)
        return ykey
      } else {
        const id = parentItem.id
        return [ id.client, id.clock ]
      }
    } else if (parent.constructor === String) { // this edge case was added by differential updates
      return parent
    } else if (parent.constructor === ID) {
      // @ts-ignore
      return [ parent.client, parent.clock ]
    } else {
      return
    }
  }

  /**
   * @param {Transaction} transaction
   * @param {number} offset
   * @param {boolean} sideEffect 立即执行副作用 
   * @returns {() => void} 如果sideEffect为false，则会返回一个执行副作用的函数，内部进行签名验证和权限验证
   */
  integrate (transaction, offset, sideEffect = true) {
    if (offset > 0) {
      this.id.clock += offset
      this.left = getItemCleanEnd(transaction, transaction.doc.store, createID(this.id.client, this.id.clock - 1))
      this.origin = this.left.lastId
      this.content = this.content.splice(offset)
      if(this.signature && this.signature instanceof Array){
        this.signature = this.signature.splice(offset)
      }
      if(this.deleteSignature && this.deleteSignature instanceof Array){
        this.deleteSignature = this.deleteSignature.splice(offset)
      }
      this.length -= offset
    }

    let sideEffectTask = () => {}

    if (this.parent) {
      if ((!this.left && (!this.right || this.right.left !== null)) || (this.left && this.left.right !== this.right)) {
        /**
         * @type {Item|null}
         */
        let left = this.left

        /**
         * @type {Item|null}
         */
        let o
        // set o to the first conflicting item
        if (left !== null) {
          o = left.right
        } else if (this.parentSub !== null) {
          o = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null
          while (o !== null && o.left !== null) {
            o = o.left
          }
        } else {
          o = /** @type {AbstractType<any>} */ (this.parent)._start
        }
        // TODO: use something like DeleteSet here (a tree implementation would be best)
        // @todo use global set definitions
        /**
         * @type {Set<Item>}
         */
        const conflictingItems = new Set()
        /**
         * @type {Set<Item>}
         */
        const itemsBeforeOrigin = new Set()
        // Let c in conflictingItems, b in itemsBeforeOrigin
        // ***{origin}bbbb{this}{c,b}{c,b}{o}***
        // Note that conflictingItems is a subset of itemsBeforeOrigin
        while (o !== null && o !== this.right) {
          itemsBeforeOrigin.add(o)
          conflictingItems.add(o)
          if (compareIDs(this.origin, o.origin)) {
            // case 1
            if (o.id.client < this.id.client) {
              left = o
              conflictingItems.clear()
            } else if (compareIDs(this.rightOrigin, o.rightOrigin)) {
              // this and o are conflicting and point to the same integration points. The id decides which item comes first.
              // Since this is to the left of o, we can break here
              break
            } // else, o might be integrated before an item that this conflicts with. If so, we will find it in the next iterations
          // @ts-ignore
          } else if (o.origin !== null && itemsBeforeOrigin.has(masterFind(transaction.doc.store, o.origin))) { // use getItem instead of getItemCleanEnd because we don't want / need to split items.
            // case 2
            // @ts-ignore
            if (!conflictingItems.has(masterFind(transaction.doc.store, o.origin))) {
              left = o
              conflictingItems.clear()
            }
          } else {
            break
          }
          o = o.right
        }
        this.left = left
      }
      // reconnect left/right + update parent map/start if necessary
      if (this.left !== null) {
        const right = this.left.right
        this.right = right
        this.left.right = this
      } else {
        let r
        if (this.parentSub !== null) {
          r = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null
          while (r !== null && r.left !== null) {
            r = r.left
          }
        } else {
          r = /** @type {AbstractType<any>} */ (this.parent)._start
          ;/** @type {AbstractType<any>} */ (this.parent)._start = this
        }
        this.right = r
      }
      if (this.right !== null) {
        this.right.left = this
      } else if (this.parentSub !== null) {
        // set as current parent value if right === null and this is parentSub
        /** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
        if (this.left !== null) {
          // this is the current attribute value of parent. delete right
          this.left.delete(transaction)
        }
      }
      // adjust length of parent
      if (this.parentSub === null && this.countable && !this.deleted) {
        /** @type {AbstractType<any>} */ (this.parent)._length += this.length
      }
      addStruct(transaction.doc.store, this)
      this.content.integrate(transaction, this)
      // add parent to transaction.changed
      addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
      
      // 添加Merkle Tree叶子结点
      this.merkleTreeLeafNodes = addMerkleTreeLeafNodes(transaction.doc.byzantineStore, this, transaction)

      if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
        // delete if parent is deleted or if this is not the current attribute value of parent
        this.delete(transaction)
      } else {
        if(sideEffect){
          this.sign(transaction, offset)
          this.verify(transaction, offset)
        } else {
          sideEffectTask = () => {
            this.sign(transaction, offset)
            this.verify(transaction, offset)
          }
        }
      }
    } else {
      // parent is not defined. Integrate GC struct instead
      new GC(this.id, this.length).integrate(transaction, 0)
    }

    return sideEffectTask
  }

  /**
   * 当用户创建了一个local的item，在integrate阶段需要按照策略对其创建数字签名
   * @param {Transaction | string} transaction 
   * @param {number} offset
   * @param {Array<string>} targets
   * @returns {boolean}
   */
  sign(transaction, offset, targets = [ 'atom', 'aggregate' ]){
    const source = typeof transaction === 'string' ? transaction : getSourceFromTransaction(transaction)
    if(source === 'local'){
      // @ts-ignore
      const doc = typeof transaction === 'string' ? this.parent.doc : transaction.doc
      const light = getSignatureLevelConfig(doc, 'signConfig', 'light')

      if(!getSignatureLevelConfig(doc, 'checkSource', source)) return true // 如果不对该来源加签，就结束

      if(targets.includes('atom') && getSignatureLevelConfig(doc, 'checkType', 'atom')
        && ((!this.signature 
            || this.signature.length === 0
            || this.signature.findIndex(signatureItem => !signatureItem) > -1
            || (!light && this.signature.findIndex(signatureItem => signatureItem.content === '') > -1)
        ))
      ){
        this.signature = Signature.sign(this, doc)
      }

      if(targets.includes('aggregate') && getSignatureLevelConfig(doc, 'checkType', 'aggregate') 
        && (!this.integrateSignature
            || (!light && this.integrateSignature.content === '')
        ) && this.length > 1){
        this.integrateSignature = IntegrateSignature.sign(this, doc)
      }

      if(targets.includes('delete') && getSignatureLevelConfig(doc, 'checkType', 'delete')
        && ((!this.deleteSignature
            || this.deleteSignature.length === 0
            || this.deleteSignature.findIndex(signatureItem => !signatureItem) > -1
            || (!light && this.deleteSignature.findIndex(signatureItem => !signatureItem || signatureItem.content === '') > -1)
        ))
      ){
        this.deleteSignature = DeleteSignature.sign(this, doc)
      }

      return true
    }
    return false
  }

  /**
   * @private
   * @param {*} transaction 
   * @param {*} offset
   */
  verify(transaction, offset){
    this.checkSignature(transaction, offset, () => {
      checkAddAuth(this, transaction)
      checkReadAuth(this, transaction)
    })
  }

  /**
   * @private
   * @param {*} transaction 
   * @param {*} offset 
   * @param {Function} successNext
   * @param {Function} failNext
   */
  checkIntegrateSignature(transaction, offset, successNext = () => {}, failNext = () => {}){
    const aggregate = getSignatureLevelConfig(transaction.doc, 'checkType', 'aggregate')
    if(aggregate){
      if(!this.integrateSignature){
        failNext()
        return
      }

      const verifyResult = IntegrateSignature.verify(this, transaction.doc, this.integrateSignature)
      if(verifyResult instanceof Promise){
        debugLog('checkIntegrateSignature - 聚合签名验证 - promise')
        verifyResult.then((result) => {
          if(result){
            debugLog('checkIntegrateSignature - 聚合签名验证 - 验证成功', this)
            successNext()
          } else {
            debugLog('checkIntegrateSignature - 聚合签名验证 - 验证失败', this)
            failNext()
          }
        })
      } else if(typeof verifyResult === 'boolean'){
        debugLog('checkIntegrateSignature - 聚合签名验证 - boolean')
        if(verifyResult){
          debugLog('checkIntegrateSignature - 聚合签名验证 - 验证成功', this)
          successNext()
        } else {
          debugLog('checkIntegrateSignature - 聚合签名验证 - 验证失败', this)
          failNext()
        }
      } else {
        debugLog('checkIntegrateSignature - 聚合签名验证 - 没有处理过的类型 -' + typeof verifyResult)
        failNext()
      }
    } else {
      failNext()
    }
  }

  /**
   * @public
   * @param {Transaction} transaction 
   * @param {number} offset
   * @param {Function} successNext
   * @param {Function} failNext
   * 
   */
  checkAtomicSignature(transaction, offset, successNext = () => {}, failNext = () => {}){
    const atom = getSignatureLevelConfig(transaction.doc, 'checkType', 'atom')
    if(atom){
      if(!this.signature || this.signature.length !== this.length) failNext()
      /**
       * @type {boolean | Array<boolean | Promise<boolean>>}
      */
      const verifyResult = Signature.verify(this, transaction.doc, this.signature, offset)
      // @ts-ignore
      if(verifyResult && verifyResult.length > 0){
        const cb = () => {
          // @ts-ignore
          const firstFailedIndex = verifyResult.findIndex(res => !res)
          if(firstFailedIndex === -1){
            this.hide(transaction, false)
            successNext()
            return
          }
          
          if(firstFailedIndex === 0){
            if(this.length > 1) {
              getItemCleanStart(transaction, createID(this.id.client, this.id.clock + 1))
              .verify(transaction, 0)
            }
            this.hide(transaction, true)
            return
          }
          
          const failedItem = getItemCleanStart(transaction, createID(this.id.client, this.id.clock + firstFailedIndex))
          if(firstFailedIndex < this.length - 1){
            getItemCleanStart(transaction, createID(this.id.client, this.id.clock + firstFailedIndex + 1))
            .verify(transaction, 0)
          }
          this.hide(transaction, false)
          failedItem.hide(transaction, true)
        }
        /**
         * @type {Array<Promise<boolean>>}
         */
        // @ts-ignore
        const promises = verifyResult.filter(res => res instanceof Promise)
        if(promises.length > 0){
          Promise.allSettled(promises).then(values => {
            for(let i = 0 ; i < values.length ; i ++){
              // @ts-ignore
              const index = verifyResult.findIndex(res => res instanceof Promise)
              if(index > -1){
                // @ts-ignore
                verifyResult[index] = values[i].status === 'fulfilled' ? values[i].value : false
              }
            }
            cb()  
          })
        } else cb()
      } else {
        debugLog('隐藏没有通过signature校验的内容，没有有效的signature校验结果')
      }
    } else {
      successNext()
    }
  }

  /**
   * @private
   * @param {Transaction} transaction
   * @param {number} offset
   * @param {Function} successNext
   */
  *checkSignatureIterate(transaction, offset, successNext = () => {}){
    this.checkIntegrateSignature(transaction, offset, successNext, () => {
      this.checkAtomicSignature(transaction, offset, successNext)
    })
  }

  /**
   * 签名校验过程
   * @param {Transaction} transaction
   * @param {number} offset
   * @param {Function} successNext
   */
  checkSignature(transaction, offset, successNext = () => {}){
    const doc = transaction.doc
    const signatureConfig = doc.signature
    const checkSource = signatureConfig.checkSource[this.source]
    
    if(checkSource){
      const lazyCheck = getSignatureLevelConfig(transaction.doc, 'verifyConfig', 'lazyCheck')
      const t = this.checkSignatureIterate(transaction, offset, successNext)
      if(!lazyCheck){
        let res
        do {
          res = t.next()
        } while(!res.done)
        return
      }
      const priority = fiber.getPriority()
      // @ts-ignore
      fiber.taskDriver.addTask(new BaseTask({
        iter: t,
        priority
      }, '' + priority))
    } else {
      successNext()
    }
  }

  /**
   * Returns the next non-deleted item
   */
  get next () {
    let n = this.right
    while (n !== null && (n.deleted || n.hidden)) {
      n = n.right
    }
    return n
  }

  /**
   * Returns the previous non-deleted item
   */
  get prev () {
    let n = this.left
    while (n !== null && (n.deleted || n.hidden)) {
      n = n.left
    }
    return n
  }

  /**
   * Computes the last content address of this Item.
   */
  get lastId () {
    // allocating ids is pretty costly because of the amount of ids created, so we try to reuse whenever possible
    return this.length === 1 ? this.id : createID(this.id.client, this.id.clock + this.length - 1, getAtomicItemHash(this, this.length - 1))
  }

  /**
   * Try to merge two items
   *
   * @param {Item} right
   * @return {boolean}
   */
  mergeWith (right) {
    /**
     * 
     * @returns {Array<any>}
     */
    const isSignatureMergable = (attr = 'signature') => {
      // @ts-ignore
      if(!this[attr] && !right[attr]){
        return [ true, () => null ]
      // @ts-ignore
      } else if(this[attr] instanceof Array && 
        // @ts-ignore
        right[attr] instanceof Array
      ){
        // @ts-ignore
        return [ true, () => [ ...(this[attr] || []), ...(right[attr] || []) ]]
      } else {
        return [ false, () => [] ]
      }
    }

    // @ts-ignore
    const signatureConfig = this.parent.doc.signature
    const currentUser = signatureConfig.currentUser
    const signatureCheckResult = isSignatureMergable()
    const deleteSignatureCheckResult = isSignatureMergable('deleteSignature')
    const newSignature = signatureCheckResult[1]()
    const newDeleteSignature = deleteSignatureCheckResult[1]()

    const isIntegrateSignatureMergable = () => {
      /**
       * 不可聚合情况
       * 1. 没有被删除时，原子签名没有通过
       * 2. 没有被删除时，原子签名人不都是我
       * 3. 已经被删除时，删除签名校验没有通过
       * 4. 已经被删除时，删除签名人不都是我
       */
        if((/** 已被删除时 */this.deleted && newDeleteSignature && newDeleteSignature.findIndex((/** @type {Signature} */ signatureItem) => 
        signatureItem.signatory !== currentUser || signatureItem.verify !== 1) > -1)
          || /** 没有被删除时 */ (!this.deleted && newSignature && newSignature.findIndex((/** @type {Signature} */ signatureItem) => 
          signatureItem.signatory !== currentUser || signatureItem.verify !== 1) > -1)
        ){
          return false
        }
        return true
    }

    const mergeMerkleTreeLeafNodes = () => {
      if(this.merkleTreeLeafNodes && right.merkleTreeLeafNodes){
        this.merkleTreeLeafNodes = [ ...this.merkleTreeLeafNodes, ...right.merkleTreeLeafNodes]
        right.merkleTreeLeafNodes.forEach(leafNode => leafNode.replaceTargetStruct(right, this))
      }
    }
    
    if (
      this.constructor === right.constructor &&
      compareIDs(right.origin, this.lastId) &&
      this.right === right &&
      compareIDs(this.rightOrigin, right.rightOrigin) &&
      this.id.client === right.id.client &&
      this.id.clock + this.length === right.id.clock &&
      right.length > 0 &&
      this.deleted === right.deleted &&
      this.hidden === right.hidden &&
      this.redone === null &&
      right.redone === null &&
      signatureCheckResult[0] &&
      deleteSignatureCheckResult[0] &&
      isIntegrateSignatureMergable() &&
      this.content.constructor === right.content.constructor &&
      this.content.mergeWith(right.content)
    ) {
      const searchMarker = /** @type {AbstractType<any>} */ (this.parent)._searchMarker
      if (searchMarker) {
        searchMarker.forEach(marker => {
          if (marker.p === right) {
            // right is going to be "forgotten" so we need to update the marker
            marker.p = this
            // adjust marker index
            if (!this.deleted && this.countable) {
              marker.index -= this.length
            }
          }
        })
      }

      this.signature = newSignature
      this.deleteSignature = newDeleteSignature
      mergeMerkleTreeLeafNodes()

      if (right.keep) {
        this.keep = true
      }
      this.right = right.right
      if (this.right !== null) {
        this.right.left = this
      }
      this.length += right.length

      // @ts-ignore
      const aggregate = getSignatureLevelConfig(this.parent.doc, 'checkType', 'aggregate')
      if(aggregate){
        if(IntegrateSignature.isSuitable(this, right.integrateSignature)){
          this.integrateSignature = right.integrateSignature
        } else {
          // @ts-ignore
          this.integrateSignature = IntegrateSignature.sign(this, this.parent.doc)
        }
      }

      return true
    }
    return false
  }

  /**
   * 回滚删除，撤销item的删除
   * @param {Transaction} transaction 
   */
  unDelete(transaction){
    const cb = (/** @type {Transaction} */ transaction) => {
      if(this.deleted){
        const parent = /** @type {AbstractType<any>} */ (this.parent)
        // adjust the length of parent
        if (this.countable && this.parentSub === null) {
          parent._length += this.length
        }
        this.deleted = false
        removeFromDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length, this.deleteSignature)
        addChangedTypeToTransaction(transaction, parent, this.parentSub)
        this.content.unDelete(transaction)
      }
    }
    if(transaction.finished){
      transact(transaction.doc, cb, transaction.origin, transaction.local)
    } else {
      cb(transaction)
    }
  }

  /**
   * Mark this Item as deleted.
   *
   * @param {Transaction} transaction
   * @param {Array<DeleteSignature | null> | null} signature
   */
  delete (transaction, signature = null) {
    if (!this.deleted) {
      this.prepareDeleteSignaturePhase(transaction, signature)
    }
  }

  /**
   * 签名准备阶段，设置签名、创建签名等工作
   * @private
   * @param {Transaction} transaction
   * @param {Array<DeleteSignature | null> | null} signature
   */ 
  prepareDeleteSignaturePhase(transaction, signature = null){
    this.deleteSignature = signature
    const shouldCheckDelete = getSignatureLevelConfig(transaction.doc, 'checkType', 'delete')
    const signResult = this.sign(transaction, 0, [ 'delete' ])
    const checkPriority = transaction.checkPriority.delete

    if(checkPriority){
      if(signResult || !shouldCheckDelete){
        checkDeleteAuth(this, transaction)
      } else if(shouldCheckDelete){
        this.checkDeleteSignaturePhase(transaction)
      }
    } else {
      this.deletePhase(transaction, signResult)
    }
  }

  /**
   * 执行删除阶段
   * @param {Transaction} transaction
   * @param {boolean} signResult
   */
  deletePhase(transaction, signResult = false){
    const cb = (/** @type {Transaction} */ transaction) => {
      const parent = /** @type {AbstractType<any>} */ (this.parent)
      // adjust the length of parent
      if (this.countable && this.parentSub === null) {
        parent._length -= this.length
      }
      this.markDeleted()
      addToDeleteSet(transaction.deleteSet, 
        this.id.client, 
        this.id.clock, 
        this.length, 
        this.deleteSignature, 
        getAllAtomicItemHash(this)
      )
      addChangedTypeToTransaction(transaction, parent, this.parentSub)
      this.content.delete(transaction)
  
      const checkPriority = transaction.checkPriority.delete
      const shouldCheckDelete = getSignatureLevelConfig(transaction.doc, 'checkType', 'delete')
      if(!checkPriority){
        if(signResult || !shouldCheckDelete){
          checkDeleteAuth(this, transaction)
        } else if(shouldCheckDelete){
          this.checkDeleteSignaturePhase(transaction)
        }
      }
    }
    if(transaction.finished){
      transact(transaction.doc, cb, transaction.origin, transaction.local)
    } else {
      cb(transaction)
    }
  }

  /**
   * 检查删除签名的合法性的阶段，会根据检测结果进行item的分离
   * @param {Transaction} transaction
   */
  checkDeleteSignaturePhase(transaction){
    if(!this.deleteSignature) return false
    const checkPriority = transaction.checkPriority.delete
    const verifyResult = DeleteSignature.verify(this, transaction.doc, this.deleteSignature, 0)
    // @ts-ignore
    if(verifyResult && verifyResult.length > 0){
      const cb = () => {
        // @ts-ignore
        const firstFailedIndex = verifyResult.findIndex(res => !res)
        
        if(firstFailedIndex === 0){
          if(this.length > 1) {
            getItemCleanStart(transaction, createID(this.id.client, this.id.clock + 1))
            .checkDeleteSignaturePhase(transaction)
          }
          // 当前item校验不通过
          if(!checkPriority){
            this.unDelete(transaction)
          }

          return
        } else if(firstFailedIndex > 0){
          const len = this.length
          const failedItem = getItemCleanStart(transaction, createID(this.id.client, this.id.clock + firstFailedIndex))
          if(firstFailedIndex < len - 1){
            getItemCleanStart(transaction, createID(this.id.client, this.id.clock + firstFailedIndex + 1)).checkDeleteSignaturePhase(transaction)
          }
          if(!checkPriority){
            failedItem.unDelete(transaction)
          }
        }

        checkDeleteAuth(this, transaction)
      }
      /**
       * @type {Array<Promise<boolean>>}
       */
      // @ts-ignore
      const promises = verifyResult.filter(res => res instanceof Promise)
      if(promises.length > 0){
        Promise.allSettled(promises).then(values => {
          for(let i = 0 ; i < values.length ; i ++){
            // @ts-ignore
            const index = verifyResult.findIndex(res => res instanceof Promise)
            if(index > -1){
              // @ts-ignore
              verifyResult[index] = values[i].status === 'fulfilled' ? values[i].value : false
            }
          }
          cb()  
        })
      } else {
        cb()
      }
    }
  }

  /**
   * Mark this Item as hidden or not.
   * hidden操作不会修改长度，只是隐藏item
   *
   * @param {Transaction} transaction
   */
  hide(transaction, hidden = true, offset = -1){
    const cb = (/** @type {Transaction} */ transaction) => {
      if(this.hidden !== hidden){
        if(offset > -1){
          if(offset === 0){
            if(this.length > 1) {
              getItemCleanStart(transaction, createID(this.id.client, this.id.clock + 1))
            }
            this.applyHideProcess(transaction, hidden)
            return
          }
          
          const rightItem = getItemCleanStart(transaction, createID(this.id.client, this.id.clock + offset))
          if(offset < this.length - 1){
            getItemCleanStart(transaction, createID(this.id.client, this.id.clock + offset + 1))
          }
          rightItem.applyHideProcess(transaction, hidden)
        } else {
          this.applyHideProcess(transaction, hidden)
        }
      }
    }
    if(transaction.finished){
      transact(transaction.doc, cb, transaction.origin, transaction.local)
    } else {
      cb(transaction)
    }
  }

  /**
   * @private
   * @param {*} transaction 
   * @param {*} hidden 
   */
  applyHideProcess(transaction, hidden = true){
    this.hidden = hidden
    const parent = /** @type {AbstractType<any>} */ (this.parent)
    addChangedTypeToTransaction(transaction, parent, this.parentSub)
  }

  /**
   * gc在拜占庭模式下不可用，这是因为会出现历史不同而导致重新同步时，缺失的项目会被弥补回来
   * @param {StructStore} store
   * @param {boolean} parentGCd
   */
  gc (store, parentGCd) {
    if (!this.deleted) {
      throw error.unexpectedCase()
    }
    this.content.gc(store)
    if (parentGCd) {
      replaceStruct(store, this, new GC(this.id, this.length))
    } else {
      // @ts-ignore
      this.content = new ContentDeleted(this.length)
    }
  }

  /**
   * signal a unique item, except origin/left and so on not immutable attr
   * item可能是多个item进行压缩的，所以需要找到确切的位置
   * @param {UpdateEncoderV1} encoder 
   * @param {number} offset
   * @param {boolean} needContent
   */
  signal (encoder, offset, needContent = true){
    // 写入client
    encoder.writeAny(this.id.client)
    // 写入limport
    encoder.writeAny(this.id.clock + offset)
    // 写入content
    if(needContent){
      this.content.signal(encoder, offset)
    }
  }

  /**
   * Transform the properties of this type to binary and write it to an
   * BinaryEncoder.
   *
   * This is called when this Item is sent to a remote peer.
   *
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
   * @param {number} offset offset一定不能比this.length - 1大
   * @param {boolean} hashMode hash模式，计算某一个offset的hash
   */
  write (encoder, offset, hashMode = false) {
    debugLog('item - write, 来自于', this)

    const origin = offset > 0 ? createID(
      this.id.client, 
      this.id.clock + offset - 1, 
      getAtomicItemHash(this, offset - 1)) : this.origin

    // 计算rightOrigin
    let rightOrigin = this.rightOrigin

    const parentSub = this.parentSub

    if(!hashMode){
      const info = (this.content.getRef() & binary.BITS5) |
        (origin === null ? 0 : binary.BIT8) | // origin is defined
        (rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
        (parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
      encoder.writeInfo(info)
    }

    // 写入自己的clock
    // 原来是不需要写clock的，是因为每一个update会带有一个起点clock，之后所有的item的clock都在此clock之后进行按位计算
    // 但是引入拜占庭后，会出现同clock的情况，因此每一个item需要独立写入clock
    encoder.writeClock(this.id)

    if (origin !== null) {
      encoder.writeLeftID(origin)
    }
    if (rightOrigin !== null) {
      encoder.writeRightID(rightOrigin)
    }
    if (origin === null && rightOrigin === null) {
      const parent = /** @type {AbstractType<any>} */ (this.parent)
      if (parent._item !== undefined) {
        const parentItem = parent._item
        if (parentItem === null) {
          // parent type on y._map
          // find the correct key
          const ykey = findRootTypeKey(parent)
          encoder.writeParentInfo(true) // write parentYKey
          encoder.writeString(ykey)
        } else {
          encoder.writeParentInfo(false) // write parent id
          encoder.writeLeftID(parentItem.id)
        }
      } else if (parent.constructor === String) { // this edge case was added by differential updates
        encoder.writeParentInfo(true) // write parentYKey
        encoder.writeString(parent)
      } else if (parent.constructor === ID) {
        encoder.writeParentInfo(false) // write parent id
        encoder.writeLeftID(parent)
      } else {
        error.unexpectedCase()
      }
      if (parentSub !== null) {
        encoder.writeString(parentSub)
      }
    }

    debugLog('item - write - content内容是', this, () => this.content.getContent(), 'offset是', offset)
    this.content.write(encoder, offset, hashMode)

    if(!hashMode){
      this.writeSignature(encoder, offset)
      this.writeIntegrateSignature(encoder)
    }
  }

  /**
   * @private
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
   * @param {number} offset
   */
  writeSignature(encoder, offset){
    this.sign(this.source, 0, [ 'atom' ])
    const havaSignature = !!this.signature && offset < this.signature.length
    encoder.writeHaveSignature(havaSignature)
    if(havaSignature && this.signature){
      encoder.writeSignature(this.signature, offset)
    }
  }

  /**
   * @private
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
   */
  writeIntegrateSignature(encoder){
    this.sign(this.source, 0, [ 'aggregate' ])
    const haveIntegrateSignature = !!this.integrateSignature
    encoder.writeHaveIntegrateSignature(haveIntegrateSignature)
    if(haveIntegrateSignature && this.integrateSignature){
      encoder.writeIntegrateSignature(this.integrateSignature)
    }
  }
}

/**
 * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
 * @param {number} info
 */
export const readItemContent = (decoder, info) => {
    return contentRefs[info & binary.BITS5](decoder)
}

/**
 * A lookup map for reading Item content.
 *
 * @type {Array<function(UpdateDecoderV1 | UpdateDecoderV2):AbstractContent>}
 */
export const contentRefs = [
  () => { error.unexpectedCase() }, // GC is not ItemContent
  readContentDeleted, // 1
  readContentJSON, // 2
  readContentBinary, // 3
  readContentString, // 4
  readContentEmbed, // 5
  readContentFormat, // 6
  readContentType, // 7
  readContentAny, // 8
  readContentDoc, // 9
  () => { error.unexpectedCase() } // 10 - Skip is not ItemContent
]

/**
 * Do not implement this class!
 */
export class AbstractContent {
  /**
   * @return {number}
   */
  getLength () {
    throw error.methodUnimplemented()
  }

  /**
   * @return {Array<any>}
   */
  getContent () {
    throw error.methodUnimplemented()
  }

  /**
   * Should return false if this Item is some kind of meta information
   * (e.g. format information).
   *
   * * Whether this Item should be addressable via `yarray.get(i)`
   * * Whether this Item should be counted when computing yarray.length
   *
   * @return {boolean}
   */
  isCountable () {
    throw error.methodUnimplemented()
  }

  /**
   * @return {AbstractContent}
   */
  copy () {
    throw error.methodUnimplemented()
  }

  /**
   * @param {number} offset
   * @return {AbstractContent}
   */
  splice (offset) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {number} start
   * @param {number} end
   * @return {AbstractContent}
   */
  slice (start, end) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {AbstractContent} right
   * @return {boolean}
   */
  mergeWith (right) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {Transaction} transaction
   * @param {Item} item
   */
  integrate (transaction, item) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {Transaction} transaction
   */
  delete (transaction) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {Transaction} transaction
   */
  unDelete(transaction){
    throw error.methodUnimplemented()
  }

  /**
   * @param {StructStore} store
   */
  gc (store) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
   * @param {number} offset
   * @param {boolean} hashMode
   */
  write (encoder, offset, hashMode = false) {
    throw error.methodUnimplemented()
  }

  /**
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
   * @param {number} offset
   */
  signal (encoder, offset) {
    throw error.methodUnimplemented()
  }
  
  /**
   * @return {number}
   */
  getRef () {
    throw error.methodUnimplemented()
  }
}
