///|
// Document state for map/list/text containers.
pub enum ContainerState {
  MapState(MapState)
  ListState(ListState)
  TextState(TextState)
  MovableListState(MovableListState)
  TreeState(TreeState)
  CounterState(CounterState)
} derive(Show)

///|
pub struct DocState {
  containers : Map[@types.ContainerID, ContainerState]
} derive(Show)

///|
pub fn DocState::new() -> DocState {
  DocState::{ containers: Map::new() }
}

///|
pub fn DocState::get_or_create(
  self : DocState,
  id : @types.ContainerID,
  container_type : @types.ContainerType,
) -> ContainerState {
  match self.containers.get(id) {
    Some(state) => state
    None => {
      let state = match container_type {
        @types.ContainerType::Map => ContainerState::MapState(MapState::new())
        @types.ContainerType::List =>
          ContainerState::ListState(ListState::new())
        @types.ContainerType::Text =>
          ContainerState::TextState(TextState::new())
        @types.ContainerType::MovableList =>
          ContainerState::MovableListState(MovableListState::new())
        @types.ContainerType::Tree =>
          ContainerState::TreeState(TreeState::new())
        @types.ContainerType::Counter =>
          ContainerState::CounterState(CounterState::new())
        _ => ContainerState::MapState(MapState::new())
      }
      self.containers[id] = state
      state
    }
  }
}

