///|
priv enum Parent {
  Root(String)
  Nested(Id)
} derive(Eq, Hash)

///|
priv enum SharedKind {
  SharedArray
  SharedMap
  SharedText
  SharedXmlElement(String)
  SharedXmlFragment
  SharedXmlText
  SharedOpaque(Int32)
  SharedWeak(LinkSource)
}

///|
priv enum Content {
  Characters(String)
  Values(ReadOnlyArray[Any])
  LegacyJson(ReadOnlyArray[Any])
  Binary(Bytes)
  Shared(SharedKind)
  Subdocument(Doc)
  Format(String, Any)
  Embed(Any)
  Deleted(Int32)
  Garbage(Int32)
}

///|
fn Content::length(self : Content) -> Int32 {
  match self {
    Characters(s) => s.length()
    Values(values) => values.length()
    LegacyJson(values) => values.length()
    Binary(_) | Shared(_) | Subdocument(_) | Format(_, _) | Embed(_) => 1
    Deleted(n) | Garbage(n) => n
  }
}

///|
// Yjs indexes text by UTF-16 code units. Splitting a surrogate pair replaces
// the orphaned halves with U+FFFD, while preserving their two logical clocks.
fn utf16_slice(s : String, start : Int32, end : Int32) -> String {
  let out = StringBuilder()
  let mut offset = 0
  for c in s {
    let size = if c.to_int() > 0xffff { 2 } else { 1 }
    if offset >= start && offset + size <= end {
      out.write_char(c)
    } else if offset < end && offset + size > start {
      out.write_char('\ufffd')
    }
    offset += size
  }
  out.to_string()
}

///|
fn Content::slice(self : Content, start : Int32, end : Int32) -> Content {
  match self {
    Characters(s) => Characters(utf16_slice(s, start, end))
    LegacyJson(values) =>
      LegacyJson(
        ReadOnlyArray::from_array(
          Array::makei(end - start, fn(i) { values[start + i] })[:],
        ),
      )
    Values(values) =>
      Values(
        ReadOnlyArray::from_array(
          Array::makei(end - start, fn(i) { values[start + i] })[:],
        ),
      )
    Binary(_) | Shared(_) | Subdocument(_) | Format(_, _) | Embed(_) => {
      guard start == 0 && end == 1 else { abort("binary item is indivisible") }
      self
    }
    Deleted(_) => Deleted(end - start)
    Garbage(_) => Garbage(end - start)
  }
}

///|
priv struct RawItem {
  id : Id
  origin : Id?
  right_origin : Id?
  parent : Parent?
  parent_sub : String?
  content : Content
}

///|
priv struct Item {
  id : Id
  origin : Id?
  right_origin : Id?
  parent : Parent?
  parent_sub : String?
  mut content : Content
  mut left : Id?
  mut right : Id?
  mut redone : Id?
  mut deleted : Bool
  mut keep : Bool
  mut linked : Bool
  links : Set[Id]
}

///|
priv struct Branch {
  mut position_hint : (Item, Int32)?
  weak_quotes : Set[Id]
  mut kind : SharedKind?
  mut head : Id?
  heads : Map[String, Id]
  values : Map[String, Id]
  mut formatted : Bool
}

