///|
// Text state modeled as per-character entries with IDs.
pub struct TextElem {
  ch : Char
  id : @types.IdFull
} derive(Show)

///|
pub struct StyleSpan {
  start : Int
  end : Int
  key : String
  value : @types.LoroValue
  expand : @types.ExpandType
} derive(Show)

///|
pub fn StyleSpan::new(
  start : Int,
  end : Int,
  key : String,
  value : @types.LoroValue,
  expand : @types.ExpandType,
) -> StyleSpan {
  StyleSpan::{ start, end, key, value, expand }
}

///|
pub fn StyleSpan::start(self : StyleSpan) -> Int {
  self.start
}

///|
pub fn StyleSpan::end(self : StyleSpan) -> Int {
  self.end
}

///|
pub fn StyleSpan::key(self : StyleSpan) -> String {
  self.key
}

///|
pub fn StyleSpan::value(self : StyleSpan) -> @types.LoroValue {
  self.value
}

///|
pub fn StyleSpan::expand(self : StyleSpan) -> @types.ExpandType {
  self.expand
}

///|
pub(all) enum AnchorType {
  Start
  End
} derive(Show, Eq)

///|
pub(all) struct StyleOp {
  id : @types.IdFull
  key : String
  value : @types.LoroValue
  info : Int
} derive(Show)

///|
pub(all) enum RichtextElem {
  Text(TextElem)
  Anchor(style~ : StyleOp, anchor~ : AnchorType)
} derive(Show)

///|
pub struct TextState {
  elems : Array[RichtextElem]
} derive(Show)

///|
pub fn TextState::new() -> TextState {
  TextState::{ elems: [] }
}

///|
fn loro_value_eq(a : @types.LoroValue, b : @types.LoroValue) -> Bool {
  match (a, b) {
    (@types.LoroValue::Null, @types.LoroValue::Null) => true
    (@types.LoroValue::Bool(x), @types.LoroValue::Bool(y)) => x == y
    (@types.LoroValue::I64(x), @types.LoroValue::I64(y)) => x == y
    (@types.LoroValue::F64(x), @types.LoroValue::F64(y)) => x == y
    (@types.LoroValue::String(x), @types.LoroValue::String(y)) => x == y
    (@types.LoroValue::Bytes(x), @types.LoroValue::Bytes(y)) => x == y
    (@types.LoroValue::List(xs), @types.LoroValue::List(ys)) => {
      if xs.length() != ys.length() {
        return false
      }
      for i = 0; i < xs.length(); i = i + 1 {
        if !loro_value_eq(xs[i], ys[i]) {
          return false
        }
      }
      true
    }
    (@types.LoroValue::Map(xm), @types.LoroValue::Map(ym)) => map_eq(xm, ym)
    (@types.LoroValue::Container(x), @types.LoroValue::Container(y)) => x == y
    _ => false
  }
}

///|
fn map_eq(
  a : Map[String, @types.LoroValue],
  b : Map[String, @types.LoroValue],
) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for k, v in a {
    match b.get(k) {
      Some(value) => if !loro_value_eq(v, value) { return false }
      None => return false
    }
  }
  true
}

///|
fn value_is_null(v : @types.LoroValue) -> Bool {
  v is @types.LoroValue::Null
}

///|
fn value_is_false(v : @types.LoroValue) -> Bool {
  match v {
    @types.LoroValue::Bool(b) => !b
    _ => false
  }
}

///|
fn style_op_same_id(a : StyleOp, b : StyleOp) -> Bool {
  a.id.peer == b.id.peer && a.id.counter == b.id.counter
}

///|
fn style_op_cmp(a : StyleOp, b : StyleOp) -> Int {
  if a.id.lamport < b.id.lamport {
    -1
  } else if a.id.lamport > b.id.lamport {
    1
  } else if a.id.peer < b.id.peer {
    -1
  } else if a.id.peer > b.id.peer {
    1
  } else {
    0
  }
}

///|
fn best_style(ops : Array[StyleOp]) -> StyleOp? {
  if ops.length() == 0 {
    return None
  }
  let mut best = ops[0]
  for i = 1; i < ops.length(); i = i + 1 {
    let candidate = ops[i]
    if style_op_cmp(candidate, best) > 0 {
      best = candidate
    }
  }
  Some(best)
}