///|
pub fn DocState::apply_raw_op(
  self : DocState,
  arena : @arena.SharedArena,
  raw_op : @op.RawOp,
) -> Result[Unit, @types.LoroError] {
  let id = raw_op.container()
  let container_type = id.container_type()
  let base_id = raw_op.id_full()
  let op = arena.to_persisted_op(raw_op)
  let state = match self.containers.get(id) {
    Some(existing) => existing
    None => {
      let created = match container_type {
        @types.ContainerType::Map => ContainerState::MapState(MapState::new())
        @types.ContainerType::List =>
          ContainerState::ListState(ListState::new())
        @types.ContainerType::Text =>
          ContainerState::TextState(TextState::new())
        @types.ContainerType::MovableList =>
          ContainerState::MovableListState(MovableListState::new())
        @types.ContainerType::Tree =>
          ContainerState::TreeState(TreeState::new())
        @types.ContainerType::Counter =>
          ContainerState::CounterState(CounterState::new())
        _ => ContainerState::MapState(MapState::new())
      }
      self.containers[id] = created
      created
    }
  }
  let updated = match (container_type, state, op.content()) {
    (
      @types.ContainerType::Map,
      ContainerState::MapState(map_state),
      @op.InnerContent::Map(map_set),
    ) => {
      let key = map_set.key()
      match map_set.value() {
        Some(v) => map_state.set(key, v, base_id)
        None => map_state.delete(key, base_id)
      }
      ContainerState::MapState(map_state)
    }
    (
      @types.ContainerType::List,
      ContainerState::ListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op(arena, inner, base_id) {
        Ok(_) => ContainerState::ListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Text,
      ContainerState::TextState(text_state),
      @op.InnerContent::List(inner),
    ) =>
      match text_state.apply_inner_op(inner, base_id) {
        Ok(_) => ContainerState::TextState(text_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::MovableList,
      ContainerState::MovableListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op(arena, inner, base_id) {
        Ok(_) => ContainerState::MovableListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Tree,
      ContainerState::TreeState(tree_state),
      @op.InnerContent::Tree(tree_op),
    ) =>
      match tree_state.apply_op(tree_op, base_id) {
        Ok(_) => ContainerState::TreeState(tree_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Counter,
      ContainerState::CounterState(counter_state),
      @op.InnerContent::Counter(counter_op),
    ) => {
      counter_state.apply(counter_op.delta())
      ContainerState::CounterState(counter_state)
    }
    _ => return Err(@types.LoroError::Unsupported("op/container mismatch"))
  }
  self.containers[id] = updated
  Ok(())
}

///|
pub fn DocState::apply_op(
  self : DocState,
  arena : @arena.SharedArena,
  op : @op.Op,
  id : @types.ID,
  lamport : @types.Lamport,
) -> Result[Unit, @types.LoroError] {
  let container_id = op.container()
  let container_type = container_id.container_type()
  let base_id = @types.IdFull::new(id.peer, id.counter, lamport)
  let state = match self.containers.get(container_id) {
    Some(existing) => existing
    None => {
      let created = match container_type {
        @types.ContainerType::Map => ContainerState::MapState(MapState::new())
        @types.ContainerType::List =>
          ContainerState::ListState(ListState::new())
        @types.ContainerType::Text =>
          ContainerState::TextState(TextState::new())
        @types.ContainerType::MovableList =>
          ContainerState::MovableListState(MovableListState::new())
        @types.ContainerType::Tree =>
          ContainerState::TreeState(TreeState::new())
        @types.ContainerType::Counter =>
          ContainerState::CounterState(CounterState::new())
        _ => ContainerState::MapState(MapState::new())
      }
      self.containers[container_id] = created
      created
    }
  }
  let updated = match (container_type, state, op.content()) {
    (
      @types.ContainerType::Map,
      ContainerState::MapState(map_state),
      @op.InnerContent::Map(map_set),
    ) => {
      let key = map_set.key()
      match map_set.value() {
        Some(v) => map_state.set(key, v, base_id)
        None => map_state.delete(key, base_id)
      }
      ContainerState::MapState(map_state)
    }
    (
      @types.ContainerType::List,
      ContainerState::ListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op(arena, inner, base_id) {
        Ok(_) => ContainerState::ListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Text,
      ContainerState::TextState(text_state),
      @op.InnerContent::List(inner),
    ) =>
      match text_state.apply_inner_op(inner, base_id) {
        Ok(_) => ContainerState::TextState(text_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::MovableList,
      ContainerState::MovableListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op(arena, inner, base_id) {
        Ok(_) => ContainerState::MovableListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Tree,
      ContainerState::TreeState(tree_state),
      @op.InnerContent::Tree(tree_op),
    ) =>
      match tree_state.apply_op(tree_op, base_id) {
        Ok(_) => ContainerState::TreeState(tree_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Counter,
      ContainerState::CounterState(counter_state),
      @op.InnerContent::Counter(counter_op),
    ) => {
      counter_state.apply(counter_op.delta())
      ContainerState::CounterState(counter_state)
    }
    _ => return Err(@types.LoroError::Unsupported("op/container mismatch"))
  }
  self.containers[container_id] = updated
  Ok(())
}

///|
pub fn DocState::apply_op_with_vv(
  self : DocState,
  arena : @arena.SharedArena,
  op : @op.Op,
  id : @types.ID,
  lamport : @types.Lamport,
  vv : @types.VersionVector,
) -> Result[Unit, @types.LoroError] {
  let container_id = op.container()
  let container_type = container_id.container_type()
  let base_id = @types.IdFull::new(id.peer, id.counter, lamport)
  let state = match self.containers.get(container_id) {
    Some(existing) => existing
    None => {
      let created = match container_type {
        @types.ContainerType::Map => ContainerState::MapState(MapState::new())
        @types.ContainerType::List =>
          ContainerState::ListState(ListState::new())
        @types.ContainerType::Text =>
          ContainerState::TextState(TextState::new())
        @types.ContainerType::MovableList =>
          ContainerState::MovableListState(MovableListState::new())
        @types.ContainerType::Tree =>
          ContainerState::TreeState(TreeState::new())
        @types.ContainerType::Counter =>
          ContainerState::CounterState(CounterState::new())
        _ => ContainerState::MapState(MapState::new())
      }
      self.containers[container_id] = created
      created
    }
  }
  let updated = match (container_type, state, op.content()) {
    (
      @types.ContainerType::Map,
      ContainerState::MapState(map_state),
      @op.InnerContent::Map(map_set),
    ) => {
      let key = map_set.key()
      match map_set.value() {
        Some(v) => map_state.set(key, v, base_id)
        None => map_state.delete(key, base_id)
      }
      ContainerState::MapState(map_state)
    }
    (
      @types.ContainerType::List,
      ContainerState::ListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op_with_vv(arena, inner, base_id, vv) {
        Ok(_) => ContainerState::ListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Text,
      ContainerState::TextState(text_state),
      @op.InnerContent::List(inner),
    ) =>
      match text_state.apply_inner_op_with_vv(inner, base_id, vv) {
        Ok(_) => ContainerState::TextState(text_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::MovableList,
      ContainerState::MovableListState(list_state),
      @op.InnerContent::List(inner),
    ) =>
      match list_state.apply_inner_op_with_vv(arena, inner, base_id, vv) {
        Ok(_) => ContainerState::MovableListState(list_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Tree,
      ContainerState::TreeState(tree_state),
      @op.InnerContent::Tree(tree_op),
    ) =>
      match tree_state.apply_op(tree_op, base_id) {
        Ok(_) => ContainerState::TreeState(tree_state)
        Err(err) => return Err(err)
      }
    (
      @types.ContainerType::Counter,
      ContainerState::CounterState(counter_state),
      @op.InnerContent::Counter(counter_op),
    ) => {
      counter_state.apply(counter_op.delta())
      ContainerState::CounterState(counter_state)
    }
    _ => return Err(@types.LoroError::Unsupported("op/container mismatch"))
  }
  self.containers[container_id] = updated
  Ok(())
}

///|
pub fn DocState::container_items(
  self : DocState,
) -> Array[(@types.ContainerID, ContainerState)] {
  let out : Array[(@types.ContainerID, ContainerState)] = []
  for id, state in self.containers {
    out.push((id, state))
  }
  out
}

///|
pub fn DocState::set_container_state(
  self : DocState,
  id : @types.ContainerID,
  state : ContainerState,
) -> Unit {
  self.containers[id] = state
}

///|
pub fn DocState::set_map_state(
  self : DocState,
  id : @types.ContainerID,
  state : MapState,
) -> Unit {
  self.containers[id] = ContainerState::MapState(state)
}

///|
pub fn DocState::set_list_state(
  self : DocState,
  id : @types.ContainerID,
  state : ListState,
) -> Unit {
  self.containers[id] = ContainerState::ListState(state)
}

///|
pub fn DocState::set_text_state(
  self : DocState,
  id : @types.ContainerID,
  state : TextState,
) -> Unit {
  self.containers[id] = ContainerState::TextState(state)
}

///|
pub fn DocState::set_movable_list_state(
  self : DocState,
  id : @types.ContainerID,
  state : MovableListState,
) -> Unit {
  self.containers[id] = ContainerState::MovableListState(state)
}

///|
pub fn DocState::set_tree_state(
  self : DocState,
  id : @types.ContainerID,
  state : TreeState,
) -> Unit {
  self.containers[id] = ContainerState::TreeState(state)
}

///|
pub fn DocState::set_counter_state(
  self : DocState,
  id : @types.ContainerID,
  state : CounterState,
) -> Unit {
  self.containers[id] = ContainerState::CounterState(state)
}

///|
pub fn DocState::list_id_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.IdFull, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::ListState(list_state)) => list_state.id_at(index)
    _ => Err(@types.LoroError::InvalidContainer("list not found"))
  }
}

///|
pub fn DocState::list_ids_in_range(
  self : DocState,
  id : @types.ContainerID,
  start : Int,
  end : Int,
) -> Result[Array[@types.IdFull], @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::ListState(list_state)) =>
      list_state.ids_in_range(start, end)
    _ => Err(@types.LoroError::InvalidContainer("list not found"))
  }
}

///|
pub fn DocState::text_id_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.IdFull, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) => text_state.id_at(index)
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_ids_in_range(
  self : DocState,
  id : @types.ContainerID,
  start : Int,
  end : Int,
) -> Result[Array[@types.IdFull], @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      text_state.ids_in_range(start, end)
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::movable_list_id_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.IdFull, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MovableListState(list_state)) =>
      list_state.id_at(index)
    _ => Err(@types.LoroError::InvalidContainer("movable list not found"))
  }
}

///|
pub fn DocState::movable_list_ids_in_range(
  self : DocState,
  id : @types.ContainerID,
  start : Int,
  end : Int,
) -> Result[Array[@types.IdFull], @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MovableListState(list_state)) =>
      list_state.ids_in_range(start, end)
    _ => Err(@types.LoroError::InvalidContainer("movable list not found"))
  }
}

///|
pub fn DocState::movable_list_elem_id_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.IdLp, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MovableListState(list_state)) =>
      list_state.elem_id_at(index)
    _ => Err(@types.LoroError::InvalidContainer("movable list not found"))
  }
}