///|
/// A document replica. Each independently edited replica must use a distinct
/// client ID. This initial API uses Yjs UTF-16 positions and retains tombstones.
pub struct Doc {
  // Original subdocument options exist only while this content is unintegrated.
  priv mut decoded_options : Any?
  priv pending_weak : Set[Id]
  priv weak_observers : Map[
    Parent,
    Observer[String, (WeakEvent, Transaction) -> Unit],
  ]
  priv identity : UInt64
  priv mut active_transaction : Transaction?
  priv mut notifying_transaction : Bool
  priv deep_observers : Map[
    Parent,
    Observer[String, (ReadOnlyArray[Event], Transaction) -> Unit],
  ]
  priv xml_observers : Map[
    Parent,
    Observer[String, (XmlEvent, Transaction) -> Unit],
  ]
  priv xml_text_observers : Map[
    Parent,
    Observer[String, (XmlTextEvent, Transaction) -> Unit],
  ]
  priv array_observers : Map[
    Parent,
    Observer[String, (ArrayEvent, Transaction) -> Unit],
  ]
  priv text_observers : Map[
    Parent,
    Observer[String, (TextEvent, Transaction) -> Unit],
  ]
  priv map_observers : Map[
    Parent,
    Observer[String, (MapEvent, Transaction) -> Unit],
  ]
  priv before_observers : Observer[String, (Transaction) -> Unit]
  priv after_observers : Observer[String, (Transaction) -> Unit]
  priv cleanup_observers : Observer[String, (Transaction) -> Unit]
  priv update_v1_observers : Observer[String, (Bytes, Transaction) -> Unit]
  priv update_v2_observers : Observer[String, (Bytes, Transaction) -> Unit]
  priv mut client : UInt64
  priv document_guid : String
  priv mut collection : String?
  priv auto_loading : Bool
  priv mut loading : Bool
  priv cleanup_enabled : Bool
  priv wire_gc : Bool
  priv wire_encoding : Int32
  priv mut owner : (Doc, Id)?
  priv children_docs : Map[UInt64, Doc]
  priv mut has_keep_marks : Bool
  priv undo_retention : Map[String, () -> Array[(UInt64, UInt32, UInt32)]]
  priv subdoc_observers : Observer[String, (SubdocsEvent, Transaction) -> Unit]
  priv destroy_observers : Observer[String, (Doc) -> Unit]
  priv state : StateVector
  priv clients : Map[UInt64, Array[Item]]
  priv items : Map[Id, Item]
  priv roots : Map[Parent, Branch]
  priv pending : Array[RawItem]
  priv mut collect_deletes : Bool
  priv deleted_items : Array[Item]
  priv inserted_items : Array[Item]
  priv deletes : Map[UInt64, Array[(UInt32, UInt32)]]
}

///|
pub fn Doc::Doc(
  client_id~ : UInt64,
  guid~ : String = "",
  collection_id? : String,
  auto_load~ : Bool = false,
  should_load~ : Bool = true,
  offset_kind~ : OffsetKind = Utf16,
  skip_gc~ : Bool = true,
  cleanup_formatting~ : Bool = true,
) -> Doc {
  make_doc(
    client_id,
    guid,
    collection_id,
    auto_load,
    should_load,
    !skip_gc,
    if offset_kind == Bytes {
      1
    } else {
      0
    },
    cleanup_enabled=cleanup_formatting,
  )
}

///|
fn make_doc(
  client : UInt64,
  guid : String,
  collection : String?,
  auto_load : Bool,
  should_load : Bool,
  wire_gc : Bool,
  wire_encoding : Int32,
  cleanup_enabled~ : Bool = true,
) -> Doc {
  {
    identity: allocate_identity(),
    active_transaction: None,
    notifying_transaction: false,
    weak_observers: {},
    deep_observers: {},
    xml_observers: {},
    xml_text_observers: {},
    array_observers: {},
    text_observers: {},
    map_observers: {},
    pending_weak: Set([]),
    before_observers: Observer(),
    after_observers: Observer(),
    cleanup_observers: Observer(),
    update_v1_observers: Observer(),
    update_v2_observers: Observer(),
    client,
    document_guid: guid,
    decoded_options: None,
    collection,
    auto_loading: auto_load,
    loading: should_load,
    wire_gc,
    wire_encoding,
    cleanup_enabled,
    owner: None,
    children_docs: {},
    has_keep_marks: false,
    undo_retention: {},
    subdoc_observers: Observer(),
    destroy_observers: Observer(),
    state: StateVector(),
    clients: Map([]),
    items: Map([]),
    roots: Map([]),
    pending: [],
    deletes: Map([]),
    collect_deletes: false,
    deleted_items: [],
    inserted_items: [],
  }
}

///|
pub fn Doc::client_id(self : Doc) -> UInt64 {
  self.client
}

///|
pub fn Doc::state_vector(self : Doc) -> StateVector {
  StateVector(entries=self.state.entries())
}

///|
pub fn Doc::pending_items(self : Doc) -> Int32 {
  self.pending.length()
}

