///|
priv struct VolumeMetadata {
  document : Document
  active : Bool
  source : Json
  compression : Map[String, Json]
  diffs : Map[Int32, Json]
  commits : Map[Int32, Json]
  has_git : Bool
}

///|
/// Bind Git records by kind/order, never by title or the files in a volume.
fn volume_metadata(document : Document) -> VolumeMetadata raise {
  let compression : Map[String, Json] = Map([])
  let diffs : Map[Int32, Json] = Map([])
  let commits : Map[Int32, Json] = Map([])
  if document.metadata == Json::null() {
    return {
      document,
      active: false,
      source: Json::null(),
      compression,
      diffs,
      commits,
      has_git: false,
    }
  }
  guard document.metadata is Object(fields) else { raise ConfigurationError }
  let source = fields.get("source").unwrap_or(Json::null())
  let records = match fields.get("compression") {
    None => []
    Some(Array(records)) => records
    _ => raise ConfigurationError
  }
  let paths : Set[String] = Set([])
  for file in document.files {
    paths.add(file.path)
  }
  for record in records {
    guard record is { "path": String(path), .. } else {
      raise ConfigurationError
    }
    if !paths.contains(path) || compression.contains(path) {
      raise ConfigurationError
    }
    compression[path] = record
  }
  let (diff_records, commit_records, has_git) = match fields.get("git") {
    None | Some(Null) => ([], [], false)
    Some({ "diffs": Array(diff_records), "commits": Array(commit_records), .. }) =>
      (diff_records, commit_records, true)
    _ => raise ConfigurationError
  }
  let mut next_diff = 0
  let mut next_commit = 0
  for index, section in document.sections {
    match section.kind {
      "git_diff" => {
        if next_diff >= diff_records.length() {
          raise ConfigurationError
        }
        diffs[index] = diff_records[next_diff]
        next_diff += 1
      }
      "git_log" => {
        if next_commit >= commit_records.length() {
          raise ConfigurationError
        }
        commits[index] = commit_records[next_commit]
        next_commit += 1
      }
      _ => ()
    }
  }
  if next_diff != diff_records.length() ||
    next_commit != commit_records.length() {
    raise ConfigurationError
  }
  { document, active: true, source, compression, diffs, commits, has_git, }
}

///|
fn VolumeMetadata::project(
  self : VolumeMetadata,
  file_indices : Array[Int32],
  section_indices : Array[Int32],
) -> Json raise {
  if !self.active {
    return Json::null()
  }
  let compression : Array[Json] = []
  let diffs : Array[Json] = []
  let commits : Array[Json] = []
  for index in file_indices {
    if index < 0 || index >= self.document.files.length() {
      raise ConfigurationError
    }
    let path = self.document.files[index].path
    if self.compression.get(path) is Some(record) {
      compression.push(record)
    }
  }
  for index in section_indices {
    if index < 0 || index >= self.document.sections.length() {
      raise ConfigurationError
    }
    if self.diffs.get(index) is Some(record) {
      diffs.push(record)
    }
    if self.commits.get(index) is Some(record) {
      commits.push(record)
    }
  }
  {
    "source": self.source,
    "compression": compression,
    "git": if self.has_git {
      { "diffs": diffs, "commits": commits }
    } else {
      Json::null()
    },
  }
}