/// Manifest construction, canonical encoding and lookup helpers.

pub fn manifest_from_chunks(name : String, chunks : Array[Chunk]) -> Manifest {
  let refs = []
  for chunk in chunks {
    refs.push(ChunkRef::new(chunk.index(), chunk.id(), chunk.size()))
  }
  Manifest::new(name, refs)
}

pub fn manifest_from_digests(name : String, digests : Array[String], sizes : Array[Int]) -> Result[Manifest, String] {
  if digests.length() != sizes.length() {
    return Err("digest and size arrays must have the same length")
  }
  let refs = []
  for i in 0.. ChunkRef? {
  for reference in manifest.references() {
    if digest_equal(reference.id(), digest) { return Some(reference) }
  }
  None
}

pub fn manifest_contains(manifest : Manifest, digest : String) -> Bool {
  manifest_lookup(manifest, digest) is Some(_)
}

pub fn manifest_position(manifest : Manifest, digest : String) -> Int? {
  match manifest_lookup(manifest, digest) {
    Some(reference) => Some(reference.at())
    None => None
  }
}

pub fn manifest_chunk_sizes(manifest : Manifest) -> Array[Int] {
  manifest.references().map(reference => reference.length())
}

pub fn manifest_digests(manifest : Manifest) -> Array[String] {
  manifest.references().map(reference => reference.id())
}

fn clean_name(name : String) -> String {
  name.replace(old="\n", new="_").replace(old="|", new="_")
}

/// Canonical line-oriented encoding. It is intentionally simple to make the
/// format inspectable in a hex editor and stable across MoonBit backends.
pub fn encode_manifest(manifest : Manifest) -> String {
  let out = StringBuilder::new()
  out.write_string("MOONCHUNK-MANIFEST|1\n")
  out.write_string("name|")
  out.write_string(clean_name(manifest.filename()))
  out.write_string("\nsize|")
  out.write_string(manifest.size().to_string())
  out.write_string("\nroot|")
  out.write_string(manifest.root_digest())
  out.write_string("\nchunks|")
  out.write_string(manifest.count().to_string())
  out.write_string("\n")
  for reference in manifest.references() {
    out.write_string("chunk|")
    out.write_string(reference.at().to_string())
    out.write_string("|")
    out.write_string(reference.id())
    out.write_string("|")
    out.write_string(reference.length().to_string())
    out.write_string("\n")
  }
  out.to_string()
}

fn parse_int(value : StringView) -> Result[Int, String] {
  try {
    Ok(@strconv.parse_int(value))
  } catch {
    _ => Err("invalid integer in manifest")
  }
}

/// Parse the canonical format and validate the recomputed Merkle root.
pub fn decode_manifest(encoded : String) -> Result[Manifest, String] {
  let lines = encoded.split("\n").collect()
  if lines.length() < 5 || lines[0] != "MOONCHUNK-MANIFEST|1" {
    return Err("unsupported manifest header")
  }
  let name_parts = lines[1].split("|").collect()
  let size_parts = lines[2].split("|").collect()
  let root_parts = lines[3].split("|").collect()
  let count_parts = lines[4].split("|").collect()
  if name_parts.length() != 2 || size_parts.length() != 2 || root_parts.length() != 2 || count_parts.length() != 2 {
    return Err("malformed manifest metadata")
  }
  let total = match parse_int(size_parts[1]) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  let count = match parse_int(count_parts[1]) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  if count < 0 || lines.length() < count + 5 { return Err("malformed manifest chunk count") }
  let refs = []
  for i in 0.. x
      Err(e) => return Err(e)
    }
    let size = match parse_int(parts[3]) {
      Ok(x) => x
      Err(e) => return Err(e)
    }
    refs.push(ChunkRef::new(position, parts[2].to_string(), size))
  }
  let manifest = Manifest::new(name_parts[1].to_string(), refs)
  if manifest.size() != total { return Err("manifest total size mismatch") }
  if !digest_equal(manifest.root_digest(), root_parts[1].to_string()) { return Err("manifest Merkle root mismatch") }
  Ok(manifest)
}

pub fn manifest_summary(manifest : Manifest) -> String {
  "{manifest.filename()} {manifest.size()} bytes, {manifest.count()} chunks, root={manifest.root_digest()}"
}

pub fn manifest_same_content(left : Manifest, right : Manifest) -> Bool {
  digest_equal(left.root_digest(), right.root_digest()) && left.size() == right.size()
}

pub fn manifest_validate_order(manifest : Manifest) -> Bool {
  let mut expected = 0
  for reference in manifest.references() {
    if reference.at() != expected || reference.length() < 0 { return false }
    expected = expected + 1
  }
  true
}

pub fn manifest_reorder(manifest : Manifest, order : Array[Int]) -> Result[Manifest, String] {
  if order.length() != manifest.count() { return Err("order length mismatch") }
  let references = manifest.references()
  let output = []
  for i in 0..= references.length() { return Err("order index out of range") }
    let reference = references[source]
    output.push(ChunkRef::new(i, reference.id(), reference.length()))
  }
  Ok(Manifest::new(manifest.filename(), output))
}

pub fn manifest_prefix(manifest : Manifest, count : Int) -> Manifest {
  let limit = if count < 0 { 0 } else if count > manifest.count() { manifest.count() } else { count }
  let refs = []
  for i in 0.. Manifest {
  let first = if start < 0 { 0 } else if start > manifest.count() { manifest.count() } else { start }
  let refs = []
  let all = manifest.references()
  for i in first..