///|
fn patch_kind(info : @bit_apply.PatchInfo) -> ChangeKind {
  if info.is_rename {
    Rename
  } else if info.is_copy {
    Copy
  } else if info.is_new {
    Add
  } else if info.is_delete {
    Delete
  } else {
    Modify
  }
}

///|
fn patch_paths(
  info : @bit_apply.PatchInfo,
  kind : ChangeKind,
) -> (String?, String?) {
  match kind {
    Add => (None, Some(info.new_name))
    Delete => (Some(info.old_name), None)
    Modify => {
      let path = if info.new_name.is_empty() {
        info.old_name
      } else {
        info.new_name
      }
      (Some(path), Some(path))
    }
    Rename | Copy => (Some(info.old_name), Some(info.new_name))
  }
}

///|
pub fn changes_from_unified_diff(
  source : String,
) -> Result[Array[Change], Diagnostic] {
  if source.is_empty() || source.length() > 16777216 {
    return Err(
      Diagnostic::new(
        "diff.input.limit",
        "diff",
        "diff is empty or exceeds the adapter limit",
        "1 through 16777216 characters",
        source.length().to_string(),
      ),
    )
  }
  let patches = @bit_apply.parse_patches(source)
  if patches.is_empty() {
    return Err(
      Diagnostic::new(
        "diff.patch.none", "diff", "upstream parser found no file patches", "at least one diff header",
        "none",
      ),
    )
  }
  let changes : Array[Change] = []
  for index, info in patches {
    let kind = patch_kind(info)
    let (old_path, new_path) = patch_paths(info, kind)
    let change = match
      Change::new(kind, old_path, new_path, info.additions, info.deletions) {
      Ok(value) => value
      Err(error) =>
        return Err(
          Diagnostic::new(
            "diff.patch.invalid",
            "patches[" + index.to_string() + "]",
            "upstream patch metadata cannot form a safe MoonChange change",
            error.expected(),
            error.actual(),
          ),
        )
    }
    changes.push(change)
  }
  Ok(changes)
}

///|
pub fn change_set_from_unified_diff(
  id : String,
  evidence : Evidence,
  source : String,
) -> Result[ChangeSet, Diagnostic] {
  let changes = match changes_from_unified_diff(source) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  ChangeSet::new(id, changes, evidence)
}