///|
fn Doc::branch(self : Doc, name : Parent) -> Branch {
  match self.roots.get(name) {
    Some(b) => b
    None => {
      let b : Branch = {
        position_hint: None,
        weak_quotes: Set([]),
        kind: None,
        head: None,
        heads: Map([]),
        values: Map([]),
        formatted: false,
      }
      self.roots[name] = b
      b
    }
  }
}

///|
// Client blocks are sorted by clock, including histories with gaps. The first
// block whose end exceeds the requested clock may contain it or follow a gap.
fn first_block_after(blocks : Array[Item], clock : UInt32) -> Int32 {
  for lo = 0, hi = blocks.length(); lo < hi; {
    let mid = lo + (hi - lo) / 2
    let item = blocks[mid]
    if item.id.clock + item.content.length().reinterpret_as_uint() <= clock {
      continue mid + 1, hi
    } else {
      continue lo, mid
    }
  } nobreak {
    lo
  }
}

///|
fn Doc::find(self : Doc, id : Id) -> Item? {
  guard self.clients.get(id.client) is Some(items) else { return None }
  for lo = 0, hi = items.length(); lo < hi; {
    let mid = lo + (hi - lo) / 2
    let item = items[mid]
    if id.clock < item.id.clock {
      continue lo, mid
    } else if id.clock - item.id.clock >=
      item.content.length().reinterpret_as_uint() {
      continue mid + 1, hi
    } else {
      return Some(item)
    }
  } nobreak {
    None
  }
}

///|
fn Doc::split(self : Doc, item : Item, at : Int32) -> Item {
  if self.active_transaction is Some(transaction) {
    transaction.merge_clients.add(item.id.client) |> ignore
  }
  if item.parent_sub is None && item.parent is Some(parent) {
    self.branch(parent).position_hint = None
  }
  let length = item.content.length()
  guard at > 0 && at < length else { abort("invalid item split") }
  let id : Id = {
    client: item.id.client,
    clock: item.id.clock + at.reinterpret_as_uint(),
  }
  let tail : Item = {
    id,
    origin: Some({ client: id.client, clock: id.clock - 1U, }),
    right_origin: item.right_origin,
    parent: item.parent,
    parent_sub: item.parent_sub,
    content: item.content.slice(at, length),
    left: Some(item.id),
    right: item.right,
    redone: item.redone.map(fn(id) {
      ({ client: id.client, clock: id.clock + at.reinterpret_as_uint(), } : Id)
    }),
    deleted: item.deleted,
    keep: item.keep,
    linked: item.linked,
    links: Set::from_array(item.links.to_array()),
  }
  if item.right is Some(right) {
    self.items[right].left = Some(id)
  } else if item.parent is Some(parent) && item.parent_sub is Some(key) {
    self.branch(parent).values[key] = id
  }
  item.content = item.content.slice(0, at)
  item.right = Some(id)
  let entries = self.clients[item.id.client]
  let index = first_block_after(entries, item.id.clock)
  guard index < entries.length() && entries[index].id == item.id else {
    abort("missing split item")
  }
  entries.insert(index + 1, tail)
  self.items[id] = tail
  tail
}

///|
fn Doc::clean_start(self : Doc, id : Id) -> Item {
  let item = self.find(id).unwrap()
  if item.id.clock == id.clock {
    item
  } else {
    self.split(item, (id.clock - item.id.clock).reinterpret_as_int())
  }
}

///|
fn Doc::clean_end(self : Doc, id : Id) -> Item {
  let item = self.find(id).unwrap()
  let offset = (id.clock - item.id.clock).reinterpret_as_int() + 1
  if offset < item.content.length() {
    self.split(item, offset) |> ignore
  }
  item
}

///|
fn Doc::integrate(self : Doc, raw : RawItem) -> Bool {
  let end = raw.id.clock + raw.content.length().reinterpret_as_uint()
  let mut start = raw.id.clock
  let missing : Array[RawItem] = []
  let blocks = self.clients.get(raw.id.client).unwrap_or([])
  // Most incoming packets append or overlap the recent end of a client's
  // history. Locate the first overlap instead of rescanning its entire past.
  let first = first_block_after(blocks, start)
  for index in first..= end {
      break
    }
    if start < item.id.clock {
      missing.push({
        ..raw.trim_start(start),
        content: raw.content.slice(
          (start - raw.id.clock).reinterpret_as_int(),
          (item.id.clock - raw.id.clock).reinterpret_as_int(),
        ),
      })
    }
    start = start.max(item_end)
  }
  if start < end {
    missing.push(raw.trim_start(start))
  }
  for part in missing {
    if !self.integrate_missing(part) {
      return false
    }
  }
  true
}

