///|
// LoroDoc is the entry for document operations.
pub struct LoroDoc {
  mut oplog : @oplog.OpLog
  mut state : @state.DocState
  mut arena : @arena.SharedArena
  mut text_style : @types.StyleConfigMap
  mut peer : @types.PeerID
  mut counter : @types.Counter
  mut lamport : @types.Lamport
  mut next_sub_id : Int
  mut root_subs : Array[SubscriberEntry]
  mut container_subs : Map[@types.ContainerID, Array[SubscriberEntry]]
  mut local_update_subs : Array[LocalUpdateEntry]
  mut peer_id_subs : Array[PeerIdEntry]
  mut pre_commit_subs : Array[PreCommitEntry]
  mut next_commit_msg : String?
  mut next_commit_origin : String?
  mut txn_active : Bool
  mut txn_ops : Array[@op.Op]
  mut txn_deps : @types.Frontiers?
  mut txn_base_id : @types.ID?
  mut txn_base_lamport : @types.Lamport
}

///|
struct SubscriberEntry {
  id : Int
  callback : Subscriber
}

///|
struct LocalUpdateEntry {
  id : Int
  callback : LocalUpdateCallback
}

///|
struct PeerIdEntry {
  id : Int
  callback : PeerIdUpdateCallback
}

///|
struct PreCommitEntry {
  id : Int
  callback : PreCommitCallback
}

///|
pub fn LoroDoc::new() -> LoroDoc {
  LoroDoc::{
    oplog: @oplog.OpLog::new(),
    state: @state.DocState::new(),
    arena: @arena.SharedArena::new(),
    text_style: @types.StyleConfigMap::default_rich_text_config(),
    peer: 1,
    counter: 0,
    lamport: 0,
    next_sub_id: 1,
    root_subs: [],
    container_subs: Map::new(),
    local_update_subs: [],
    peer_id_subs: [],
    pre_commit_subs: [],
    next_commit_msg: None,
    next_commit_origin: None,
    txn_active: false,
    txn_ops: [],
    txn_deps: None,
    txn_base_id: None,
    txn_base_lamport: 0,
  }
}

///|
pub fn LoroDoc::set_peer_id(self : LoroDoc, peer : @types.PeerID) -> Unit {
  self.peer = peer
  self.emit_peer_id_change(peer)
}

///|
fn LoroDoc::alloc_sub_id(self : LoroDoc) -> Int {
  let id = self.next_sub_id
  self.next_sub_id = id + 1
  id
}

///|
pub fn LoroDoc::subscribe(
  self : LoroDoc,
  container_id : @types.ContainerID,
  callback : Subscriber,
) -> Subscription {
  let id = self.alloc_sub_id()
  let entry = SubscriberEntry::{ id, callback }
  let list = match self.container_subs.get(container_id) {
    Some(value) => value
    None => []
  }
  list.push(entry)
  self.container_subs[container_id] = list
  Subscription::{ doc: self, id }
}

///|
pub fn LoroDoc::subscribe_root(
  self : LoroDoc,
  callback : Subscriber,
) -> Subscription {
  let id = self.alloc_sub_id()
  self.root_subs.push(SubscriberEntry::{ id, callback })
  Subscription::{ doc: self, id }
}

///|
pub fn LoroDoc::subscribe_local_update(
  self : LoroDoc,
  callback : LocalUpdateCallback,
) -> Subscription {
  let id = self.alloc_sub_id()
  self.local_update_subs.push(LocalUpdateEntry::{ id, callback })
  Subscription::{ doc: self, id }
}

///|
pub fn LoroDoc::subscribe_peer_id_change(
  self : LoroDoc,
  callback : PeerIdUpdateCallback,
) -> Subscription {
  let id = self.alloc_sub_id()
  self.peer_id_subs.push(PeerIdEntry::{ id, callback })
  Subscription::{ doc: self, id }
}

///|
pub fn LoroDoc::subscribe_pre_commit(
  self : LoroDoc,
  callback : PreCommitCallback,
) -> Subscription {
  let id = self.alloc_sub_id()
  self.pre_commit_subs.push(PreCommitEntry::{ id, callback })
  Subscription::{ doc: self, id }
}

///|
pub fn LoroDoc::set_next_commit_message(
  self : LoroDoc,
  message : String?,
) -> Unit {
  self.next_commit_msg = message
}

///|
pub fn LoroDoc::set_next_commit_origin(
  self : LoroDoc,
  origin : String?,
) -> Unit {
  self.next_commit_origin = origin
}

///|
fn LoroDoc::unsubscribe(self : LoroDoc, id : Int) -> Unit {
  self.root_subs = filter_subscribers_by_id(self.root_subs, id)
  self.local_update_subs = filter_update_subs_by_id(self.local_update_subs, id)
  self.peer_id_subs = filter_peer_subs_by_id(self.peer_id_subs, id)
  self.pre_commit_subs = filter_pre_commit_subs_by_id(self.pre_commit_subs, id)
  let next_map : Map[@types.ContainerID, Array[SubscriberEntry]] = {}
  for container_id, entries in self.container_subs {
    let filtered = filter_subscribers_by_id(entries, id)
    if filtered.length() > 0 {
      next_map[container_id] = filtered
    }
  }
  self.container_subs = next_map
}

