///|
/// Aggregate statistics for an archive listing.
pub struct ArchiveMetrics {
  pub entry_count : Int
  pub total_size : Int
  pub total_compressed_size : Int
  pub largest_size : Int
  pub largest_index : Int
  pub folders : Int
  pub files_with_data : Int
} derive(Show, Eq)

///|
/// Calculate stable, allocation-free metrics for a parsed listing.
pub fn archive_metrics(listing : ArchiveListing) -> ArchiveMetrics {
  let mut total = 0
  let mut compressed = 0
  let mut largest = 0
  let mut largest_index = -1
  let mut folders = 0
  let mut data = 0
  let mut seen_folder = -1
  for i, entry in listing.entries {
    total = total + entry.size
    compressed = compressed + entry.compressed_size
    if entry.size > largest {
      largest = entry.size
      largest_index = i
    }
    if entry.folder != seen_folder {
      folders = folders + 1
      seen_folder = entry.folder
    }
    if entry.size > 0 {
      data = data + 1
    }
  }
  {
    entry_count: listing.entries.length(),
    total_size: total,
    total_compressed_size: compressed,
    largest_size: largest,
    largest_index,
    folders,
    files_with_data: data,
  }
}

///|
/// Find an entry by its exact archive path.
pub fn find_archive_entry(
  listing : ArchiveListing,
  name : String,
) -> ArchiveEntry? {
  for entry in listing.entries {
    if entry.name == name {
      return Some(entry)
    }
  }
  None
}

///|
/// Return all entries belonging to one CAB folder or CHM section.
pub fn entries_in_container(
  listing : ArchiveListing,
  container : Int,
) -> Array[ArchiveEntry] {
  let result : Array[ArchiveEntry] = []
  for entry in listing.entries {
    if entry.folder == container {
      result.push(entry)
    }
  }
  result
}

///|
/// Validate listing metadata before an extraction loop.
pub fn validate_listing(
  listing : ArchiveListing,
  limits? : Limits = default_limits,
) -> Unit raise MspackError {
  if listing.entries.length() > limits.max_entries {
    raise MspackError("listing entry limit exceeded")
  }
  let mut total = 0
  for entry in listing.entries {
    if entry.size < 0 ||
      entry.compressed_size < 0 ||
      entry.offset < 0 ||
      entry.folder < 0 {
      raise MspackError("negative archive entry metadata")
    }
    if entry.size > limits.max_output {
      raise MspackError("archive entry exceeds output limit")
    }
    if entry.compressed_size > limits.max_output {
      raise MspackError("compressed entry exceeds output limit")
    }
    total = total + entry.size
    if total < 0 || total > limits.max_output {
      raise MspackError("listing total output limit exceeded")
    }
  }
}

///|
/// Select entries whose uncompressed size is within a caller supplied range.
pub fn entries_by_size(
  listing : ArchiveListing,
  minimum : Int,
  maximum : Int,
) -> Array[ArchiveEntry] {
  let result : Array[ArchiveEntry] = []
  if minimum > maximum {
    return result
  }
  for entry in listing.entries {
    if entry.size >= minimum && entry.size <= maximum {
      result.push(entry)
    }
  }
  result
}

///|
/// Return true when all entry names are unique.
pub fn has_unique_entry_names(listing : ArchiveListing) -> Bool {
  for i, entry in listing.entries {
    for j in (i + 1).. Array[ArchiveEntry] {
  let result : Array[ArchiveEntry] = []
  if count <= 0 {
    return result
  }
  let limit = if count < listing.entries.length() {
    count
  } else {
    listing.entries.length()
  }
  for i in 0.. UInt {
  let mut hash : UInt = 2166136261
  for entry in listing.entries {
    for byte in entry.name.to_bytes() {
      hash = (hash ^ byte.to_uint()) * 16777619
    }
    hash = (hash ^ 0xFF) * 16777619
  }
  hash
}

///|
/// Check that offsets and sizes do not overflow an input buffer.
pub fn validate_entry_ranges(
  listing : ArchiveListing,
  input_size : Int,
) -> Unit raise MspackError {
  if input_size < 0 {
    raise MspackError("negative input size")
  }
  for entry in listing.entries {
    if entry.offset > input_size ||
      entry.compressed_size > input_size - entry.offset {
      raise MspackError("archive entry range exceeds input")
    }
  }
}

///|
/// Return a compact human-readable format label for diagnostics.
pub fn archive_format_label(listing : ArchiveListing) -> String {
  if listing.format == "CAB" {
    "Microsoft Cabinet"
  } else if listing.format == "CHM" {
    "Compiled HTML Help"
  } else {
    listing.format
  }
}

///|
/// Check whether extraction can proceed without exceeding a byte budget.
pub fn within_output_budget(listing : ArchiveListing, budget : Int) -> Bool {
  if budget < 0 {
    return false
  }
  let mut total = 0
  for entry in listing.entries {
    total = total + entry.size
    if total < 0 || total > budget {
      return false
    }
  }
  true
}

///|
/// Return the sum of sizes for one container.
pub fn container_size(listing : ArchiveListing, container : Int) -> Int {
  let mut total = 0
  for entry in listing.entries {
    if entry.folder == container {
      total = total + entry.size
    }
  }
  total
}

///|
/// Return the number of entries with a given compression flag mask.
pub fn count_entries_with_flags(listing : ArchiveListing, mask : Int) -> Int {
  let mut count = 0
  for entry in listing.entries {
    if (entry.flags & mask) == mask {
      count = count + 1
    }
  }
  count
}

///|
/// Verify that all entries have a non-empty name and valid container id.
pub fn validate_entry_identity(
  listing : ArchiveListing,
) -> Unit raise MspackError {
  for entry in listing.entries {
    if entry.name.length() == 0 {
      raise MspackError("archive entry has an empty name")
    }
    if entry.folder < 0 {
      raise MspackError("archive entry has an invalid container")
    }
  }
}