///|
fn tar_need(ok : Bool, message : String) -> Unit raise @ledger.InputError {
  if !ok {
    raise bad("TarProfile", "$", message)
  }
}

///|
fn tar_zero(data : BytesView) -> Bool {
  data.iter().all(byte => byte == b'\x00')
}

///|
fn tar_string(
  data : Bytes,
  start : Int32,
  width : Int32,
) -> String raise @ledger.InputError {
  let mut end = start + width
  for at in start.. Int64 raise @ledger.InputError {
  let mut value = 0L
  let mut digits = false
  let mut ended = false
  for at in start..<(start + width) {
    let byte = data[at].to_int()
    if byte == 0 || byte == 32 {
      if digits || byte == 0 {
        ended = true
      }
    } else {
      tar_need(
        !ended && byte >= 48 && byte <= 55,
        "Only nonnegative octal USTAR numbers are supported",
      )
      tar_need(
        value <= (9223372036854775807L - (byte - 48).to_int64()) / 8L,
        "USTAR numeric overflow",
      )
      value = value * 8L + (byte - 48).to_int64()
      digits = true
    }
  }
  value
}

///|
fn tar_entries(
  data : Bytes,
  limits : @ledger.Limits,
  maximum : Int32,
) -> Array[CarrierMember] raise @ledger.InputError {
  carrier_budget(data.length(), maximum)
  tar_need(
    data.length() >= 1024 && data.length() % 512 == 0,
    "USTAR requires whole blocks and two zero end blocks",
  )
  let result : Array[CarrierMember] = []
  let names : Set[String] = Set([])
  let mut total = 0L
  let mut cursor = 0
  for ;; {
    tar_need(cursor + 1024 <= data.length(), "Missing USTAR end blocks")
    if tar_zero(data[cursor:cursor + 512]) {
      tar_need(tar_zero(data[cursor:]), "Nonzero data after USTAR end marker")
      break
    }
    tar_need(result.length() < limits.max_reports + 1, "Too many USTAR members")
    tar_need(
      data[cursor + 257:cursor + 265] == b"ustar\x0000",
      "Only POSIX USTAR magic/version are supported",
    )
    let mut checksum = 0L
    for at in 0..<512 {
      checksum += if at >= 148 && at < 156 {
        32L
      } else {
        data[cursor + at].to_int().to_int64()
      }
    }
    tar_need(
      checksum == tar_number(data, cursor + 148, 8),
      "USTAR header checksum mismatch",
    )
    tar_need(
      data[cursor + 156] == b'0' || data[cursor + 156] == b'\x00',
      "Only regular files; directories, links, PAX, GNU and sparse extensions are refused",
    )
    tar_need(
      tar_zero(data[cursor + 157:cursor + 257]) &&
      tar_zero(data[cursor + 500:cursor + 512]),
      "Link targets or unknown USTAR header extension bytes",
    )
    ignore(tar_number(data, cursor + 100, 8))
    ignore(tar_number(data, cursor + 108, 8))
    ignore(tar_number(data, cursor + 116, 8))
    ignore(tar_number(data, cursor + 136, 12))
    ignore(tar_string(data, cursor + 265, 32))
    ignore(tar_string(data, cursor + 297, 32))
    tar_need(
      tar_number(data, cursor + 329, 8) == 0L &&
      tar_number(data, cursor + 337, 8) == 0L,
      "Device metadata on regular files is unsupported",
    )
    let prefix = tar_string(data, cursor + 345, 155)
    let base = tar_string(data, cursor, 100)
    tar_need(base != "", "Empty USTAR filename")
    let name = if prefix == "" { base } else { prefix + "/" + base }
    ignore(collection_path(name))
    tar_need(!names.contains(name), "Duplicate USTAR filename")
    names.add(name)
    let size = tar_number(data, cursor + 124, 12)
    tar_need(size <= 268435456L, "USTAR member exceeds the transport ceiling")
    total += size
    carrier_sizes(size.to_int(), total, limits)
    let start = cursor + 512
    let end = start + size.to_int()
    let next = start + (size.to_int() + 511) / 512 * 512
    tar_need(next <= data.length(), "Truncated USTAR payload")
    tar_need(tar_zero(data[end:next]), "Nonzero member block padding")
    result.push({ name, data: data[start:end].to_owned(), })
    cursor = next
  }
  result
}

///|
fn tar_write_string(header : Array[Byte], at : Int32, text : String) -> Unit {
  let bytes = @utf8.encode(text)
  for i in 0.. Unit {
  let mut remaining = value
  header[at + width - 1] = b'\x00'
  for i = width - 2; i >= 0; i = i - 1 {
    header[at + i] = (48 + remaining % 8).to_byte()
    remaining /= 8
  }
}

///|
fn tar_write(
  entries : Array[CarrierMember],
  maximum : Int32,
) -> Bytes raise @ledger.InputError {
  carrier_budget(0, maximum)
  let mut size = 1024L
  for entry in entries {
    tar_need(
      entry.name.length() <= 100,
      "Writer supports names up to 100 ASCII bytes",
    )
    ignore(collection_path(entry.name))
    size += 512L + (entry.data.length().to_int64() + 511L) / 512L * 512L
  }
  tar_need(
    size <= maximum.to_int64(),
    "USTAR output exceeds the configured transport budget",
  )
  let output : Array[Byte] = []
  for entry in entries {
    let header = Array::make(512, b'\x00')
    tar_write_string(header, 0, entry.name)
    for
      (at, width, value) in [
        (100, 8, 420),
        (108, 8, 0),
        (116, 8, 0),
        (124, 12, entry.data.length()),
        (136, 12, 0),
        (329, 8, 0),
        (337, 8, 0),
      ] {
      tar_write_octal(header, at, width, value)
    }
    header[156] = b'0'
    tar_write_string(header, 257, "ustar\u000000")
    for at in 148..<156 {
      header[at] = b' '
    }
    let checksum = header.fold(init=0, (sum, byte) => sum + byte.to_int())
    tar_write_octal(header, 148, 7, checksum)
    header[155] = b' '
    output.append(header)
    output.append(entry.data.to_array())
    let padding = (512 - entry.data.length() % 512) % 512
    for _ in 0.. Bytes raise @ledger.InputError {
  let bytes = tar_write(carrier_pack(self, limits), max_bytes)
  ignore(tar_entries(bytes, limits, max_bytes))
  bytes
}

///|
/// Restore only a sealed evidence pack; reject unsupported USTAR extensions,
/// duplicate/extra members, changed original XML or inconsistent full plan.
pub fn decode_tar_pack(
  data : Bytes,
  limits~ : @ledger.Limits = @ledger.Limits::default(),
  max_bytes~ : Int32 = 16777216,
) -> Evidence raise @ledger.InputError {
  carrier_restore(tar_entries(data, limits, max_bytes), limits)
}