///|
fn Doc::integrate_missing(self : Doc, raw : RawItem) -> Bool {
  if raw.parent is Some(Nested(id)) && self.find(id) is None {
    return false
  }
  for dep in [raw.origin, raw.right_origin] {
    if dep is Some(id) && self.find(id) is None {
      return false
    }
  }
  let mut left = raw.origin.map(fn(id) { self.clean_end(id).id })
  let right = raw.right_origin.map(fn(id) { self.clean_start(id).id })
  let mut parent = match raw.parent {
    Some(name) => Some(name)
    None =>
      match left {
        Some(id) => self.items[id].parent
        None => right.bind(fn(id) { self.items[id].parent })
      }
  }
  if parent is Some(Nested(id)) {
    let owner = self.find(id).unwrap()
    if !(owner.content is Shared(_)) {
      parent = None
    }
  }
  let parent_sub = match left.bind(fn(id) { self.items[id].parent_sub }) {
    Some(key) => Some(key)
    None =>
      match right.bind(fn(id) { self.items[id].parent_sub }) {
        Some(key) => Some(key)
        None => raw.parent_sub
      }
  }
  let mut content = raw.content
  if parent is None {
    content = Garbage(content.length())
  }
  let item : Item = {
    id: raw.id,
    origin: raw.origin,
    right_origin: raw.right_origin,
    parent,
    parent_sub,
    content,
    left,
    right,
    redone: None,
    keep: false,
    linked: false,
    links: Set([]),
    deleted: content is (Deleted(_) | Garbage(_)),
  }
  if parent is Some(name) && parent_sub is Some(key) {
    self.touch_map(name, key)
  }
  if parent is Some(name) {
    let branch = self.branch(name)
    if parent_sub is None {
      branch.position_hint = None
    }
    if content is Format(_, _) {
      branch.formatted = true
    }
    let start = match left {
      Some(id) => self.items[id].right
      None => branch.first(parent_sub)
    }
    let conflict = match (left, right) {
      (None, Some(id)) => self.items[id].left is Some(_)
      _ => start != right || left is None
    }
    if conflict {
      let before : Set[Id] = Set([])
      let conflicting : Set[Id] = Set([])
      let mut cursor = start
      while cursor is Some(id) && cursor != right {
        let other = self.items[id]
        before.add(id)
        conflicting.add(id)
        if raw.origin == other.origin {
          if id.client < raw.id.client {
            left = Some(id)
            conflicting.clear()
          } else if raw.right_origin == other.right_origin {
            break
          }
        } else if other.origin is Some(origin) &&
          self.find(origin) is Some(origin_item) {
          if before.contains(origin_item.id) {
            if !conflicting.contains(origin_item.id) {
              left = Some(id)
              conflicting.clear()
            }
          } else {
            break
          }
        } else {
          break
        }
        cursor = other.right
      }
    }
    item.left = left
    item.right = match left {
      Some(id) => self.items[id].right
      None => branch.first(parent_sub)
    }
    if left is Some(id) {
      self.items[id].right = Some(item.id)
    } else {
      branch.set_first(parent_sub, item.id)
    }
    if item.right is Some(id) {
      self.items[id].left = Some(item.id)
    }
  }
  self.items[item.id] = item
  // GC/deleted blocks may arrive without an explicit deletion-set entry, or
  // be synthesized when their parent has already been collected. Remember
  // their deletion so peers that already know the clock receive it as well.
  if item.deleted {
    self.add_delete(
      item.id.client,
      item.id.clock,
      item.id.clock + item.content.length().reinterpret_as_uint(),
    )
  }
  if self.active_transaction is Some(transaction) {
    if parent is Some(name) && parent_sub is None {
      transaction.sequences.add(name)
    }
    transaction.added.push(
      (
        item.id.client,
        item.id.clock,
        item.id.clock + item.content.length().reinterpret_as_uint(),
      ),
    )
    if item.deleted {
      transaction.removed.push(
        (
          item.id.client,
          item.id.clock,
          item.id.clock + item.content.length().reinterpret_as_uint(),
        ),
      )
    }
  }
  match self.clients.get(item.id.client) {
    Some(entries) => {
      let mut index = entries.length()
      while index > 0 && entries[index - 1].id.clock > item.id.clock {
        index -= 1
      }
      entries.insert(index, item)
    }
    None => self.clients[item.id.client] = [item]
  }
  if item.content is Subdocument(child) {
    child.decoded_options = None
    child.owner = Some((self, item.id))
    if self.active_transaction is Some(txn) {
      txn.sub_changed = true
      txn.sub_added[child.identity] = child
      if child.loading {
        txn.sub_loaded[child.identity] = child
      }
    }
  }
  if parent is Some(name) && parent_sub is Some(key) {
    if item.right is None {
      self.branch(name).values[key] = item.id
      if item.left is Some(id) {
        let previous = self.items[id]
        if previous.linked {
          item.linked = true
          for link in previous.links {
            item.links.add(link)
          }
          previous.links.clear()
          previous.linked = false
        }
        self.mark_deleted(previous)
      }
    } else {
      self.mark_deleted(item)
    }
  }
  if !item.deleted {
    self.join_weak_links(item)
  }
  if !item.deleted && item.content is Shared(SharedWeak(source)) {
    self.materialize_weak(item.id, source)
  }
  if self.active_transaction is Some(txn) {
    for link in item.links {
      txn.weak_changes.add(Nested(link))
    }
  }
  if parent is Some(Nested(id)) && self.find(id).unwrap().deleted {
    self.mark_deleted(item)
  }
  if self.collect_deletes {
    self.inserted_items.push(item)
  }
  let mut prefix = self.state.get(item.id.client)
  while self.find({ client: item.id.client, clock: prefix, }) is Some(known) {
    prefix = known.id.clock + known.content.length().reinterpret_as_uint()
  }
  self.state.set_max(item.id.client, prefix)
  true
}

