///|
/// Match Quill Delta's fast-diff 1.3.0 text operation mapping. Embeds are
/// represented by NUL placeholders for text alignment and then compared as
/// full JSON payloads in equal regions.
fn Delta::diff_fast(self : Delta, target : Delta) -> Delta {
  let old_text = self.diff_placeholder_text()
  let new_text = target.diff_placeholder_text()
  let edits = fast_diff_text(old_text, new_text)
  let old_iter = OpIterator::new(self.ops)
  let new_iter = OpIterator::new(target.ops)
  let result = Delta::new()
  for edit in edits {
    let mut remaining = edit.text.length()
    while remaining > 0 {
      if edit.kind == 1 {
        let available = new_iter.peek_length()
        let amount = if available < remaining { available } else { remaining }
        ignore(result.push(new_iter.next(amount)))
        remaining -= amount
      } else if edit.kind == -1 {
        let available = old_iter.peek_length()
        let amount = if available < remaining { available } else { remaining }
        ignore(old_iter.next(amount))
        ignore(result.delete(amount))
        remaining -= amount
      } else {
        let old_available = old_iter.peek_length()
        let new_available = new_iter.peek_length()
        let mut amount = if old_available < new_available {
          old_available
        } else {
          new_available
        }
        if remaining < amount {
          amount = remaining
        }
        let old_op = old_iter.next(amount)
        let new_op = new_iter.next(amount)
        if same_insert_content(old_op, new_op) {
          ignore(
            result.retain(
              amount,
              attributes_diff(old_op.attributes(), new_op.attributes()),
            ),
          )
        } else {
          ignore(result.push(new_op))
          ignore(result.delete(amount))
        }
        remaining -= amount
      }
    }
  }
  result.chop()
}

///|
fn Delta::diff_placeholder_text(self : Delta) -> String {
  let buffer = StringBuilder()
  for op in self.ops {
    match op {
      InsertText(text, _) => buffer.write_string(text)
      InsertEmbed(_, _) => buffer.write_char('\u{0000}')
      _ => ()
    }
  }
  buffer.to_string()
}

///|
fn same_insert_content(left : Op, right : Op) -> Bool {
  match (left, right) {
    (InsertText(a, _), InsertText(b, _)) => a == b
    (InsertEmbed(a, _), InsertEmbed(b, _)) => a == b
    _ => false
  }
}