///|
/// Internal cursor over operations. It splits text using UTF-16 offsets, the
/// same unit used by Quill's JavaScript implementation.
pub struct OpIterator {
  ops : Array[Op]
  mut index : Int32
  mut offset : Int32
}

///|
pub fn OpIterator::new(ops : Array[Op]) -> OpIterator {
  { ops, index: 0, offset: 0, }
}

///|
pub fn OpIterator::has_next(self : OpIterator) -> Bool {
  self.index < self.ops.length()
}

///|
pub fn OpIterator::peek(self : OpIterator) -> Op? {
  if self.has_next() {
    Some(self.ops[self.index])
  } else {
    None
  }
}

///|
pub fn OpIterator::peek_length(self : OpIterator) -> Int32 {
  if self.has_next() {
    self.ops[self.index].length() - self.offset
  } else {
    2147483647
  }
}

///|
pub fn OpIterator::peek_kind(self : OpIterator) -> String {
  if self.has_next() {
    self.ops[self.index].kind()
  } else {
    "retain"
  }
}

///|
pub fn OpIterator::next(self : OpIterator, requested : Int32) -> Op {
  if !self.has_next() {
    return Retain(2147483647, empty_attributes())
  }
  let op = self.ops[self.index]
  let remaining = op.length() - self.offset
  let amount = if requested == 0 || requested >= remaining {
    remaining
  } else {
    requested
  }
  let start = self.offset
  if amount == remaining {
    self.index += 1
    self.offset = 0
  } else {
    self.offset += amount
  }
  match op {
    InsertText(text, attrs) =>
      InsertText(utf16_substring(text, start, amount), attrs)
    InsertEmbed(embed, attrs) => InsertEmbed(embed, attrs)
    Retain(_, attrs) => Retain(amount, attrs)
    RetainEmbed(embed, attrs) => RetainEmbed(embed, attrs)
    Delete(_) => Delete(amount)
  }
}

///|
/// Preserve isolated surrogate halves when an operation boundary falls inside
/// an emoji. JavaScript's substr() can return such halves; StringView::to_owned
/// does not preserve them on every MoonBit backend.
fn utf16_substring(text : String, start : Int32, length : Int32) -> String {
  if start == 0 && length == text.length() {
    return text
  }
  let units : Array[Char] = []
  for index in start..<(start + length) {
    units.push(text.at(index).to_int().unsafe_to_char())
  }
  String::from_array(units)
}

///|
pub fn OpIterator::rest(self : OpIterator) -> Array[Op] {
  let out : Array[Op] = []
  let cursor : OpIterator = {
    ops: self.ops,
    index: self.index,
    offset: self.offset,
  }
  while cursor.has_next() {
    out.push(cursor.next(cursor.peek_length()))
  }
  out
}

///|
pub fn Delta::slice(self : Delta, start : Int32, end : Int32) -> Delta {
  let result : Array[Op] = []
  let iter = OpIterator::new(self.ops)
  let mut index = 0
  while index < end && iter.has_next() {
    let amount = if index < start { start - index } else { end - index }
    let op = iter.next(amount)
    if index >= start {
      result.push(op)
    }
    index += op.length()
  }
  Delta::from_ops_raw(result)
}

///|
/// Return a cursor over a copy of the operation list.
pub fn Delta::iterator(self : Delta) -> OpIterator {
  OpIterator::new(self.operations())
}

///|
/// Slice from a UTF-16 offset through the end, corresponding to Quill's
/// slice(start) overload.
pub fn Delta::slice_from(self : Delta, start : Int32) -> Delta {
  self.slice(start, self.length())
}

///|
/// Return the whole Delta as a separate value, corresponding to slice().
pub fn Delta::slice_all(self : Delta) -> Delta {
  self.slice(0, self.length())
}

///|
/// Consume the remaining part of the next operation.
pub fn OpIterator::next_full(self : OpIterator) -> Op {
  self.next(self.peek_length())
}