///|
/// One named canonical document in a reproducible digest manifest.
pub(all) struct DigestEntry {
  name : String
  digest : String
  byte_length : Int
}

///|
pub fn digest_batch(
  documents : Array[(String, String)],
) -> Result[Array[DigestEntry], String] {
  let entries : Array[DigestEntry] = []
  for document in documents {
    let prepared = match prepare_document(document.1) {
      Ok(prepared) => prepared
      Err(error) => return Err(document.0 + ": " + error)
    }
    entries.push({
      name: document.0,
      digest: prepared.digest,
      byte_length: prepared.byte_length,
    })
  }
  sort_digest_entries(entries)
  Ok(entries)
}

///|
/// Build a canonical JSON array describing a named document set. Sorting by
/// name makes the manifest independent of filesystem traversal order.
pub fn digest_manifest(
  documents : Array[(String, String)],
) -> Result[String, String] {
  let entries = match digest_batch(documents) {
    Ok(entries) => entries
    Err(error) => return Err(error)
  }
  let mut output = "["
  let mut first = true
  for entry in entries {
    if !first {
      output = output + ","
    }
    first = false
    output = output +
      "{\"name\":" +
      canonical_string(entry.name) +
      ",\"sha256\":" +
      canonical_string(entry.digest) +
      ",\"byte_length\":" +
      entry.byte_length.to_string() +
      "}"
  }
  let raw = output + "]"
  let value = match parse(raw) {
    Ok(value) => value
    Err(error) => return Err(error.message())
  }
  match canonicalize(value) {
    Ok(canonical) => Ok(canonical)
    Err(error) => Err(error.message())
  }
}

///|
pub fn verify_digest(input : String, expected : String) -> Result[Bool, String] {
  match digest_json(input) {
    Ok(actual) => Ok(actual == expected)
    Err(error) => Err(error)
  }
}

///|
/// Verify a manifest by recomputing every named document and comparing the
/// canonical manifest bytes, including deterministic entry ordering.
pub fn verify_manifest(
  manifest : String,
  documents : Array[(String, String)],
) -> Result[Bool, String] {
  let expected = match digest_manifest(documents) {
    Ok(expected) => expected
    Err(error) => return Err(error)
  }
  let parsed = match parse(manifest) {
    Ok(value) => value
    Err(error) => return Err(error.message())
  }
  let actual = match canonicalize(parsed) {
    Ok(actual) => actual
    Err(error) => return Err(error.message())
  }
  Ok(actual == expected)
}

///|
pub fn manifest_digest(
  documents : Array[(String, String)],
) -> Result[String, String] {
  let manifest = match digest_manifest(documents) {
    Ok(manifest) => manifest
    Err(error) => return Err(error)
  }
  match digest_json(manifest) {
    Ok(digest) => Ok(digest)
    Err(error) => Err(error)
  }
}

///|
fn sort_digest_entries(entries : Array[DigestEntry]) -> Unit {
  let mut index = 1
  while index < entries.length() {
    let current = entries[index]
    let mut cursor = index
    while cursor > 0 && entries[cursor - 1].name > current.name {
      entries[cursor] = entries[cursor - 1]
      cursor = cursor - 1
    }
    entries[cursor] = current
    index = index + 1
  }
}