///|
pub(all) struct UpgradePlan {
  payload_manifests : Map[String, String]
  tag_algorithms : Array[Algorithm]
} derive(@debug.Debug)

///|
/// Refuses invalid sources. The caller must rehash all tags after writing the plan.
pub fn plan_upgrade(
  view : BagView,
  algorithms : Array[Algorithm],
) -> UpgradePlan raise FormatError {
  checked_algorithms(algorithms)
  guard validate(view).valid else {
    raise Invalid("cannot upgrade an invalid or incomplete bag")
  }
  let payload = view.files.filter(f => f.path.has_prefix("data/"))
  let payload_manifests : Map[String, String] = Map([])
  let tag_algorithms : Array[Algorithm] = []
  for file in view.files {
    if manifest_name(file.path, true) {
      let manifest = parse_manifest(
        file.path,
        view.texts.get(file.path).unwrap(),
      )
      if !tag_algorithms.contains(manifest.algorithm) {
        tag_algorithms.push(manifest.algorithm)
      }
    }
  }
  for alg in algorithms {
    let name = "manifest-" + alg.name() + ".txt"
    if !view.files.any(f => f.path == name) {
      payload_manifests[name] = render_manifest(record_entries(payload, alg))
    }
    if !tag_algorithms.contains(alg) {
      tag_algorithms.push(alg)
    }
  }
  { payload_manifests, tag_algorithms, }
}