///|
pub fn DocState::list_len(
  self : DocState,
  id : @types.ContainerID,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::ListState(list_state)) => Ok(list_state.len())
    _ => Err(@types.LoroError::InvalidContainer("list not found"))
  }
}

///|
pub fn DocState::list_value_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.LoroValue?, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::ListState(list_state)) => list_state.value_at(index)
    _ => Err(@types.LoroError::InvalidContainer("list not found"))
  }
}

///|
pub fn DocState::movable_list_value_at(
  self : DocState,
  id : @types.ContainerID,
  index : Int,
) -> Result[@types.LoroValue?, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MovableListState(list_state)) =>
      list_state.value_at(index)
    _ => Err(@types.LoroError::InvalidContainer("movable list not found"))
  }
}

///|
pub fn DocState::text_len(
  self : DocState,
  id : @types.ContainerID,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) => Ok(text_state.len())
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_plain_text(
  self : DocState,
  id : @types.ContainerID,
) -> Result[String, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) => Ok(text_state.plain_text())
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_styles(
  self : DocState,
  id : @types.ContainerID,
) -> Result[Array[StyleSpan], @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) => Ok(text_state.styles())
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_list_pos_to_text_pos(
  self : DocState,
  id : @types.ContainerID,
  list_pos : Int,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      Ok(text_state.list_pos_to_text_pos(list_pos))
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_text_pos_to_list_pos(
  self : DocState,
  id : @types.ContainerID,
  text_pos : Int,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      Ok(text_state.text_pos_to_list_pos(text_pos))
    None => {
      let container_type = id.container_type()
      if container_type == @types.ContainerType::Text ||
        container_type == @types.ContainerType::RichText {
        Ok(0)
      } else {
        Err(@types.LoroError::InvalidContainer("text not found"))
      }
    }
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_text_index_to_list_pos(
  self : DocState,
  id : @types.ContainerID,
  text_pos : Int,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      text_state.text_index_to_list_pos(text_pos)
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_slice(
  self : DocState,
  id : @types.ContainerID,
  start : Int,
  end : Int,
) -> Result[String, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) => text_state.slice(start, end)
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_unicode_index_from_utf16(
  self : DocState,
  id : @types.ContainerID,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      text_state.unicode_index_from_utf16(offset)
    None => {
      let container_type = id.container_type()
      if container_type == @types.ContainerType::Text ||
        container_type == @types.ContainerType::RichText {
        Ok(0)
      } else {
        Err(@types.LoroError::InvalidContainer("text not found"))
      }
    }
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_unicode_index_from_utf8(
  self : DocState,
  id : @types.ContainerID,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      text_state.unicode_index_from_utf8(offset)
    None => {
      let container_type = id.container_type()
      if container_type == @types.ContainerType::Text ||
        container_type == @types.ContainerType::RichText {
        Ok(0)
      } else {
        Err(@types.LoroError::InvalidContainer("text not found"))
      }
    }
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::text_richtext_value(
  self : DocState,
  id : @types.ContainerID,
) -> Result[@types.LoroValue, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TextState(text_state)) =>
      Ok(text_state.to_richtext_value())
    _ => Err(@types.LoroError::InvalidContainer("text not found"))
  }
}

///|
pub fn DocState::movable_list_len(
  self : DocState,
  id : @types.ContainerID,
) -> Result[Int, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MovableListState(list_state)) => Ok(list_state.len())
    _ => Err(@types.LoroError::InvalidContainer("movable list not found"))
  }
}

///|
pub fn DocState::tree_children(
  self : DocState,
  id : @types.ContainerID,
  parent : @types.TreeParentId,
) -> Result[Array[@types.TreeID], @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TreeState(tree_state)) =>
      match tree_state.children(parent) {
        Some(children) => Ok(children)
        None => Err(@types.LoroError::InvalidContainer("tree parent not found"))
      }
    _ => Err(@types.LoroError::InvalidContainer("tree not found"))
  }
}