///|
fn Doc::settle(
  self : Doc,
  incoming~ : Array[(UInt64, UInt32, UInt32)] = [],
) -> Unit {
  for ;; {
    let mut progress = false
    let unresolved = []
    // Each block has explicit dependencies. A missing dependency in one
    // block must not stall independent later blocks from the same client.
    for raw in normalize_items(self.pending) {
      let before = self.items.length()
      if self.integrate(raw) {
        progress = true
      } else {
        unresolved.push(raw)
      }
      if self.items.length() != before {
        progress = true
      }
    }
    self.pending.clear()
    for raw in unresolved {
      self.pending.push(raw)
    }
    if !progress {
      break
    }
  }
  for link in self.pending_weak.to_array() {
    if self.find(link)
      is Some({ content: Shared(SharedWeak(source)), deleted: false, .. }) {
      self.materialize_weak(link, source)
    } else {
      self.pending_weak.remove(link)
    }
  }
  // Previously applied deletions do not need to be replayed against old
  // blocks. Only incoming ranges and retained deletions overlapping this
  // transaction's insertions can change state. Keep retained early deletions
  // in self.deletes so later dependency integration still observes them.
  let applicable = incoming.copy()
  if self.active_transaction is Some(transaction) {
    for (client, start, end) in transaction.added {
      guard self.deletes.get(client) is Some(ranges) else { continue }
      let first = for lo = 0, hi = ranges.length(); lo < hi; {
        let mid = lo + (hi - lo) / 2
        if ranges[mid].1 <= start {
          continue mid + 1, hi
        } else {
          continue lo, mid
        }
      } nobreak {
        lo
      }
      for i in first..= end {
          break
        }
        applicable.push((client, a.max(start), b.min(end)))
      }
    }
  }
  let selected = Snapshot(deletions=applicable).deletes
  // Preserve established client iteration order for transaction side effects.
  for client in self.deletes.keys().to_array() {
    guard selected.get(client) is Some(ranges) else { continue }
    for (start, end) in ranges {
      guard self.clients.get(client) is Some(blocks) else { continue }
      // Deletions are retained across updates, so scanning every block for every
      // old range makes long editing sessions quadratic per incoming packet.
      // Snapshot only overlapping blocks: splitting below changes the index.
      let first = for lo = 0, hi = blocks.length(); lo < hi; {
        let mid = lo + (hi - lo) / 2
        let item = blocks[mid]
        if item.id.clock + item.content.length().reinterpret_as_uint() <= start {
          continue mid + 1, hi
        } else {
          continue lo, mid
        }
      } nobreak {
        lo
      }
      let last = for lo = first, hi = blocks.length(); lo < hi; {
        let mid = lo + (hi - lo) / 2
        if blocks[mid].id.clock < end {
          continue mid + 1, hi
        } else {
          continue lo, mid
        }
      } nobreak {
        lo
      }
      let known = blocks[first:last].to_owned()
      for found in known {
        let item = if found.id.clock < start {
          self.split(found, (start - found.id.clock).reinterpret_as_int())
        } else {
          found
        }
        if item.id.clock + item.content.length().reinterpret_as_uint() > end {
          self.split(item, (end - item.id.clock).reinterpret_as_int()) |> ignore
        }
        self.mark_deleted(item)
      }
    }
  }
}

