///|
/// Transform `other` against this Delta. With `priority = true`, this Delta
/// wins when both changes insert at the same position or format the same key.
pub fn Delta::transform(
  self : Delta,
  other : Delta,
  priority : Bool,
) -> Delta raise DeltaError {
  self.transform_with_handlers(other, priority, default_embed_handlers)
}

///|
/// Transform concurrent changes using registered embed payload algorithms.
pub fn Delta::transform_with_handlers(
  self : Delta,
  other : Delta,
  priority : Bool,
  handlers : EmbedHandlers,
) -> Delta raise DeltaError {
  let left = OpIterator::new(self.ops)
  let right = OpIterator::new(other.ops)
  let result = Delta::new()
  while left.has_next() || right.has_next() {
    if left.peek_kind() == "insert" &&
      (priority || right.peek_kind() != "insert") {
      ignore(result.retain_plain(left.next(left.peek_length()).length()))
      continue
    }
    if right.peek_kind() == "insert" {
      ignore(result.push(right.next(right.peek_length())))
      continue
    }
    let a_length = left.peek_length()
    let b_length = right.peek_length()
    let amount = if a_length < b_length { a_length } else { b_length }
    let a = left.next(amount)
    let b = right.next(amount)
    if a.kind() == "delete" {
      continue
    }
    match b {
      Delete(_) => ignore(result.delete(amount))
      RetainEmbed(embed, attrs) => {
        let mut transformed_embed = embed
        match a {
          RetainEmbed(own_embed, _) => {
            let parts = embed_parts(own_embed, embed) catch {
              _ => ("", Json::null(), Json::null())
            }
            let (kind, own_data, other_data) = parts
            if kind != "" {
              let handler = handlers.require(kind)
              transformed_embed = wrap_embed(
                kind,
                (handler.transform_value)(own_data, other_data, priority),
              )
            }
          }
          _ => ()
        }
        let transformed = attributes_transform(a.attributes(), attrs, priority)
        ignore(result.retain_embed(transformed_embed, transformed))
      }
      Retain(_, attrs) => {
        let transformed = attributes_transform(a.attributes(), attrs, priority)
        ignore(result.retain(amount, transformed))
      }
      _ => ()
    }
  }
  result.chop()
}

///|
/// Rebase a UTF-16 cursor position through this change.
pub fn Delta::transform_position(
  self : Delta,
  position : Int32,
  priority : Bool,
) -> Int32 {
  let mut index = position
  let mut offset = 0
  for op in self.ops {
    if offset > index {
      break
    }
    match op {
      InsertText(_, _) | InsertEmbed(_, _) => {
        if offset < index || (offset == index && !priority) {
          index += op.length()
        }
        offset += op.length()
      }
      Delete(length) =>
        if offset < index {
          let removable = index - offset
          index -= if removable < length { removable } else { length }
        }
      Retain(_, _) | RetainEmbed(_, _) => offset += op.length()
    }
  }
  index
}