///|
fn anchor_id(style : StyleOp, anchor : AnchorType) -> @types.IdFull {
  match anchor {
    AnchorType::Start => style.id
    AnchorType::End =>
      @types.IdFull::new(
        style.id.peer,
        style.id.counter + 1,
        style.id.lamport + 1U,
      )
  }
}

///|
fn text_elem_visible_in_vv(
  vv : @types.VersionVector,
  elem : RichtextElem,
) -> Bool {
  match elem {
    RichtextElem::Text(text) => vv.includes_id(text.id.to_id())
    RichtextElem::Anchor(style~, anchor=_) => vv.includes_id(style.id.to_id())
  }
}

///|
fn text_elem_order_id(elem : RichtextElem) -> @types.IdFull {
  match elem {
    RichtextElem::Text(text) => text.id
    RichtextElem::Anchor(style~, anchor~) => anchor_id(style, anchor)
  }
}

///|
fn prefer_insert_before(info : Int, anchor : AnchorType) -> Bool {
  let expand = @types.ExpandType::from_info(info)
  match anchor {
    AnchorType::Start => !expand.expand_before()
    AnchorType::End => expand.expand_after()
  }
}

///|
pub fn TextState::to_value(self : TextState) -> @types.LoroValue {
  @types.LoroValue::String(self.plain_text())
}

///|
pub fn TextState::elems(self : TextState) -> Array[TextElem] {
  let out : Array[TextElem] = []
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => out.push(text)
      _ => ()
    }
  }
  out
}

///|
pub fn TextState::raw_elems(self : TextState) -> Array[RichtextElem] {
  let out : Array[RichtextElem] = []
  for elem in self.elems {
    out.push(elem)
  }
  out
}

///|
pub fn TextState::styles(self : TextState) -> Array[StyleSpan] {
  let spans : Array[StyleSpan] = []
  let active : Map[String, Array[StyleOp]] = {}
  let mut current : Map[String, StyleOp] = {}
  let open : Map[String, (Int, StyleOp)] = {}
  let mut text_pos = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Anchor(style~, anchor~) => {
        match active.get(style.key) {
          Some(list) =>
            if anchor == AnchorType::Start {
              list.push(style)
            } else {
              let mut idx = 0
              while idx < list.length() {
                if style_op_same_id(list[idx], style) {
                  ignore(list.remove(idx))
                  break
                }
                idx = idx + 1
              }
            }
          None => if anchor == AnchorType::Start { active[style.key] = [style] }
        }
        continue
      }
      RichtextElem::Text(_) => ()
    }
    let next : Map[String, StyleOp] = {}
    for key, list in active {
      match best_style(list) {
        Some(best) => if !value_is_null(best.value) { next[key] = best }
        None => ()
      }
    }
    let keys : Array[String] = []
    for key, _ in current {
      keys.push(key)
    }
    for key, _ in next {
      if !keys.contains(key) {
        keys.push(key)
      }
    }
    for key in keys {
      let old = current.get(key)
      let new = next.get(key)
      let same = match (old, new) {
        (Some(a), Some(b)) => style_op_same_id(a, b)
        (None, None) => true
        _ => false
      }
      if same {
        continue
      }
      match old {
        Some(_) =>
          match open.get(key) {
            Some((start, op)) =>
              if start < text_pos {
                spans.push(StyleSpan::{
                  start,
                  end: text_pos,
                  key,
                  value: op.value,
                  expand: @types.ExpandType::from_info(op.info),
                })
              }
            None => ()
          }
        None => ()
      }
      match new {
        Some(next_op) => open[key] = (text_pos, next_op)
        None => ignore(open.remove(key))
      }
    }
    current = next
    text_pos = text_pos + 1
  }
  for key, entry in open {
    let (start, op) = entry
    if start < text_pos && !value_is_null(op.value) {
      spans.push(StyleSpan::{
        start,
        end: text_pos,
        key,
        value: op.value,
        expand: @types.ExpandType::from_info(op.info),
      })
    }
  }
  spans
}