///|
fn Doc::add_delete(
  self : Doc,
  client : UInt64,
  start : UInt32,
  end : UInt32,
) -> Unit {
  if start == end {
    return
  }
  let ranges = self.deletes
    .get(client)
    .map(fn(ranges) { ranges[:].to_owned() })
    .unwrap_or([])
  ranges.push((start, end))
  ranges.sort_by(fn(a, b) { a.0.compare(b.0) })
  let merged : Array[(UInt32, UInt32)] = []
  for range in ranges {
    if merged.last() is Some(previous) && previous.1 >= range.0 {
      merged[merged.length() - 1] = (previous.0, previous.1.max(range.1))
    } else {
      merged.push(range)
    }
  }
  self.deletes[client] = merged
}

///|
pub struct Text {
  priv doc : Doc
  priv name : Parent
}

///|
pub fn Doc::get_or_insert_text(self : Doc, name : String) -> Text {
  let name : Parent = Root(name)
  self.register_kind(name, SharedText)
  { doc: self, name, }
}

///|
pub fn Text::get_string(self : Text) -> String {
  let out = StringBuilder()
  let mut cursor = self.doc.branch(self.name).head
  while cursor is Some(id) {
    let item = self.doc.items[id]
    if !item.deleted && item.content is Characters(s) {
      out.write_string(s)
    }
    cursor = item.right
  }
  out.to_string()
}

///|
pub fn Text::length(self : Text) -> Int32 {
  let mut length = 0
  let mut cursor = self.doc.branch(self.name).head
  while cursor is Some(id) {
    let item = self.doc.items[id]
    if !item.deleted {
      length += item.content.offset_length(self.doc)
    }
    cursor = item.right
  }
  length
}

///|
fn Text::position(self : Text, index : Int32) -> (Id?, Id?) {
  guard index >= 0 else { abort("negative text index") }
  let (start, previous, next) = self.position_start(index)
  let mut remaining = index - start
  let mut left = previous
  let mut cursor = next
  while cursor is Some(id) {
    let item = self.doc.items[id]
    if !item.deleted {
      if remaining == 0 {
        return (left, cursor)
      }
      let len = item.content.offset_length(self.doc)
      if remaining < len {
        let tail = self.doc.split(
          item,
          item.content.block_offset(remaining, self.doc),
        )
        return (Some(item.id), Some(tail.id))
      }
      remaining -= len
    }
    left = cursor
    cursor = item.right
  }
  guard remaining == 0 else { abort("text index out of bounds") }
  (left, None)
}

///|
pub fn Text::insert(self : Text, index : Int32, value : String) -> Unit {
  self.doc.with_mutation(fn() {
    let (left, right) = self.position(index)
    if value.is_empty() {
      return
    }
    let clock = self.doc.next_clock(self.doc.client)
    guard clock.to_uint64() + value.length().to_uint64() <= 0xffff_ffffUL else {
      abort("document clock exhausted")
    }
    let origin = left.map(fn(id) {
      let item = self.doc.items[id]
      (
        {
          client: id.client,
          clock: id.clock + item.content.length().reinterpret_as_uint() - 1U,
        } : Id)
    })
    let raw : RawItem = {
      id: { client: self.doc.client, clock, },
      origin,
      right_origin: right,
      parent: Some(self.name),
      parent_sub: None,
      content: Characters(value),
    }
    guard self.doc.integrate(raw) else {
      abort("local insert has missing dependency")
    }
    let branch = self.doc.branch(self.name)
    let item = self.doc.items[raw.id]
    if !branch.formatted && !item.deleted {
      branch.position_hint = Some((item, index))
    }
    self.doc.settle()
  })
}