///|
pub fn DocState::tree_node(
  self : DocState,
  id : @types.ContainerID,
  target : @types.TreeID,
) -> Result[TreeNode, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::TreeState(tree_state)) =>
      match tree_state.get_node(target) {
        Some(node) => Ok(node)
        None => Err(@types.LoroError::TreeNodeNotFound(target))
      }
    _ => Err(@types.LoroError::InvalidContainer("tree not found"))
  }
}

///|
pub fn DocState::map_value(
  self : DocState,
  id : @types.ContainerID,
) -> @types.LoroValue? {
  match self.containers.get(id) {
    Some(ContainerState::MapState(map_state)) => Some(map_state.to_value())
    _ => None
  }
}

///|
pub fn DocState::counter_value(
  self : DocState,
  id : @types.ContainerID,
) -> Result[Double, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::CounterState(counter_state)) =>
      Ok(counter_state.value())
    _ => Err(@types.LoroError::InvalidContainer("counter not found"))
  }
}

///|
pub fn DocState::deep_value_for_container(
  self : DocState,
  id : @types.ContainerID,
) -> @types.LoroValue {
  let seen : Map[@types.ContainerID, Bool] = {}
  self.container_deep_value(id, seen)
}

///|
pub fn DocState::map_get(
  self : DocState,
  id : @types.ContainerID,
  key : String,
) -> Result[@types.LoroValue?, @types.LoroError] {
  match self.containers.get(id) {
    Some(ContainerState::MapState(map_state)) => Ok(map_state.get(key))
    _ => Err(@types.LoroError::InvalidContainer("map not found"))
  }
}