///|
pub fn TextState::append_elem(
  self : TextState,
  ch : Char,
  id : @types.IdFull,
) -> Unit {
  self.elems.push(RichtextElem::Text(TextElem::{ ch, id }))
}

///|
pub fn TextState::append_anchor(
  self : TextState,
  style : StyleOp,
  anchor : AnchorType,
) -> Unit {
  self.elems.push(RichtextElem::Anchor(style~, anchor~))
}

///|
pub fn TextState::plain_text(self : TextState) -> String {
  let sb = StringBuilder::new()
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => sb.write_char(text.ch)
      _ => ()
    }
  }
  sb.to_string()
}

///|
fn text_elem_id_at(base : @types.IdFull, offset : Int) -> @types.IdFull {
  @types.IdFull::new(
    base.peer,
    base.counter + offset,
    base.lamport + offset.reinterpret_as_uint(),
  )
}

///|
fn utf8_len(ch : Char) -> Int {
  let code = ch.to_int()
  if code <= 0x7f {
    1
  } else if code <= 0x7ff {
    2
  } else if code <= 0xffff {
    3
  } else {
    4
  }
}

///|
pub fn TextState::len(self : TextState) -> Int {
  let mut count = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(_) => count = count + 1
      _ => ()
    }
  }
  count
}