///|
pub fn Text::remove_range(self : Text, index : Int32, length : Int32) -> Unit {
  self.doc.with_mutation(fn() {
    guard length >= 0 &&
      index >= 0 &&
      index <= self.length() &&
      length <= self.length() - index else {
      abort("text deletion out of bounds")
    }
    let pos = self.rich_position(index)
    pos.remove(length)
  })
}

///|
fn Branch::first(self : Branch, key : String?) -> Id? {
  match key {
    None => self.head
    Some(key) => self.heads.get(key)
  }
}

///|
fn Branch::set_first(self : Branch, key : String?, id : Id) -> Unit {
  match key {
    None => self.head = Some(id)
    Some(key) => self.heads[key] = id
  }
}

///|
fn Doc::mark_deleted(self : Doc, item : Item) -> Unit {
  let work = [item]
  while work.pop() is Some(item) {
    if item.deleted {
      continue
    }
    if item.parent is Some(parent) && item.parent_sub is Some(key) {
      self.touch_map(parent, key)
    }
    if item.parent is Some(parent) &&
      item.parent_sub is None &&
      self.active_transaction is Some(transaction) {
      transaction.sequences.add(parent)
    }
    if item.content is Subdocument(child) &&
      self.active_transaction is Some(txn) {
      if txn.sub_added.contains(child.identity) {
        txn.sub_added.remove(child.identity)
      } else {
        txn.sub_removed[child.identity] = child
      }
    }
    if item.content is Shared(SharedWeak(_)) {
      self.unlink_weak(item.id)
    }
    if self.active_transaction is Some(txn) {
      for link in item.links {
        txn.weak_changes.add(Nested(link))
      }
    }
    item.links.clear()
    if item.parent_sub is None && item.parent is Some(parent) {
      self.branch(parent).position_hint = None
    }
    item.deleted = true
    if self.active_transaction is Some(transaction) {
      transaction.removed.push(
        (
          item.id.client,
          item.id.clock,
          item.id.clock + item.content.length().reinterpret_as_uint(),
        ),
      )
    }
    if self.collect_deletes {
      self.deleted_items.push(item)
    }
    self.add_delete(
      item.id.client,
      item.id.clock,
      item.id.clock + item.content.length().reinterpret_as_uint(),
    )
    if item.content is Shared(_) &&
      self.roots.get(Nested(item.id)) is Some(branch) {
      // Only the current map value can be live. Restored values may retain
      // neighbors in an older parent, so a cached chain head is insufficient.
      for id in branch.values.values() {
        work.push(self.items[id])
      }
      let mut cursor = branch.head
      while cursor is Some(id) {
        let child = self.items[id]
        work.push(child)
        cursor = child.right
      }
    }
  }
}

///|
fn Content::visible_length(self : Content) -> Int32 {
  match self {
    Format(_, _) | Deleted(_) | Garbage(_) => 0
    _ => self.length()
  }
}

///|
fn Doc::next_clock(self : Doc, client : UInt64) -> UInt32 {
  match self.clients.get(client).bind(Array::last) {
    Some(item) => item.id.clock + item.content.length().reinterpret_as_uint()
    None => 0U
  }
}

///|
pub fn Doc::has_missing_updates(self : Doc) -> Bool {
  if !self.pending.is_empty() {
    return true
  }
  for client, _ in self.clients {
    if self.state.get(client) < self.next_clock(client) {
      return true
    }
  }
  for client, ranges in self.deletes {
    if ranges.last() is Some((_, end)) && end > self.next_clock(client) {
      return true
    }
  }
  false
}

///|
/// Whether remote transactions remove redundant rich-text format markers.
pub fn Doc::cleanup_formatting_enabled(self : Doc) -> Bool {
  self.cleanup_enabled
}