///|
fn DocState::empty_container_value(
  _self : DocState,
  container_type : @types.ContainerType,
) -> @types.LoroValue {
  match container_type {
    @types.ContainerType::Map => @types.LoroValue::Map({})
    @types.ContainerType::List => @types.LoroValue::List([])
    @types.ContainerType::Text => @types.LoroValue::String("")
    @types.ContainerType::MovableList => @types.LoroValue::List([])
    @types.ContainerType::Tree => @types.LoroValue::List([])
    @types.ContainerType::Counter => @types.LoroValue::F64(0.0)
    _ => @types.LoroValue::Null
  }
}

///|
fn DocState::tree_node_value(
  self : DocState,
  tree_state : TreeState,
  node_id : @types.TreeID,
  seen : Map[@types.ContainerID, Bool],
) -> @types.LoroValue {
  let children = match tree_state.children(@types.TreeParentId::Node(node_id)) {
    Some(value) => value
    None => []
  }
  let child_values : Array[@types.LoroValue] = []
  for child in children {
    child_values.push(self.tree_node_value(tree_state, child, seen))
  }
  let map : Map[String, @types.LoroValue] = {}
  map["id"] = @types.LoroValue::String("\{node_id.peer}:\{node_id.counter}")
  map["children"] = @types.LoroValue::List(child_values)
  let meta_id = node_id.meta_container()
  let meta_value = match self.map_value(meta_id) {
    Some(value) => self.resolve_value_inner(value, seen)
    None => @types.LoroValue::Map({})
  }
  map["meta"] = meta_value
  @types.LoroValue::Map(map)
}

///|
fn DocState::container_deep_value(
  self : DocState,
  id : @types.ContainerID,
  seen : Map[@types.ContainerID, Bool],
) -> @types.LoroValue {
  if seen.get(id) is Some(true) {
    return @types.LoroValue::Null
  }
  seen[id] = true
  match self.containers.get(id) {
    Some(ContainerState::MapState(map_state)) =>
      self.resolve_value_inner(map_state.to_value(), seen)
    Some(ContainerState::ListState(list_state)) =>
      self.resolve_value_inner(list_state.to_value(), seen)
    Some(ContainerState::TextState(text_state)) => text_state.to_value()
    Some(ContainerState::MovableListState(list_state)) =>
      self.resolve_value_inner(list_state.to_value(), seen)
    Some(ContainerState::TreeState(tree_state)) => {
      let roots = tree_state.roots()
      let items : Array[@types.LoroValue] = []
      for root in roots {
        items.push(self.tree_node_value(tree_state, root, seen))
      }
      @types.LoroValue::List(items)
    }
    Some(ContainerState::CounterState(counter_state)) =>
      counter_state.to_value()
    None => self.empty_container_value(id.container_type())
  }
}

///|
fn DocState::resolve_value_inner(
  self : DocState,
  value : @types.LoroValue,
  seen : Map[@types.ContainerID, Bool],
) -> @types.LoroValue {
  match value {
    @types.LoroValue::Container(id) => self.container_deep_value(id, seen)
    @types.LoroValue::List(items) => {
      let out : Array[@types.LoroValue] = []
      for item in items {
        out.push(self.resolve_value_inner(item, seen))
      }
      @types.LoroValue::List(out)
    }
    @types.LoroValue::Map(map) => {
      let out : Map[String, @types.LoroValue] = {}
      for k, v in map {
        out[k] = self.resolve_value_inner(v, seen)
      }
      @types.LoroValue::Map(out)
    }
    other => other
  }
}

///|
pub fn DocState::get_deep_value(self : DocState) -> @types.LoroValue {
  let root : Map[String, @types.LoroValue] = {}
  for id, _state in self.containers {
    match id {
      @types.ContainerID::Root(name~, container_type=_) => {
        let seen : Map[@types.ContainerID, Bool] = {}
        root[name] = self.container_deep_value(id, seen)
      }
      _ => ()
    }
  }
  @types.LoroValue::Map(root)
}