///|
/// A scalar-range patch against an exact source snapshot. Expected text prevents
/// stale editor state from silently changing a different token.
pub(all) struct SourceEdit {
  span : Span
  expected : String
  replacement : String
}

///|
pub suberror EditError {
  InvalidEdit(Int32)
  UnorderedOrOverlappingEdit(Int32)
  StaleEdit(Int32)
}

///|
/// Apply caller-ordered, nonoverlapping patches atomically. No partial result is
/// returned on failure. Two edits at the same start are deliberately ambiguous.
pub fn apply_edits(
  source : String,
  edits : Array[SourceEdit],
) -> String raise EditError {
  let map = SourceMap::new(source)
  let mut previous_end = 0
  let mut previous_start = -1
  for i, edit in edits {
    let { start, end, } = edit.span
    if start < 0 || end < start || end > map.length() {
      raise InvalidEdit(i)
    }
    if start < previous_end || start <= previous_start {
      raise UnorderedOrOverlappingEdit(i)
    }
    let actual = map.slice(edit.span) catch { _ => raise InvalidEdit(i) }
    if actual != edit.expected {
      raise StaleEdit(i)
    }
    previous_start = start
    previous_end = end
  }
  let out = StringBuilder()
  let mut cursor = 0
  for i, edit in edits {
    let prefix = map.slice({ start: cursor, end: edit.span.start, }) catch {
      _ => raise InvalidEdit(i)
    }
    out.write_string(prefix)
    out.write_string(edit.replacement)
    cursor = edit.span.end
  }
  let tail = map.slice({ start: cursor, end: map.length(), }) catch {
    _ => raise InvalidEdit(edits.length())
  }
  out.write_string(tail)
  out.to_string()
}