///|
/// A decoded, unintegrated update. Inspect or merge it without creating a replica.
pub struct Update {
  priv mut wire : Bytes
  priv mut blocks : Array[RawItem]
  priv deleted : IdSet
  priv mut has_blocks : Bool
}

///|
pub fn Update::Update() -> Update {
  { wire: b"\x00\x00", blocks: [], deleted: IdSet(), has_blocks: false, }
}

///|
pub fn Update::decode_v1(bytes : Bytes) -> Update raise DecodeError {
  let (blocks, deleted) = read_update({ bytes, offset: 0, })
  let header : Reader = { bytes, offset: 0, }
  {
    wire: bytes,
    blocks: normalize_items(blocks),
    deleted,
    has_blocks: header.uint() > 0UL,
  }
}

///|
pub fn Update::decode_v2(
  bytes : Bytes,
  max_items~ : Int32 = 1000000,
) -> Update raise DecodeError {
  let (blocks, deleted, has_blocks) = read_update_fields(
    UpdateReader::make(bytes, true, max_items),
  )
  let result = Update::from_blocks(blocks, deleted)
  result.has_blocks = has_blocks
  result
}

///|
pub fn Update::encode_v1(self : Update) -> Bytes {
  self.wire
}

///|
// Keep the public fallible signature for source compatibility.
#warnings("-unused_error_type")
pub fn Update::encode_v2(self : Update) -> Bytes raise DecodeError {
  encode_raw_update(self.blocks, self.deleted.deletion_map(), v2=true)
}

///|
pub fn Update::is_empty(self : Update) -> Bool {
  !self.has_blocks && self.deleted.is_empty()
}

///|
/// Whether a non-skip block covers the next expected clock for any client.
/// Deletions alone do not extend a state vector.
pub fn Update::extends(self : Update, state : StateVector) -> Bool {
  self.blocks.any(fn(item) {
    let clock = state.get(item.id.client)
    item.id.clock <= clock &&
    clock < item.id.clock + item.content.length().reinterpret_as_uint()
  })
}

///|
pub fn Update::state_vector(self : Update) -> StateVector {
  let result = StateVector()
  for item in self.blocks {
    if item.id.clock == result.get(item.id.client) {
      result.set_max(
        item.id.client,
        item.id.clock + item.content.length().reinterpret_as_uint(),
      )
    }
  }
  result
}

///|
pub fn Update::state_vector_lower(self : Update) -> StateVector {
  let result = StateVector()
  for item in self.blocks {
    if !result.contains_client(item.id.client) {
      result.set_max(item.id.client, item.id.clock)
    }
  }
  result
}

///|
/// The wire delete set has not been integrated: encoded item blocks (including
/// ContentDeleted) are included; include_deleted also includes GC blocks.
pub fn Update::insertions(
  self : Update,
  include_deleted~ : Bool = false,
) -> IdSet {
  let result = IdSet()
  for item in self.blocks {
    if include_deleted || !(item.content is Garbage(_)) {
      result.insert(item.id, item.content.length().reinterpret_as_uint())
    }
  }
  result
}

///|
pub fn Update::delete_set(self : Update) -> IdSet {
  self.deleted.copy()
}

///|
pub fn Update::merge(self : Update, other : Update) -> Unit {
  self.blocks = normalize_items(self.blocks + other.blocks)
  self.deleted.merge_with(other.deleted)
  self.wire = encode_raw_update(self.blocks, self.deleted.deletion_map())
  self.has_blocks = !self.blocks.is_empty()
}

///|
pub fn Update::encode_diff_v1(self : Update, state : StateVector) -> Bytes {
  self.encode_diff_wire(state, false)
}

///|
fn Update::encode_diff_wire(
  self : Update,
  state : StateVector,
  v2 : Bool,
) -> Bytes {
  let blocks = self.blocks.filter_map(fn(item) {
    let known = state.get(item.id.client)
    if item.id.clock + item.content.length().reinterpret_as_uint() > known {
      Some(item.trim_start(known))
    } else {
      None
    }
  })
  encode_raw_update(blocks, self.deleted.deletion_map(), v2~)
}

///|
// Keep the public fallible signature for source compatibility.
#warnings("-unused_error_type")
pub fn Update::encode_diff_v2(
  self : Update,
  state : StateVector,
) -> Bytes raise DecodeError {
  self.encode_diff_wire(state, true)
}

///|
// Keep the public fallible signature for source compatibility.
#warnings("-unused_error_type")
pub fn Doc::apply_update(self : Doc, update : Update) -> Unit raise DecodeError {
  self.apply_raw_update(update.blocks, update.deleted)
}

///|
fn Update::from_blocks(blocks : Array[RawItem], deleted : IdSet) -> Update {
  let blocks = normalize_items(blocks.map(RawItem::owned_update_item))
  let deleted = deleted.copy()
  {
    wire: encode_raw_update(blocks, deleted.deletion_map()),
    blocks,
    deleted,
    has_blocks: !blocks.is_empty(),
  }
}