///|
pub fn TextState::id_at(
  self : TextState,
  index : Int,
) -> Result[@types.IdFull, @types.LoroError] {
  if index < 0 {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  let mut text_idx = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => {
        if text_idx == index {
          return Ok(text.id)
        }
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  Err(@types.LoroError::InternalError("index out of range"))
}

///|
pub fn TextState::ids_in_range(
  self : TextState,
  start : Int,
  end : Int,
) -> Result[Array[@types.IdFull], @types.LoroError] {
  if start < 0 || end < start {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  let out : Array[@types.IdFull] = []
  let mut text_idx = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => {
        if text_idx >= start && text_idx < end {
          out.push(text.id)
        }
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  if end > text_idx {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  Ok(out)
}

///|
pub fn TextState::slice(
  self : TextState,
  start : Int,
  end : Int,
) -> Result[String, @types.LoroError] {
  if start < 0 || end < start {
    return Err(@types.LoroError::InternalError("slice out of range"))
  }
  let sb = StringBuilder::new()
  let mut text_idx = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => {
        if text_idx >= start && text_idx < end {
          sb.write_char(text.ch)
        }
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  if end > text_idx {
    return Err(@types.LoroError::InternalError("slice out of range"))
  }
  Ok(sb.to_string())
}

///|
pub fn TextState::unicode_index_from_utf16(
  self : TextState,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  if offset < 0 {
    return Err(@types.LoroError::InternalError("utf16 offset out of range"))
  }
  let mut count = 0
  let mut text_idx = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => {
        if count == offset {
          return Ok(text_idx)
        }
        let len = text.ch.utf16_len()
        if count + len > offset {
          return Err(
            @types.LoroError::InternalError("utf16 offset in middle of char"),
          )
        }
        count = count + len
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  if count == offset {
    Ok(text_idx)
  } else {
    Err(@types.LoroError::InternalError("utf16 offset out of range"))
  }
}

///|
pub fn TextState::unicode_index_from_utf8(
  self : TextState,
  offset : Int,
) -> Result[Int, @types.LoroError] {
  if offset < 0 {
    return Err(@types.LoroError::InternalError("utf8 offset out of range"))
  }
  let mut count = 0
  let mut text_idx = 0
  for elem in self.elems {
    match elem {
      RichtextElem::Text(text) => {
        if count == offset {
          return Ok(text_idx)
        }
        let len = utf8_len(text.ch)
        if count + len > offset {
          return Err(
            @types.LoroError::InternalError("utf8 offset in middle of char"),
          )
        }
        count = count + len
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  if count == offset {
    Ok(text_idx)
  } else {
    Err(@types.LoroError::InternalError("utf8 offset out of range"))
  }
}

///|
pub fn TextState::insert(
  self : TextState,
  pos : Int,
  text : String,
  base_id : @types.IdFull,
) -> Unit {
  if pos < 0 || pos > self.elems.length() {
    return
  }
  let mut offset = 0
  for ch in text {
    let id = text_elem_id_at(base_id, offset)
    self.elems.insert(pos + offset, RichtextElem::Text(TextElem::{ ch, id }))
    offset = offset + 1
  }
}

///|
fn TextState::insert_pos_with_vv(
  self : TextState,
  pos : Int,
  vv : @types.VersionVector,
  new_id : @types.IdFull,
) -> Int? {
  if pos < 0 {
    return None
  }
  let mut visible_count = 0
  let mut last_visible = -1
  let mut idx = 0
  let mut gap_start = 0
  let mut gap_end = self.elems.length()
  let mut found_gap = false
  while idx < self.elems.length() {
    let elem = self.elems[idx]
    if text_elem_visible_in_vv(vv, elem) {
      if visible_count == pos {
        gap_start = last_visible + 1
        gap_end = idx
        found_gap = true
        break
      }
      visible_count = visible_count + 1
      last_visible = idx
    }
    idx = idx + 1
  }
  if !found_gap {
    if visible_count == pos {
      gap_start = last_visible + 1
      gap_end = self.elems.length()
    } else {
      return None
    }
  }
  let mut insert_idx = gap_start
  while insert_idx < gap_end {
    let elem = self.elems[insert_idx]
    let elem_id = text_elem_order_id(elem)
    if id_full_cmp(elem_id, new_id) < 0 {
      insert_idx = insert_idx + 1
    } else {
      break
    }
  }
  Some(insert_idx)
}

///|
pub fn TextState::insert_with_vv(
  self : TextState,
  pos : Int,
  text : String,
  base_id : @types.IdFull,
  vv : @types.VersionVector,
) -> Unit {
  let insert_at = match self.insert_pos_with_vv(pos, vv, base_id) {
    Some(value) => value
    None => return
  }
  let mut offset = 0
  for ch in text {
    let id = text_elem_id_at(base_id, offset)
    self.elems.insert(
      insert_at + offset,
      RichtextElem::Text(TextElem::{ ch, id }),
    )
    offset = offset + 1
  }
}

///|
pub fn TextState::delete(self : TextState, start : Int, end : Int) -> Unit {
  if start < 0 || end < start {
    return
  }
  let mut text_idx = 0
  let kept : Array[RichtextElem] = []
  for elem in self.elems {
    match elem {
      RichtextElem::Text(_) => {
        if text_idx < start || text_idx >= end {
          kept.push(elem)
        }
        text_idx = text_idx + 1
      }
      _ => kept.push(elem)
    }
  }
  if end > text_idx {
    return
  }
  self.elems.clear()
  self.elems.append(kept[:])
}

///|
pub fn TextState::delete_by_id(
  self : TextState,
  span : @op.DeleteSpanWithId,
) -> Unit {
  let id_start = span.id_start()
  let len = span.content_len()
  if len <= 0 {
    return
  }
  let start_ctr = id_start.counter
  let end_ctr = start_ctr + len
  let peer = id_start.peer
  let kept : Array[RichtextElem] = []
  for elem in self.elems {
    let elem_id = match elem {
      RichtextElem::Text(text) => text.id
      RichtextElem::Anchor(style~, anchor~) => anchor_id(style, anchor)
    }
    if elem_id.peer == peer &&
      elem_id.counter >= start_ctr &&
      elem_id.counter < end_ctr {
      continue
    }
    kept.push(elem)
  }
  self.elems.clear()
  self.elems.append(kept[:])
}

///|
fn attrs_from_active(
  active : Map[String, Array[StyleOp]],
) -> Map[String, @types.LoroValue] {
  let attrs : Map[String, @types.LoroValue] = {}
  for key, list in active {
    match best_style(list) {
      Some(best) => if !value_is_null(best.value) { attrs[key] = best.value }
      None => ()
    }
  }
  attrs
}

///|
pub fn TextState::to_richtext_value(self : TextState) -> @types.LoroValue {
  let deltas : Array[@types.LoroValue] = []
  let active : Map[String, Array[StyleOp]] = {}
  let mut sb = StringBuilder::new()
  let mut has_text = false
  let mut current_attrs : Map[String, @types.LoroValue]? = None
  for elem in self.elems {
    match elem {
      RichtextElem::Anchor(style~, anchor~) =>
        match active.get(style.key) {
          Some(list) =>
            if anchor == AnchorType::Start {
              list.push(style)
            } else {
              let mut idx = 0
              while idx < list.length() {
                if style_op_same_id(list[idx], style) {
                  ignore(list.remove(idx))
                  break
                }
                idx = idx + 1
              }
            }
          None => if anchor == AnchorType::Start { active[style.key] = [style] }
        }
      RichtextElem::Text(text) => {
        let next_attrs = attrs_from_active(active)
        match current_attrs {
          Some(attrs) =>
            if map_eq(attrs, next_attrs) {
              sb.write_char(text.ch)
              has_text = true
            } else {
              if has_text {
                deltas.push(richtext_delta(sb.to_string(), attrs))
              }
              sb = StringBuilder::new()
              has_text = false
              current_attrs = Some(next_attrs)
              sb.write_char(text.ch)
              has_text = true
            }
          None => {
            current_attrs = Some(next_attrs)
            sb.write_char(text.ch)
            has_text = true
          }
        }
      }
    }
  }
  match current_attrs {
    Some(attrs) =>
      if has_text {
        deltas.push(richtext_delta(sb.to_string(), attrs))
      }
    None => ()
  }
  @types.LoroValue::List(deltas)
}

///|
fn richtext_delta(
  text : String,
  attrs : Map[String, @types.LoroValue],
) -> @types.LoroValue {
  let map : Map[String, @types.LoroValue] = {}
  map["insert"] = @types.LoroValue::String(text)
  if attrs.length() > 0 {
    let attributes : Map[String, @types.LoroValue] = {}
    for k, v in attrs {
      attributes[k] = v
    }
    map["attributes"] = @types.LoroValue::Map(attributes)
  }
  @types.LoroValue::Map(map)
}

///|
fn TextState::entity_index_of_text_index(
  self : TextState,
  target : Int,
) -> Int? {
  if target < 0 {
    return None
  }
  let mut text_idx = 0
  for i = 0; i < self.elems.length(); i = i + 1 {
    match self.elems[i] {
      RichtextElem::Text(_) => {
        if text_idx == target {
          return Some(i)
        }
        text_idx = text_idx + 1
      }
      _ => ()
    }
  }
  None
}

///|
fn TextState::find_best_insert_pos(
  self : TextState,
  left : Int,
  right : Int,
) -> Int {
  let mut iter = left
  let mut entity_index = left
  let visited : Array[(StyleOp, AnchorType, Int)] = []
  while iter < right {
    let elem = self.elems[iter]
    match elem {
      RichtextElem::Anchor(style~, anchor~) => {
        visited.push((style, anchor, iter))
        if anchor == AnchorType::Start &&
          (!value_is_null(style.value) || !value_is_false(style.value)) {
          break
        }
        if prefer_insert_before(style.info, anchor) {
          break
        }
      }
      _ => ()
    }
    iter = iter + 1
    entity_index = entity_index + 1
  }
  while visited.length() > 0 {
    let idx = visited.length() - 1
    let (style, anchor, pos) = visited[idx]
    ignore(visited.remove(idx))
    if !prefer_insert_before(style.info, anchor) {
      break
    }
    iter = pos
    entity_index = pos
  }
  entity_index
}

///|
pub fn TextState::text_pos_to_list_pos(self : TextState, text_pos : Int) -> Int {
  let max_pos = self.len()
  let mut pos = text_pos
  if pos < 0 {
    pos = 0
  } else if pos > max_pos {
    pos = max_pos
  }
  if self.elems.length() == 0 {
    return 0
  }
  if pos == 0 {
    let mut right = 0
    while right < self.elems.length() {
      match self.elems[right] {
        RichtextElem::Text(_) => break
        _ => right = right + 1
      }
    }
    if right == 0 {
      return 0
    }
    return self.find_best_insert_pos(0, right)
  }
  let prev_entity = match self.entity_index_of_text_index(pos - 1) {
    Some(v) => v
    None => return self.elems.length()
  }
  let left = prev_entity + 1
  if left >= self.elems.length() {
    return left
  }
  let mut right = left
  while right < self.elems.length() {
    match self.elems[right] {
      RichtextElem::Text(_) => break
      _ => right = right + 1
    }
  }
  if left == right {
    left
  } else {
    self.find_best_insert_pos(left, right)
  }
}

///|
pub fn TextState::text_index_to_list_pos(
  self : TextState,
  text_pos : Int,
) -> Result[Int, @types.LoroError] {
  if text_pos < 0 {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  let len = self.len()
  if text_pos == len {
    return Ok(self.elems.length())
  }
  if text_pos > len {
    return Err(@types.LoroError::InternalError("index out of range"))
  }
  match self.entity_index_of_text_index(text_pos) {
    Some(pos) => Ok(pos)
    None => Err(@types.LoroError::InternalError("index out of range"))
  }
}

///|
pub fn TextState::list_pos_to_text_pos(self : TextState, list_pos : Int) -> Int {
  if list_pos <= 0 {
    return 0
  }
  let max_list_pos = self.elems.length()
  if list_pos >= max_list_pos {
    return self.len()
  }
  let mut count = 0
  let mut idx = 0
  while idx < list_pos {
    match self.elems[idx] {
      RichtextElem::Text(_) => count = count + 1
      _ => ()
    }
    idx = idx + 1
  }
  count
}

///|
pub fn TextState::apply_inner_op(
  self : TextState,
  op : @op.InnerListOp,
  base_id : @types.IdFull,
) -> Result[Unit, @types.LoroError] {
  match op {
    @op.InnerListOp::InsertText(str~, unicode_len=_, pos~, unicode_start=_) => {
      self.insert(pos, str, base_id)
      Ok(())
    }
    @op.InnerListOp::Delete(span) => {
      self.delete_by_id(span)
      Ok(())
    }
    @op.InnerListOp::StyleStart(start~, end~, key~, info~, value~) => {
      if start < 0 || end < start {
        return Ok(())
      }
      let style = StyleOp::{ id: base_id, key, value, info }
      if end <= self.elems.length() {
        self.elems.insert(
          end,
          RichtextElem::Anchor(style~, anchor=AnchorType::End),
        )
      }
      if start <= self.elems.length() {
        self.elems.insert(
          start,
          RichtextElem::Anchor(style~, anchor=AnchorType::Start),
        )
      }
      Ok(())
    }
    @op.InnerListOp::StyleEnd => Ok(())
    _ => Err(@types.LoroError::Unsupported("text op not supported"))
  }
}

///|
pub fn TextState::apply_inner_op_with_vv(
  self : TextState,
  op : @op.InnerListOp,
  base_id : @types.IdFull,
  vv : @types.VersionVector,
) -> Result[Unit, @types.LoroError] {
  match op {
    @op.InnerListOp::InsertText(str~, unicode_len=_, pos~, unicode_start=_) => {
      self.insert_with_vv(pos, str, base_id, vv)
      Ok(())
    }
    @op.InnerListOp::Delete(span) => {
      self.delete_by_id(span)
      Ok(())
    }
    @op.InnerListOp::StyleStart(start~, end~, key~, info~, value~) => {
      if start < 0 || end < start {
        return Ok(())
      }
      let style = StyleOp::{ id: base_id, key, value, info }
      let end_pos = self.insert_pos_with_vv(
        end,
        vv,
        anchor_id(style, AnchorType::End),
      )
      let start_pos = self.insert_pos_with_vv(
        start,
        vv,
        anchor_id(style, AnchorType::Start),
      )
      match end_pos {
        Some(pos) =>
          if pos <= self.elems.length() {
            self.elems.insert(
              pos,
              RichtextElem::Anchor(style~, anchor=AnchorType::End),
            )
          }
        None => ()
      }
      match start_pos {
        Some(pos) =>
          if pos <= self.elems.length() {
            self.elems.insert(
              pos,
              RichtextElem::Anchor(style~, anchor=AnchorType::Start),
            )
          }
        None => ()
      }
      Ok(())
    }
    @op.InnerListOp::StyleEnd => Ok(())
    _ => Err(@types.LoroError::Unsupported("text op not supported"))
  }
}