///|
fn filter_subscribers_by_id(
  entries : Array[SubscriberEntry],
  id : Int,
) -> Array[SubscriberEntry] {
  let out : Array[SubscriberEntry] = []
  for entry in entries {
    if entry.id != id {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_update_subs_by_id(
  entries : Array[LocalUpdateEntry],
  id : Int,
) -> Array[LocalUpdateEntry] {
  let out : Array[LocalUpdateEntry] = []
  for entry in entries {
    if entry.id != id {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_peer_subs_by_id(
  entries : Array[PeerIdEntry],
  id : Int,
) -> Array[PeerIdEntry] {
  let out : Array[PeerIdEntry] = []
  for entry in entries {
    if entry.id != id {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_pre_commit_subs_by_id(
  entries : Array[PreCommitEntry],
  id : Int,
) -> Array[PreCommitEntry] {
  let out : Array[PreCommitEntry] = []
  for entry in entries {
    if entry.id != id {
      out.push(entry)
    }
  }
  out
}

///|
pub fn LoroDoc::begin_transaction(
  self : LoroDoc,
) -> Result[Unit, @types.LoroError] {
  if self.txn_active {
    return Err(@types.LoroError::InternalError("transaction already active"))
  }
  self.txn_active = true
  self.txn_ops = []
  self.txn_deps = Some(self.oplog.frontiers().clone())
  self.txn_base_id = None
  self.txn_base_lamport = 0
  Ok(())
}

///|
pub fn LoroDoc::commit_transaction(
  self : LoroDoc,
) -> Result[Unit, @types.LoroError] {
  if !self.txn_active {
    return Err(@types.LoroError::InternalError("transaction not active"))
  }
  if self.txn_ops.length() == 0 {
    self.reset_transaction()
    return Ok(())
  }
  match (self.txn_base_id, self.txn_deps) {
    (Some(id), Some(deps)) => {
      let meta = self.build_commit_meta()
      let change = @oplog.Change::new_with_meta(
        self.txn_ops,
        deps,
        id,
        self.txn_base_lamport,
        meta.timestamp(),
        meta.message(),
        meta.origin(),
      )
      self.oplog.insert_new_change(change, true)
      match self.oplog.get_change(id) {
        Some(saved) => self.emit_change(saved, true)
        None => ()
      }
      self.reset_transaction()
      Ok(())
    }
    _ => Err(@types.LoroError::InternalError("invalid transaction state"))
  }
}

///|
pub fn LoroDoc::in_transaction(self : LoroDoc) -> Bool {
  self.txn_active
}

///|
fn LoroDoc::reset_transaction(self : LoroDoc) -> Unit {
  self.txn_active = false
  self.txn_ops = []
  self.txn_deps = None
  self.txn_base_id = None
  self.txn_base_lamport = 0
}

///|
fn LoroDoc::emit_peer_id_change(self : LoroDoc, peer : @types.PeerID) -> Unit {
  if self.peer_id_subs.length() == 0 {
    return
  }
  self.peer_id_subs = filter_peer_subs(self.peer_id_subs, peer)
}

///|
fn LoroDoc::build_commit_meta(self : LoroDoc) -> CommitMeta {
  let meta = CommitMeta::new(@env.now().reinterpret_as_int64())
  match self.next_commit_msg {
    Some(msg) => {
      meta.set_message(Some(msg))
      self.next_commit_msg = None
    }
    None => ()
  }
  match self.next_commit_origin {
    Some(origin) => {
      meta.set_origin(Some(origin))
      self.next_commit_origin = None
    }
    None => ()
  }
  if self.pre_commit_subs.length() > 0 {
    self.pre_commit_subs = filter_pre_commit_subs(self.pre_commit_subs, meta)
  }
  meta
}

///|
fn LoroDoc::emit_change(
  self : LoroDoc,
  change : @oplog.Change,
  is_local : Bool,
) -> Unit {
  let events = self.events_for_change(change)
  if events.length() > 0 {
    let batch = EventBatch::{ events, is_local, peer: change.peer() }
    self.root_subs = filter_subscribers(self.root_subs, batch)
    for event in events {
      let container_id = event.container()
      match self.container_subs.get(container_id) {
        Some(entries) => {
          let container_batch = EventBatch::{
            events: [event],
            is_local,
            peer: change.peer(),
          }
          let kept = filter_subscribers(entries, container_batch)
          if kept.length() > 0 {
            self.container_subs[container_id] = kept
          } else {
            self.container_subs.remove(container_id)
          }
        }
        None => ()
      }
    }
  }
  if is_local {
    self.emit_local_update(change)
  }
}

///|
fn LoroDoc::emit_snapshot(self : LoroDoc) -> Unit {
  let events : Array[ContainerEvent] = []
  let items = self.state.container_items()
  for entry in items {
    let (id, _state) = entry
    let diff = self.container_diff(id)
    events.push(ContainerEvent::{ container: id, diff })
  }
  if events.length() == 0 {
    return
  }
  let batch = EventBatch::{ events, is_local: false, peer: self.peer }
  self.root_subs = filter_subscribers(self.root_subs, batch)
  for event in events {
    let container_id = event.container()
    match self.container_subs.get(container_id) {
      Some(entries) => {
        let container_batch = EventBatch::{
          events: [event],
          is_local: false,
          peer: self.peer,
        }
        let kept = filter_subscribers(entries, container_batch)
        if kept.length() > 0 {
          self.container_subs[container_id] = kept
        } else {
          self.container_subs.remove(container_id)
        }
      }
      None => ()
    }
  }
}

///|
fn LoroDoc::emit_local_update(self : LoroDoc, change : @oplog.Change) -> Unit {
  if self.local_update_subs.length() == 0 {
    return
  }
  let bytes = match @encoding.encode_changes(self.arena, [change]) {
    Ok(value) => value
    Err(_) => return
  }
  self.local_update_subs = filter_local_updates(self.local_update_subs, bytes)
}

///|
fn LoroDoc::events_for_change(
  self : LoroDoc,
  change : @oplog.Change,
) -> Array[ContainerEvent] {
  let events : Array[ContainerEvent] = []
  let seen : Map[@types.ContainerID, Bool] = {}
  for op in change.ops() {
    let container_id = op.container()
    if seen.get(container_id) is Some(true) {
      continue
    }
    seen[container_id] = true
    let diff = self.container_diff(container_id)
    events.push(ContainerEvent::{ container: container_id, diff })
  }
  events
}

///|
fn LoroDoc::container_diff(
  self : LoroDoc,
  container_id : @types.ContainerID,
) -> Array[@types.LoroValue] {
  let container_type = container_id.container_type()
  if container_type == @types.ContainerType::Map {
    match self.map_diff(@container.MapHandler::new(container_id)) {
      Ok(value) => value
      Err(_) => []
    }
  } else if container_type == @types.ContainerType::List {
    match self.list_diff(@container.ListHandler::new(container_id)) {
      Ok(value) => value
      Err(_) => []
    }
  } else if container_type == @types.ContainerType::MovableList {
    match
      self.movable_list_diff(@container.MovableListHandler::new(container_id)) {
      Ok(value) => value
      Err(_) => []
    }
  } else if container_type == @types.ContainerType::Text ||
    container_type == @types.ContainerType::RichText {
    match self.text_diff(@container.TextHandler::new(container_id)) {
      Ok(value) => value
      Err(_) => []
    }
  } else if container_type == @types.ContainerType::Tree {
    let value = self.state.deep_value_for_container(container_id)
    match self.list_diff_from_value(value) {
      Ok(list) => list
      Err(_) => []
    }
  } else if container_type == @types.ContainerType::Counter {
    match self.state.counter_value(container_id) {
      Ok(value) => [@types.LoroValue::F64(value)]
      Err(_) => []
    }
  } else {
    []
  }
}

///|
fn filter_subscribers(
  entries : Array[SubscriberEntry],
  batch : EventBatch,
) -> Array[SubscriberEntry] {
  let out : Array[SubscriberEntry] = []
  for entry in entries {
    if (entry.callback)(batch) {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_local_updates(
  entries : Array[LocalUpdateEntry],
  bytes : Bytes,
) -> Array[LocalUpdateEntry] {
  let out : Array[LocalUpdateEntry] = []
  for entry in entries {
    if (entry.callback)(bytes) {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_peer_subs(
  entries : Array[PeerIdEntry],
  peer : @types.PeerID,
) -> Array[PeerIdEntry] {
  let out : Array[PeerIdEntry] = []
  for entry in entries {
    if (entry.callback)(peer) {
      out.push(entry)
    }
  }
  out
}

///|
fn filter_pre_commit_subs(
  entries : Array[PreCommitEntry],
  meta : CommitMeta,
) -> Array[PreCommitEntry] {
  let out : Array[PreCommitEntry] = []
  for entry in entries {
    if (entry.callback)(meta) {
      out.push(entry)
    }
  }
  out
}

///|
pub fn LoroDoc::container_map(
  _self : LoroDoc,
  id : @types.ContainerID,
) -> @container.MapHandler {
  @container.MapHandler::new(id)
}

///|
pub fn LoroDoc::container_list(
  _self : LoroDoc,
  id : @types.ContainerID,
) -> @container.ListHandler {
  @container.ListHandler::new(id)
}

///|
pub fn LoroDoc::container_text(
  _self : LoroDoc,
  id : @types.ContainerID,
) -> @container.TextHandler {
  @container.TextHandler::new(id)
}

///|
pub fn LoroDoc::container_tree(
  _self : LoroDoc,
  id : @types.ContainerID,
) -> @container.TreeHandler {
  @container.TreeHandler::new(id)
}

///|
pub fn LoroDoc::container_movable_list(
  _self : LoroDoc,
  id : @types.ContainerID,
) -> @container.MovableListHandler {
  @container.MovableListHandler::new(id)
}

///|
pub fn LoroDoc::config_text_style(
  self : LoroDoc,
  text_style : @types.StyleConfigMap,
) -> Unit {
  self.text_style = text_style
}

///|
pub fn LoroDoc::config_default_text_style(
  self : LoroDoc,
  text_style : @types.StyleConfig?,
) -> Unit {
  self.text_style.set_default(text_style)
}

///|
pub fn alloc_id_and_lamport(
  doc : LoroDoc,
  len : Int,
) -> (@types.ID, @types.Lamport) {
  let id = @types.ID::new(doc.peer, doc.counter)
  let lamport = doc.lamport
  doc.counter = doc.counter + len
  doc.lamport = doc.lamport + len.reinterpret_as_uint()
  (id, lamport)
}

///|
pub fn LoroDoc::get_list(
  _self : LoroDoc,
  name : String,
) -> @container.ListHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::List)
  @container.ListHandler::new(id)
}

///|
pub fn LoroDoc::get_movable_list(
  _self : LoroDoc,
  name : String,
) -> @container.MovableListHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::MovableList)
  @container.MovableListHandler::new(id)
}

///|
pub fn LoroDoc::get_tree(
  _self : LoroDoc,
  name : String,
) -> @container.TreeHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::Tree)
  @container.TreeHandler::new(id)
}

///|
pub fn LoroDoc::get_map(
  _self : LoroDoc,
  name : String,
) -> @container.MapHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::Map)
  @container.MapHandler::new(id)
}

///|
pub fn LoroDoc::get_counter(
  _self : LoroDoc,
  name : String,
) -> @container.CounterHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::Counter)
  @container.CounterHandler::new(id)
}

///|
pub fn LoroDoc::map_id(
  _self : LoroDoc,
  handler : @container.MapHandler,
) -> @types.ContainerID {
  handler.id()
}

///|
pub fn LoroDoc::get_text(
  _self : LoroDoc,
  name : String,
) -> @container.TextHandler {
  let id = @types.ContainerID::root(name, @types.ContainerType::Text)
  @container.TextHandler::new(id)
}

///|
fn LoroDoc::apply_local_content(
  self : LoroDoc,
  container_id : @types.ContainerID,
  content : @op.RawOpContent,
) -> Result[Unit, @types.LoroError] {
  let len = content.content_len()
  let (id, lamport) = alloc_id_and_lamport(self, len)
  self.apply_local_content_with_id(id, lamport, container_id, content)
}

///|
pub fn LoroDoc::apply_local_content_with_id(
  self : LoroDoc,
  id : @types.ID,
  lamport : @types.Lamport,
  container_id : @types.ContainerID,
  content : @op.RawOpContent,
) -> Result[Unit, @types.LoroError] {
  let raw_op = @op.RawOp::new(id, lamport, container_id, content)
  let op = self.arena.to_persisted_op(raw_op)
  if self.txn_active {
    match self.txn_base_id {
      None => {
        self.txn_base_id = Some(id)
        self.txn_base_lamport = lamport
      }
      Some(_) => ()
    }
    self.txn_ops.push(op)
    match self.state.apply_raw_op(self.arena, raw_op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  } else {
    let deps = self.oplog.frontiers().clone()
    let meta = self.build_commit_meta()
    let change = @oplog.Change::new_with_meta(
      [op],
      deps,
      id,
      lamport,
      meta.timestamp(),
      meta.message(),
      meta.origin(),
    )
    self.oplog.insert_new_change(change, true)
    match self.state.apply_raw_op(self.arena, raw_op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
    match self.oplog.get_change(id) {
      Some(saved) => self.emit_change(saved, true)
      None => ()
    }
  }
  Ok(())
}

///|
fn LoroDoc::alloc_container_id(
  self : LoroDoc,
  container_type : @types.ContainerType,
) -> (@types.ContainerID, @types.ID, @types.Lamport) {
  let (id, lamport) = alloc_id_and_lamport(self, 1)
  let container_id = @types.ContainerID::normal(
    id.peer,
    id.counter,
    container_type,
  )
  (container_id, id, lamport)
}

///|
fn LoroDoc::apply_list_op_to(
  self : LoroDoc,
  container_id : @types.ContainerID,
  op : @op.ListOp,
) -> Result[Unit, @types.LoroError] {
  self.apply_local_content(container_id, @op.RawOpContent::List(op))
}

///|
pub fn LoroDoc::apply_list_op(
  self : LoroDoc,
  handler : @container.ListHandler,
  op : @op.ListOp,
) -> Result[Unit, @types.LoroError] {
  self.apply_list_op_to(handler.id(), op)
}

///|
pub fn LoroDoc::apply_map_set(
  self : LoroDoc,
  handler : @container.MapHandler,
  key : String,
  value : @types.LoroValue?,
) -> Result[Unit, @types.LoroError] {
  let map_set = @op.MapSet::new(key, value)
  self.apply_local_content(handler.id(), @op.RawOpContent::Map(map_set))
}

///|
pub fn LoroDoc::apply_text_op(
  self : LoroDoc,
  handler : @container.TextHandler,
  op : @op.ListOp,
) -> Result[Unit, @types.LoroError] {
  self.apply_list_op_to(handler.id(), op)
}

///|
pub fn LoroDoc::apply_tree_op(
  self : LoroDoc,
  handler : @container.TreeHandler,
  op : @op.TreeOp,
) -> Result[Unit, @types.LoroError] {
  self.apply_local_content(handler.id(), @op.RawOpContent::Tree(op))
}

///|
pub fn LoroDoc::list_insert(
  self : LoroDoc,
  handler : @container.ListHandler,
  pos : Int,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let slice = @op.ListSlice::RawData([value])
  let op = @op.ListOp::Insert(slice~, pos~)
  self.apply_list_op(handler, op)
}

///|
pub fn LoroDoc::list_insert_container(
  self : LoroDoc,
  handler : @container.ListHandler,
  pos : Int,
  container_type : @types.ContainerType,
) -> Result[@types.ContainerID, @types.LoroError] {
  let (container_id, id, lamport) = self.alloc_container_id(container_type)
  let slice = @op.ListSlice::RawData([@types.LoroValue::Container(container_id)])
  let op = @op.ListOp::Insert(slice~, pos~)
  match
    self.apply_local_content_with_id(
      id,
      lamport,
      handler.id(),
      @op.RawOpContent::List(op),
    ) {
    Ok(_) => Ok(container_id)
    Err(err) => Err(err)
  }
}

///|
pub fn LoroDoc::list_push(
  self : LoroDoc,
  handler : @container.ListHandler,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let len = match self.state.list_len(handler.id()) {
    Ok(value) => value
    Err(@types.LoroError::InvalidContainer(_)) => 0
    Err(err) => return Err(err)
  }
  self.list_insert(handler, len, value)
}

///|
pub fn LoroDoc::list_push_container(
  self : LoroDoc,
  handler : @container.ListHandler,
  container_type : @types.ContainerType,
) -> Result[@types.ContainerID, @types.LoroError] {
  let len = match self.state.list_len(handler.id()) {
    Ok(value) => value
    Err(@types.LoroError::InvalidContainer(_)) => 0
    Err(err) => return Err(err)
  }
  self.list_insert_container(handler, len, container_type)
}

///|
pub fn LoroDoc::list_get_or_create_container(
  self : LoroDoc,
  handler : @container.ListHandler,
  pos : Int,
  container_type : @types.ContainerType,
) -> Result[@types.ContainerID, @types.LoroError] {
  match self.state.list_value_at(handler.id(), pos) {
    Ok(Some(@types.LoroValue::Container(id))) =>
      if id.container_type() != container_type {
        Err(@types.LoroError::InternalError("container type mismatch"))
      } else {
        Ok(id)
      }
    Ok(Some(_)) =>
      Err(@types.LoroError::InternalError("list element is not container"))
    Ok(None) => self.list_insert_container(handler, pos, container_type)
    Err(@types.LoroError::InvalidContainer(_)) =>
      self.list_insert_container(handler, pos, container_type)
    Err(err) => Err(err)
  }
}

///|
fn delete_spans_from_ids(
  ids : Array[@types.IdFull],
  start_pos : Int,
  list_pos_at : (Int) -> Result[Int, @types.LoroError],
) -> Result[Array[@op.DeleteSpanWithId], @types.LoroError] {
  let spans : Array[@op.DeleteSpanWithId] = []
  if ids.length() == 0 {
    return Ok(spans)
  }
  let mut current_peer = ids[0].peer
  let mut start_counter = ids[0].counter
  let mut last_counter = ids[0].counter
  let mut current_pos = start_pos
  for i = 1; i < ids.length(); i = i + 1 {
    let id = ids[i]
    if id.peer == current_peer && id.counter == last_counter + 1 {
      last_counter = id.counter
      continue
    }
    let span_len = last_counter - start_counter + 1
    let list_pos = match list_pos_at(current_pos) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    spans.push(
      @op.DeleteSpanWithId::new(
        @types.ID::new(current_peer, start_counter),
        list_pos,
        span_len,
      ),
    )
    current_peer = id.peer
    start_counter = id.counter
    last_counter = id.counter
    current_pos = start_pos + i
  }
  let span_len = last_counter - start_counter + 1
  let list_pos = match list_pos_at(current_pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  spans.push(
    @op.DeleteSpanWithId::new(
      @types.ID::new(current_peer, start_counter),
      list_pos,
      span_len,
    ),
  )
  Ok(spans)
}

///|
pub fn LoroDoc::list_delete(
  self : LoroDoc,
  handler : @container.ListHandler,
  pos : Int,
  len : Int,
) -> Result[Unit, @types.LoroError] {
  if len <= 0 {
    return Err(@types.LoroError::InternalError("len must be > 0"))
  }
  let end = pos + len
  let ids = match self.state.list_ids_in_range(handler.id(), pos, end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let spans = match delete_spans_from_ids(ids, pos, p => Ok(p)) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if spans.length() == 0 {
    return Ok(())
  }
  let need_txn = !self.txn_active && spans.length() > 1
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  for span in spans {
    let op = @op.ListOp::Delete(span)
    match self.apply_list_op(handler, op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::list_set(
  self : LoroDoc,
  handler : @container.ListHandler,
  pos : Int,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let id_full = match self.state.list_id_at(handler.id(), pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let need_txn = !self.txn_active
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  let del = @op.DeleteSpanWithId::new(id_full.to_id(), pos, 1)
  let del_op = @op.ListOp::Delete(del)
  match self.apply_list_op(handler, del_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let slice = @op.ListSlice::RawData([value])
  let ins_op = @op.ListOp::Insert(slice~, pos~)
  match self.apply_list_op(handler, ins_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::movable_list_insert(
  self : LoroDoc,
  handler : @container.MovableListHandler,
  pos : Int,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let slice = @op.ListSlice::RawData([value])
  let op = @op.ListOp::Insert(slice~, pos~)
  self.apply_list_op_to(handler.id(), op)
}

///|
pub fn LoroDoc::movable_list_push(
  self : LoroDoc,
  handler : @container.MovableListHandler,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let len = match self.state.movable_list_len(handler.id()) {
    Ok(value) => value
    Err(@types.LoroError::InvalidContainer(_)) => 0
    Err(err) => return Err(err)
  }
  self.movable_list_insert(handler, len, value)
}

///|
pub fn LoroDoc::movable_list_delete(
  self : LoroDoc,
  handler : @container.MovableListHandler,
  pos : Int,
  len : Int,
) -> Result[Unit, @types.LoroError] {
  if len <= 0 {
    return Err(@types.LoroError::InternalError("len must be > 0"))
  }
  let end = pos + len
  let ids = match self.state.movable_list_ids_in_range(handler.id(), pos, end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let spans = match delete_spans_from_ids(ids, pos, p => Ok(p)) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if spans.length() == 0 {
    return Ok(())
  }
  let need_txn = !self.txn_active && spans.length() > 1
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  for span in spans {
    let op = @op.ListOp::Delete(span)
    match self.apply_list_op_to(handler.id(), op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::movable_list_move(
  self : LoroDoc,
  handler : @container.MovableListHandler,
  from : Int,
  to : Int,
) -> Result[Unit, @types.LoroError] {
  if from == to {
    return Ok(())
  }
  let elem_id = match self.state.movable_list_elem_id_at(handler.id(), from) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let op = @op.ListOp::Move(from~, to~, elem_id~)
  self.apply_list_op_to(handler.id(), op)
}

///|
pub fn LoroDoc::movable_list_set(
  self : LoroDoc,
  handler : @container.MovableListHandler,
  pos : Int,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let elem_id = match self.state.movable_list_elem_id_at(handler.id(), pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let op = @op.ListOp::Set(elem_id~, value~)
  self.apply_list_op_to(handler.id(), op)
}

///|
fn LoroDoc::tree_children(
  self : LoroDoc,
  handler : @container.TreeHandler,
  parent : @types.TreeParentId,
) -> Result[Array[@types.TreeID], @types.LoroError] {
  self.state.tree_children(handler.id(), parent)
}

///|
fn LoroDoc::tree_position_for_index(
  self : LoroDoc,
  handler : @container.TreeHandler,
  parent : @types.TreeParentId,
  index : Int,
) -> Result[@types.FractionalIndex, @types.LoroError] {
  let children = match self.tree_children(handler, parent) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if index < 0 || index > children.length() {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  let lower = if index > 0 {
    let lower_id = children[index - 1]
    let node = match self.state.tree_node(handler.id(), lower_id) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    Some(node.position)
  } else {
    None
  }
  let upper = if index < children.length() {
    let upper_id = children[index]
    let node = match self.state.tree_node(handler.id(), upper_id) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    Some(node.position)
  } else {
    None
  }
  match @types.FractionalIndex::new(lower, upper) {
    Some(pos) => Ok(pos)
    None => Err(@types.LoroError::InternalError("cannot generate position"))
  }
}

///|
fn LoroDoc::tree_child_index(
  self : LoroDoc,
  handler : @container.TreeHandler,
  parent : @types.TreeParentId,
  target : @types.TreeID,
) -> Result[Int, @types.LoroError] {
  let children = match self.tree_children(handler, parent) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  for i = 0; i < children.length(); i = i + 1 {
    if children[i] == target {
      return Ok(i)
    }
  }
  Err(@types.LoroError::TreeNodeNotFound(target))
}

///|
fn LoroDoc::ensure_tree_container(
  self : LoroDoc,
  handler : @container.TreeHandler,
) -> Unit {
  ignore(self.state.get_or_create(handler.id(), @types.ContainerType::Tree))
}

///|
pub fn LoroDoc::tree_create(
  self : LoroDoc,
  handler : @container.TreeHandler,
  parent : @types.TreeID?,
) -> Result[@types.TreeID, @types.LoroError] {
  self.ensure_tree_container(handler)
  let parent_id = @types.TreeParentId::from_option(parent)
  let children = match self.tree_children(handler, parent_id) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let pos = match
    self.tree_position_for_index(handler, parent_id, children.length()) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let (id, lamport) = alloc_id_and_lamport(self, 1)
  let target = @types.TreeID::from_id(id)
  let op = @op.TreeOp::Create(target~, parent=parent_id, position=pos)
  let raw_op = @op.RawOp::new(
    id,
    lamport,
    handler.id(),
    @op.RawOpContent::Tree(op),
  )
  let persisted = self.arena.to_persisted_op(raw_op)
  let deps = self.oplog.frontiers().clone()
  let meta = self.build_commit_meta()
  let change = @oplog.Change::new_with_meta(
    [persisted],
    deps,
    id,
    lamport,
    meta.timestamp(),
    meta.message(),
    meta.origin(),
  )
  self.oplog.insert_new_change(change, true)
  match self.state.apply_raw_op(self.arena, raw_op) {
    Ok(_) => Ok(target)
    Err(err) => Err(err)
  }
}

///|
pub fn LoroDoc::tree_create_at(
  self : LoroDoc,
  handler : @container.TreeHandler,
  parent : @types.TreeID?,
  index : Int,
) -> Result[@types.TreeID, @types.LoroError] {
  self.ensure_tree_container(handler)
  let parent_id = @types.TreeParentId::from_option(parent)
  let pos = match self.tree_position_for_index(handler, parent_id, index) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let (id, lamport) = alloc_id_and_lamport(self, 1)
  let target = @types.TreeID::from_id(id)
  let op = @op.TreeOp::Create(target~, parent=parent_id, position=pos)
  let raw_op = @op.RawOp::new(
    id,
    lamport,
    handler.id(),
    @op.RawOpContent::Tree(op),
  )
  let persisted = self.arena.to_persisted_op(raw_op)
  let deps = self.oplog.frontiers().clone()
  let meta = self.build_commit_meta()
  let change = @oplog.Change::new_with_meta(
    [persisted],
    deps,
    id,
    lamport,
    meta.timestamp(),
    meta.message(),
    meta.origin(),
  )
  self.oplog.insert_new_change(change, true)
  match self.state.apply_raw_op(self.arena, raw_op) {
    Ok(_) => Ok(target)
    Err(err) => Err(err)
  }
}

///|
pub fn LoroDoc::tree_move(
  self : LoroDoc,
  handler : @container.TreeHandler,
  target : @types.TreeID,
  parent : @types.TreeID?,
) -> Result[Unit, @types.LoroError] {
  let parent_id = @types.TreeParentId::from_option(parent)
  let children = match self.tree_children(handler, parent_id) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let pos = match
    self.tree_position_for_index(handler, parent_id, children.length()) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let op = @op.TreeOp::Move(target~, parent=parent_id, position=pos)
  self.apply_tree_op(handler, op)
}

///|
pub fn LoroDoc::tree_move_to(
  self : LoroDoc,
  handler : @container.TreeHandler,
  target : @types.TreeID,
  parent : @types.TreeID?,
  index : Int,
) -> Result[Unit, @types.LoroError] {
  let parent_id = @types.TreeParentId::from_option(parent)
  let pos = match self.tree_position_for_index(handler, parent_id, index) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let op = @op.TreeOp::Move(target~, parent=parent_id, position=pos)
  self.apply_tree_op(handler, op)
}

///|
pub fn LoroDoc::tree_move_before(
  self : LoroDoc,
  handler : @container.TreeHandler,
  target : @types.TreeID,
  before : @types.TreeID,
) -> Result[Unit, @types.LoroError] {
  let node = match self.state.tree_node(handler.id(), before) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let parent = node.parent
  let idx = match self.tree_child_index(handler, parent, before) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.tree_move_to(handler, target, parent.to_option(), idx)
}

///|
pub fn LoroDoc::tree_move_after(
  self : LoroDoc,
  handler : @container.TreeHandler,
  target : @types.TreeID,
  after : @types.TreeID,
) -> Result[Unit, @types.LoroError] {
  let node = match self.state.tree_node(handler.id(), after) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let parent = node.parent
  let idx = match self.tree_child_index(handler, parent, after) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.tree_move_to(handler, target, parent.to_option(), idx + 1)
}

///|
pub fn LoroDoc::tree_delete(
  self : LoroDoc,
  handler : @container.TreeHandler,
  target : @types.TreeID,
) -> Result[Unit, @types.LoroError] {
  let op = @op.TreeOp::Delete(target~)
  self.apply_tree_op(handler, op)
}

///|
pub fn LoroDoc::tree_get_meta(
  _self : LoroDoc,
  target : @types.TreeID,
) -> @container.MapHandler {
  let meta_id = target.meta_container()
  @container.MapHandler::new(meta_id)
}

///|
fn LoroDoc::tree_node_value(
  self : LoroDoc,
  handler : @container.TreeHandler,
  node_id : @types.TreeID,
  with_meta : Bool,
) -> Result[@types.LoroValue, @types.LoroError] {
  let children = match
    self.tree_children(handler, @types.TreeParentId::Node(node_id)) {
    Ok(value) => value
    Err(_) => []
  }
  let child_values : Array[@types.LoroValue] = []
  for child in children {
    match self.tree_node_value(handler, child, with_meta) {
      Ok(value) => child_values.push(value)
      Err(err) => return Err(err)
    }
  }
  let map : Map[String, @types.LoroValue] = {}
  map["id"] = @types.LoroValue::String("\{node_id.peer}:\{node_id.counter}")
  map["children"] = @types.LoroValue::List(child_values)
  if with_meta {
    let meta_id = node_id.meta_container()
    map["meta"] = self.state.deep_value_for_container(meta_id)
  }
  Ok(@types.LoroValue::Map(map))
}

///|
pub fn LoroDoc::tree_get_value(
  self : LoroDoc,
  handler : @container.TreeHandler,
) -> Result[@types.LoroValue, @types.LoroError] {
  let roots = match self.tree_children(handler, @types.TreeParentId::Root) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let values : Array[@types.LoroValue] = []
  for root in roots {
    match self.tree_node_value(handler, root, false) {
      Ok(value) => values.push(value)
      Err(err) => return Err(err)
    }
  }
  Ok(@types.LoroValue::List(values))
}

///|
pub fn LoroDoc::tree_get_value_with_meta(
  self : LoroDoc,
  handler : @container.TreeHandler,
) -> Result[@types.LoroValue, @types.LoroError] {
  let roots = match self.tree_children(handler, @types.TreeParentId::Root) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let values : Array[@types.LoroValue] = []
  for root in roots {
    match self.tree_node_value(handler, root, true) {
      Ok(value) => values.push(value)
      Err(err) => return Err(err)
    }
  }
  Ok(@types.LoroValue::List(values))
}

///|
pub fn LoroDoc::map_set(
  self : LoroDoc,
  handler : @container.MapHandler,
  key : String,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  self.apply_map_set(handler, key, Some(value))
}

///|
pub fn LoroDoc::map_insert_container(
  self : LoroDoc,
  handler : @container.MapHandler,
  key : String,
  container_type : @types.ContainerType,
) -> Result[@types.ContainerID, @types.LoroError] {
  let (container_id, id, lamport) = self.alloc_container_id(container_type)
  let map_set = @op.MapSet::new(
    key,
    Some(@types.LoroValue::Container(container_id)),
  )
  match
    self.apply_local_content_with_id(
      id,
      lamport,
      handler.id(),
      @op.RawOpContent::Map(map_set),
    ) {
    Ok(_) => Ok(container_id)
    Err(err) => Err(err)
  }
}

///|
pub fn LoroDoc::map_get_or_create_container(
  self : LoroDoc,
  handler : @container.MapHandler,
  key : String,
  container_type : @types.ContainerType,
) -> Result[@types.ContainerID, @types.LoroError] {
  let existing = match self.state.map_get(handler.id(), key) {
    Ok(value) => value
    Err(@types.LoroError::InvalidContainer(_)) => None
    Err(err) => return Err(err)
  }
  match existing {
    Some(@types.LoroValue::Container(id)) =>
      if id.container_type() != container_type {
        Err(@types.LoroError::InternalError("container type mismatch"))
      } else {
        Ok(id)
      }
    _ => self.map_insert_container(handler, key, container_type)
  }
}

///|
pub fn LoroDoc::map_delete(
  self : LoroDoc,
  handler : @container.MapHandler,
  key : String,
) -> Result[Unit, @types.LoroError] {
  self.apply_map_set(handler, key, None)
}

///|
pub fn LoroDoc::counter_add(
  self : LoroDoc,
  handler : @container.CounterHandler,
  delta : Double,
) -> Result[Unit, @types.LoroError] {
  let op = @op.CounterOp::new(delta)
  self.apply_local_content(handler.id(), @op.RawOpContent::Counter(op))
}

///|
pub fn LoroDoc::counter_inc(
  self : LoroDoc,
  handler : @container.CounterHandler,
  delta : Int,
) -> Result[Unit, @types.LoroError] {
  self.counter_add(handler, delta.to_double())
}

///|
pub fn LoroDoc::counter_value(
  self : LoroDoc,
  handler : @container.CounterHandler,
) -> Result[Double, @types.LoroError] {
  self.state.counter_value(handler.id())
}

///|
pub fn LoroDoc::text_insert(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  text : String,
) -> Result[Unit, @types.LoroError] {
  let slice = @op.ListSlice::from_str(text)
  let list_pos = match self.state.text_text_pos_to_list_pos(handler.id(), pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let op = @op.ListOp::Insert(slice~, pos=list_pos)
  self.apply_text_op(handler, op)
}

///|
pub fn LoroDoc::text_to_string(
  self : LoroDoc,
  handler : @container.TextHandler,
) -> Result[String, @types.LoroError] {
  self.state.text_plain_text(handler.id())
}

///|
pub fn LoroDoc::text_slice(
  self : LoroDoc,
  handler : @container.TextHandler,
  start : Int,
  end : Int,
) -> Result[String, @types.LoroError] {
  self.state.text_slice(handler.id(), start, end)
}

///|
pub fn LoroDoc::text_to_delta(
  self : LoroDoc,
  handler : @container.TextHandler,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  match self.text_get_richtext_value(handler) {
    Ok(@types.LoroValue::List(items)) => Ok(items)
    Ok(_) => Err(@types.LoroError::InternalError("richtext value is not list"))
    Err(err) => Err(err)
  }
}

///|
pub fn LoroDoc::text_diff(
  self : LoroDoc,
  handler : @container.TextHandler,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  self.text_to_delta(handler)
}

///|
fn LoroDoc::list_diff_from_value(
  _self : LoroDoc,
  value : @types.LoroValue,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  match value {
    @types.LoroValue::List(items) => {
      let map : Map[String, @types.LoroValue] = {}
      map["insert"] = @types.LoroValue::List(items)
      Ok([@types.LoroValue::Map(map)])
    }
    _ => Err(@types.LoroError::InternalError("list diff expects list value"))
  }
}

///|
pub fn LoroDoc::list_diff(
  self : LoroDoc,
  handler : @container.ListHandler,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  let value = self.state.deep_value_for_container(handler.id())
  self.list_diff_from_value(value)
}

///|
pub fn LoroDoc::movable_list_diff(
  self : LoroDoc,
  handler : @container.MovableListHandler,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  let value = self.state.deep_value_for_container(handler.id())
  self.list_diff_from_value(value)
}

///|
pub fn LoroDoc::map_diff(
  self : LoroDoc,
  handler : @container.MapHandler,
) -> Result[Array[@types.LoroValue], @types.LoroError] {
  let value = self.state.deep_value_for_container(handler.id())
  match value {
    @types.LoroValue::Map(map) => {
      let out : Array[@types.LoroValue] = []
      for k, v in map {
        let entry : Map[String, @types.LoroValue] = {}
        entry["key"] = @types.LoroValue::String(k)
        entry["value"] = v
        out.push(@types.LoroValue::Map(entry))
      }
      Ok(out)
    }
    _ => Err(@types.LoroError::InternalError("map diff expects map value"))
  }
}

///|
fn LoroDoc::text_unicode_index_from_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  self.state.text_unicode_index_from_utf16(handler.id(), offset)
}

///|
fn LoroDoc::text_unicode_index_from_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  self.state.text_unicode_index_from_utf8(handler.id(), offset)
}

///|
fn LoroDoc::text_unicode_range_from_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[(Int, Int), @types.LoroError] {
  if pos < 0 || len < 0 {
    return Err(@types.LoroError::InternalError("invalid utf16 range"))
  }
  let end = pos + len
  let start_unicode = match self.text_unicode_index_from_utf16(handler, pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let end_unicode = match self.text_unicode_index_from_utf16(handler, end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  Ok((start_unicode, end_unicode))
}

///|
fn LoroDoc::text_unicode_range_from_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[(Int, Int), @types.LoroError] {
  if pos < 0 || len < 0 {
    return Err(@types.LoroError::InternalError("invalid utf8 range"))
  }
  let end = pos + len
  let start_unicode = match self.text_unicode_index_from_utf8(handler, pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let end_unicode = match self.text_unicode_index_from_utf8(handler, end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  Ok((start_unicode, end_unicode))
}

///|
pub fn LoroDoc::text_insert_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  text : String,
) -> Result[Unit, @types.LoroError] {
  let unicode_pos = match self.text_unicode_index_from_utf16(handler, pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_insert(handler, unicode_pos, text)
}

///|
pub fn LoroDoc::text_slice_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[String, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf16(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_slice(handler, start, end)
}

///|
pub fn LoroDoc::text_insert_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  text : String,
) -> Result[Unit, @types.LoroError] {
  let unicode_pos = match self.text_unicode_index_from_utf8(handler, pos) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_insert(handler, unicode_pos, text)
}

///|
pub fn LoroDoc::text_slice_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[String, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf8(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_slice(handler, start, end)
}

///|
pub fn LoroDoc::text_delete(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[Unit, @types.LoroError] {
  if len <= 0 {
    return Err(@types.LoroError::InternalError("len must be > 0"))
  }
  let end = pos + len
  let ids = match self.state.text_ids_in_range(handler.id(), pos, end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let list_pos_at = (text_pos : Int) => self.state.text_text_index_to_list_pos(
    handler.id(),
    text_pos,
  )
  let spans = match delete_spans_from_ids(ids, pos, list_pos_at) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if spans.length() == 0 {
    return Ok(())
  }
  let need_txn = !self.txn_active && spans.length() > 1
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  for span in spans {
    let op = @op.ListOp::Delete(span)
    match self.apply_text_op(handler, op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::text_delete_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf16(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let unicode_len = end - start
  self.text_delete(handler, start, unicode_len)
}

///|
pub fn LoroDoc::text_delete_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf8(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let unicode_len = end - start
  self.text_delete(handler, start, unicode_len)
}

///|
pub fn LoroDoc::text_mark(
  self : LoroDoc,
  handler : @container.TextHandler,
  start : Int,
  end : Int,
  key : String,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  if start < 0 || end < start {
    return Err(@types.LoroError::InternalError("invalid text range"))
  }
  if start == end {
    return Ok(())
  }
  let info = match self.text_style.style_info(key, false) {
    Some(v) => v
    None => return Err(@types.LoroError::StyleConfigMissing(key))
  }
  let list_start = match
    self.state.text_text_pos_to_list_pos(handler.id(), start) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let list_end = match self.state.text_text_pos_to_list_pos(handler.id(), end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let need_txn = !self.txn_active
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  let start_op = @op.ListOp::StyleStart(
    start=list_start,
    end=list_end,
    key~,
    info~,
    value~,
  )
  match self.apply_text_op(handler, start_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let end_op = @op.ListOp::StyleEnd
  match self.apply_text_op(handler, end_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::text_mark_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
  key : String,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf16(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_mark(handler, start, end, key, value)
}

///|
pub fn LoroDoc::text_mark_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
  key : String,
  value : @types.LoroValue,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf8(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_mark(handler, start, end, key, value)
}

///|
pub fn LoroDoc::text_unmark(
  self : LoroDoc,
  handler : @container.TextHandler,
  start : Int,
  end : Int,
  key : String,
) -> Result[Unit, @types.LoroError] {
  if start < 0 || end < start {
    return Err(@types.LoroError::InternalError("invalid text range"))
  }
  if start == end {
    return Ok(())
  }
  let info = match self.text_style.style_info(key, true) {
    Some(v) => v
    None => return Err(@types.LoroError::StyleConfigMissing(key))
  }
  let list_start = match
    self.state.text_text_pos_to_list_pos(handler.id(), start) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let list_end = match self.state.text_text_pos_to_list_pos(handler.id(), end) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let need_txn = !self.txn_active
  if need_txn {
    match self.begin_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  let start_op = @op.ListOp::StyleStart(
    start=list_start,
    end=list_end,
    key~,
    info~,
    value=@types.LoroValue::Null,
  )
  match self.apply_text_op(handler, start_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let end_op = @op.ListOp::StyleEnd
  match self.apply_text_op(handler, end_op) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if need_txn {
    match self.commit_transaction() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::text_unmark_utf16(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
  key : String,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf16(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_unmark(handler, start, end, key)
}

///|
pub fn LoroDoc::text_unmark_utf8(
  self : LoroDoc,
  handler : @container.TextHandler,
  pos : Int,
  len : Int,
  key : String,
) -> Result[Unit, @types.LoroError] {
  let (start, end) = match
    self.text_unicode_range_from_utf8(handler, pos, len) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.text_unmark(handler, start, end, key)
}

///|
pub fn LoroDoc::text_get_richtext_value(
  self : LoroDoc,
  handler : @container.TextHandler,
) -> Result[@types.LoroValue, @types.LoroError] {
  self.state.text_richtext_value(handler.id())
}

///|
pub fn LoroDoc::get_deep_value(self : LoroDoc) -> @types.LoroValue {
  self.state.get_deep_value()
}

///|
pub fn LoroDoc::oplog_vv(self : LoroDoc) -> @types.VersionVector {
  self.oplog.vv()
}

///|
pub fn LoroDoc::version(self : LoroDoc) -> @types.VersionVector {
  self.oplog.vv()
}

///|
pub fn LoroDoc::oplog_frontiers(self : LoroDoc) -> @types.Frontiers {
  self.oplog.frontiers().clone()
}

///|
pub fn LoroDoc::frontiers_to_vv(
  self : LoroDoc,
  frontiers : @types.Frontiers,
) -> Result[@types.VersionVector, @types.LoroError] {
  self.oplog.frontiers_to_vv(frontiers)
}

///|
pub fn LoroDoc::vv_to_frontiers(
  self : LoroDoc,
  vv : @types.VersionVector,
) -> @types.Frontiers {
  self.oplog.vv_to_frontiers(vv)
}

///|
fn trim_change_by_vv(
  change : @oplog.Change,
  vv : @types.VersionVector,
) -> @oplog.Change? {
  let end = vv.get(change.id.peer)
  if change.id.counter >= end {
    return Some(change)
  }
  let change_end = change.id.counter + change.len()
  if change_end <= end {
    return None
  }
  let offset = end - change.id.counter
  Some(change.slice(offset, change.len()))
}

///|
fn LoroDoc::changes_until_vv(
  self : LoroDoc,
  vv : @types.VersionVector,
) -> Array[@oplog.Change] {
  let changes = self.oplog.all_changes()
  let out : Array[@oplog.Change] = []
  for change in changes {
    let change_id = change.id()
    let end = change_id.counter + change.len()
    let vv_end = vv.get(change_id.peer)
    if vv_end <= change_id.counter {
      continue
    }
    if vv_end >= end {
      out.push(change)
    } else {
      let slice_end = vv_end - change_id.counter
      if slice_end > 0 {
        out.push(change.slice(0, slice_end))
      }
    }
  }
  out
}

///|
pub fn LoroDoc::export_updates_from(
  self : LoroDoc,
  from : @types.VersionVector?,
) -> Result[Bytes, @types.LoroError] {
  let changes = self.oplog.all_changes()
  match from {
    None => @encoding.encode_changes(self.arena, changes)
    Some(vv) => {
      let trimmed : Array[@oplog.Change] = []
      for change in changes {
        match trim_change_by_vv(change, vv) {
          Some(value) => trimmed.push(value)
          None => ()
        }
      }
      @encoding.encode_changes(self.arena, trimmed)
    }
  }
}

///|
pub fn LoroDoc::export_from(
  self : LoroDoc,
  from : @types.VersionVector?,
) -> Result[Bytes, @types.LoroError] {
  self.export_updates_from(from)
}

///|
pub fn LoroDoc::checkout(
  self : LoroDoc,
  frontiers : @types.Frontiers,
) -> Result[Unit, @types.LoroError] {
  let vv = match self.oplog.frontiers_to_vv(frontiers) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let changes = self.changes_until_vv(vv)
  let next_state = @state.DocState::new()
  for change in changes {
    let base_vv = match self.oplog.frontiers_to_vv(change.deps()) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    let local_vv = base_vv
    let change_id = change.id()
    let base_lamport = change.lamport()
    for op in change.ops() {
      let counter = op.counter()
      let offset = counter - change_id.counter
      let op_lamport = base_lamport + offset.reinterpret_as_uint()
      let id = @types.ID::new(change_id.peer, counter)
      match
        next_state.apply_op_with_vv(self.arena, op, id, op_lamport, local_vv) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let len = op.content_len()
      if len > 0 {
        let last_id = @types.ID::new(change_id.peer, counter + len - 1)
        local_vv.extend_to_include_last_id(last_id)
      }
    }
  }
  self.state = next_state
  self.emit_snapshot()
  Ok(())
}

///|
pub fn LoroDoc::fork_at(
  self : LoroDoc,
  frontiers : @types.Frontiers,
) -> Result[LoroDoc, @types.LoroError] {
  let vv = match self.oplog.frontiers_to_vv(frontiers) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let changes = self.changes_until_vv(vv)
  let bytes = match @encoding.encode_changes(self.arena, changes) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let new_doc = LoroDoc::new()
  match new_doc.import_updates(bytes) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  Ok(new_doc)
}

///|
pub fn LoroDoc::export_bytes(
  self : LoroDoc,
  mode : @types.ExportMode,
) -> Result[Bytes, @types.LoroError] {
  match mode {
    @types.ExportMode::Snapshot => {
      let changes = self.oplog.all_changes()
      @encoding.encode_snapshot(self.arena, self.state, changes)
    }
    @types.ExportMode::Updates(from) => self.export_updates_from(from)
  }
}

///|
pub fn LoroDoc::export_updates(
  self : LoroDoc,
) -> Result[Bytes, @types.LoroError] {
  self.export_updates_from(None)
}

///|
pub fn LoroDoc::export_loro_updates(
  self : LoroDoc,
) -> Result[Bytes, @types.LoroError] {
  let changes = self.oplog.all_changes()
  @encoding.encode_loro_updates(self.arena, changes)
}

///|
pub fn LoroDoc::export_loro_snapshot(
  self : LoroDoc,
) -> Result[Bytes, @types.LoroError] {
  let changes = self.oplog.all_changes()
  @encoding.encode_loro_snapshot(self.arena, self.state, changes)
}

///|
priv enum ApplyState {
  Applied
  CanApply
  Awaiting
}

///|
fn change_apply_state(
  vv : @types.VersionVector,
  change : @encoding.DecodedChange,
) -> ApplyState {
  let span_start = change.id.counter
  let span_end = span_start + change.len()
  let vv_latest_ctr = vv.get(change.id.peer)
  if vv_latest_ctr >= span_end {
    return ApplyState::Applied
  }
  if vv_latest_ctr < span_start {
    return ApplyState::Awaiting
  }
  for dep in change.deps.iter() {
    if !vv.includes_id(dep) {
      return ApplyState::Awaiting
    }
  }
  ApplyState::CanApply
}

///|
fn LoroDoc::apply_decoded_change(
  self : LoroDoc,
  change : @encoding.DecodedChange,
) -> Result[Unit, @types.LoroError] {
  match self.oplog.get_change(change.id) {
    Some(_) => return Ok(())
    None => ()
  }
  let ops : Array[@op.Op] = []
  let mut offset = 0
  for entry in change.ops {
    let content_len = entry.content.content_len()
    let id = change.id.inc(offset)
    let lamport = change.lamport + offset.reinterpret_as_uint()
    let raw_op = @op.RawOp::new(id, lamport, entry.container, entry.content)
    let persisted = self.arena.to_persisted_op(raw_op)
    ops.push(persisted)
    match self.state.apply_raw_op(self.arena, raw_op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
    offset = offset + content_len
  }
  let new_change = @oplog.Change::new_with_meta(
    ops,
    change.deps,
    change.id,
    change.lamport,
    change.timestamp,
    change.commit_msg,
    change.origin,
  )
  self.oplog.insert_new_change(new_change, false)
  match self.oplog.get_change(change.id) {
    Some(saved) => self.emit_change(saved, false)
    None => ()
  }
  let change_end = change.lamport + offset.reinterpret_as_uint()
  if change_end > self.lamport {
    self.lamport = change_end
  }
  if change.id.peer == self.peer {
    let local_end = change.id.counter + offset
    if local_end > self.counter {
      self.counter = local_end
    }
  }
  Ok(())
}

///|
fn LoroDoc::apply_decoded_change_to_state_and_oplog(
  self : LoroDoc,
  change : @encoding.DecodedChange,
) -> Result[Unit, @types.LoroError] {
  match self.oplog.get_change(change.id) {
    Some(_) => return Ok(())
    None => ()
  }
  let ops : Array[@op.Op] = []
  let mut offset = 0
  for entry in change.ops {
    let content_len = entry.content.content_len()
    let id = change.id.inc(offset)
    let lamport = change.lamport + offset.reinterpret_as_uint()
    let raw_op = @op.RawOp::new(id, lamport, entry.container, entry.content)
    let persisted = self.arena.to_persisted_op(raw_op)
    ops.push(persisted)
    match self.state.apply_raw_op(self.arena, raw_op) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
    offset = offset + content_len
  }
  let new_change = @oplog.Change::new_with_meta(
    ops,
    change.deps,
    change.id,
    change.lamport,
    change.timestamp,
    change.commit_msg,
    change.origin,
  )
  self.oplog.insert_new_change(new_change, false)
  let change_end = change.lamport + offset.reinterpret_as_uint()
  if change_end > self.lamport {
    self.lamport = change_end
  }
  if change.id.peer == self.peer {
    let local_end = change.id.counter + offset
    if local_end > self.counter {
      self.counter = local_end
    }
  }
  Ok(())
}

///|
fn LoroDoc::apply_decoded_change_to_oplog(
  self : LoroDoc,
  change : @encoding.DecodedChange,
) -> Result[Unit, @types.LoroError] {
  match self.oplog.get_change(change.id) {
    Some(_) => return Ok(())
    None => ()
  }
  let ops : Array[@op.Op] = []
  let mut offset = 0
  for entry in change.ops {
    let content_len = entry.content.content_len()
    let id = change.id.inc(offset)
    let lamport = change.lamport + offset.reinterpret_as_uint()
    let raw_op = @op.RawOp::new(id, lamport, entry.container, entry.content)
    let persisted = self.arena.to_persisted_op(raw_op)
    ops.push(persisted)
    offset = offset + content_len
  }
  let new_change = @oplog.Change::new_with_meta(
    ops,
    change.deps,
    change.id,
    change.lamport,
    change.timestamp,
    change.commit_msg,
    change.origin,
  )
  self.oplog.insert_new_change(new_change, false)
  let change_end = change.lamport + offset.reinterpret_as_uint()
  if change_end > self.lamport {
    self.lamport = change_end
  }
  if change.id.peer == self.peer {
    let local_end = change.id.counter + offset
    if local_end > self.counter {
      self.counter = local_end
    }
  }
  Ok(())
}

///|
pub fn LoroDoc::import_updates(
  self : LoroDoc,
  bytes : Bytes,
) -> Result[Unit, @types.LoroError] {
  let decoded = match @encoding.decode_changes(bytes) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  self.apply_decoded_changes(decoded)
}

///|
fn LoroDoc::apply_decoded_changes(
  self : LoroDoc,
  decoded : Array[@encoding.DecodedChange],
) -> Result[Unit, @types.LoroError] {
  let mut pending = decoded
  let mut applied_any = false
  let mut progressed = true
  while progressed {
    progressed = false
    let next_pending : Array[@encoding.DecodedChange] = []
    for change in pending {
      match change_apply_state(self.oplog.vv(), change) {
        ApplyState::Applied => ()
        ApplyState::CanApply =>
          match self.apply_decoded_change(change) {
            Ok(_) => {
              progressed = true
              applied_any = true
            }
            Err(err) => return Err(err)
          }
        ApplyState::Awaiting => next_pending.push(change)
      }
    }
    pending = next_pending
  }
  if pending.length() > 0 {
    return Err(@types.LoroError::MissingDependency("missing dependencies"))
  }
  if applied_any {
    match self.rebuild_state_from_oplog() {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
fn LoroDoc::rebuild_state_from_oplog(
  self : LoroDoc,
) -> Result[Unit, @types.LoroError] {
  let new_state = @state.DocState::new()
  let changes = self.oplog.changes_sorted()
  for change in changes {
    let base_vv = match self.oplog.frontiers_to_vv(change.deps()) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    let vv = base_vv
    let base_id = change.id()
    let base_lamport = change.lamport()
    for op in change.ops() {
      let counter = op.counter()
      let offset = counter - base_id.counter
      let lamport = base_lamport + offset.reinterpret_as_uint()
      let id = @types.ID::new(base_id.peer, counter)
      match new_state.apply_op_with_vv(self.arena, op, id, lamport, vv) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
      let len = op.content_len()
      if len > 0 {
        let last_id = @types.ID::new(base_id.peer, counter + len - 1)
        vv.extend_to_include_last_id(last_id)
      }
    }
  }
  self.state = new_state
  Ok(())
}

///|
fn LoroDoc::apply_decoded_snapshot(
  self : LoroDoc,
  snapshot : @encoding.DecodedSnapshot,
) -> Result[Unit, @types.LoroError] {
  self.state = snapshot.state
  self.oplog = @oplog.OpLog::new()
  self.arena = @arena.SharedArena::new()
  self.counter = 0
  self.lamport = 0
  if snapshot.needs_replay {
    for change in snapshot.changes {
      match self.apply_decoded_change_to_state_and_oplog(change) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  } else {
    for change in snapshot.changes {
      match self.apply_decoded_change_to_oplog(change) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  }
  self.emit_snapshot()
  Ok(())
}

///|
pub fn LoroDoc::import_bytes(
  self : LoroDoc,
  bytes : Bytes,
) -> Result[Unit, @types.LoroError] {
  let decoded = match @encoding.decode_payload(bytes) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  match decoded {
    @encoding.DecodedPayload::Updates(changes) =>
      self.apply_decoded_changes(changes)
    @encoding.DecodedPayload::Snapshot(snapshot) =>
      self.apply_decoded_snapshot(snapshot)
  }
}