/// Deterministic benchmark scenarios without wall-clock dependencies.
///
/// The benchmark reports work in bytes and operations so it can be compared
/// across Native, JavaScript, and WebAssembly targets in CI.

pub(all) struct BenchmarkInput {
  name : String
  bytes : Array[Byte]
  description : String
} derive(Show)

pub fn BenchmarkInput::name(self : BenchmarkInput) -> String { self.name }
pub fn BenchmarkInput::bytes(self : BenchmarkInput) -> Array[Byte] { self.bytes.copy() }
pub fn BenchmarkInput::description(self : BenchmarkInput) -> String { self.description }

pub(all) struct BenchmarkResult {
  name : String
  input_bytes : Int
  chunks : Int
  digest_operations : Int
  manifest_bytes : Int
  restored_bytes : Int
  reused_bytes : Int
  uploaded_bytes : Int
  checksum : String
} derive(Show)

pub fn BenchmarkResult::name(self : BenchmarkResult) -> String { self.name }
pub fn BenchmarkResult::input(self : BenchmarkResult) -> Int { self.input_bytes }
pub fn BenchmarkResult::chunks(self : BenchmarkResult) -> Int { self.chunks }
pub fn BenchmarkResult::digest_ops(self : BenchmarkResult) -> Int { self.digest_operations }
pub fn BenchmarkResult::manifest_size(self : BenchmarkResult) -> Int { self.manifest_bytes }
pub fn BenchmarkResult::restored(self : BenchmarkResult) -> Int { self.restored_bytes }
pub fn BenchmarkResult::reused(self : BenchmarkResult) -> Int { self.reused_bytes }
pub fn BenchmarkResult::uploaded(self : BenchmarkResult) -> Int { self.uploaded_bytes }
pub fn BenchmarkResult::checksum(self : BenchmarkResult) -> String { self.checksum }

fn deterministic_byte(seed : UInt, index : Int) -> Byte {
  let mut x = seed + index.reinterpret_as_uint() * 0x9e3779b9U
  x = x ^ (x >> 16)
  x = x * 0x7feb352dU
  x = x ^ (x >> 15)
  (x & 0xffU).to_int().to_byte()
}

pub fn synthetic_input(name : String, size : Int, seed : UInt) -> BenchmarkInput {
  let count = if size < 0 { 0 } else { size }
  let data = Array::makei(count, i => deterministic_byte(seed, i))
  { name, bytes: data, description: "deterministic pseudo-random bytes" }
}

pub fn repetitive_input(name : String, size : Int, pattern : String) -> BenchmarkInput {
  let _ = pattern
  let count = if size < 0 { 0 } else { size }
  let data : Array[Byte] = []
  let repeated : Byte = b'x'
  for _ in 0.. BenchmarkInput {
  let paragraph = "MoonChunk content-defined boundaries support backups, caches, and edge replicas.\n"
  let out = StringBuilder::new()
  let count = if paragraphs < 0 { 0 } else { paragraphs }
  for _ in 0.. BenchmarkInput {
  let data = input.bytes()
  let first = if start < 0 { 0 } else if start > data.length() { data.length() } else { start }
  let last = if length < 0 { first } else if first + length > data.length() { data.length() } else { first + length }
  for i in first.. BenchmarkInput {
  let source = input.bytes()
  let position = if at < 0 { 0 } else if at > source.length() { source.length() } else { at }
  let out = []
  for i in 0.. BenchmarkInput {
  let source = input.bytes()
  let first = if start < 0 { 0 } else if start > source.length() { source.length() } else { start }
  let last = if length < 0 { first } else if first + length > source.length() { source.length() } else { first + length }
  let out = []
  for i in 0.. BenchmarkResult {
  let data = input.bytes()
  let chunks = chunk_bytes(data, config)
  let manifest = manifest_from_chunks(input.name(), chunks)
  let encoded = encode_manifest(manifest)
  let store = ChunkStore::new()
  let mut digest_operations = 0
  for chunk in chunks {
    let _ = store.put(chunk)
    digest_operations = digest_operations + 1
  }
  let restored = match restore_manifest(store, manifest) {
    Ok(value) => value.length()
    Err(_) => 0
  }
  { name: input.name(), input_bytes: data.length(), chunks: chunks.length(), digest_operations, manifest_bytes: encoded.to_bytes().length(), restored_bytes: restored, reused_bytes: data.length(), uploaded_bytes: 0, checksum: digest_bytes(data) }
}

pub fn run_comparison(old : BenchmarkInput, next : BenchmarkInput, config : ChunkerConfig) -> BenchmarkResult {
  let old_chunks = chunk_bytes(old.bytes(), config)
  let next_chunks = chunk_bytes(next.bytes(), config)
  let old_manifest = manifest_from_chunks(old.name(), old_chunks)
  let next_manifest = manifest_from_chunks(next.name(), next_chunks)
  let plan = diff_manifests(old_manifest, next_manifest)
  let encoded = encode_manifest(next_manifest)
  let store = ChunkStore::new()
  let _ = store_chunks(store, next_chunks)
  let restored = match restore_manifest(store, next_manifest) {
    Ok(value) => value.length()
    Err(_) => 0
  }
  { name: old.name() + " -> " + next.name(), input_bytes: next.bytes().length(), chunks: next_chunks.length(), digest_operations: old_chunks.length() + next_chunks.length(), manifest_bytes: encoded.to_bytes().length(), restored_bytes: restored, reused_bytes: plan.reused(), uploaded_bytes: plan.uploaded(), checksum: next_manifest.root_digest() }
}

pub fn benchmark_scenarios() -> Array[BenchmarkInput] {
  [
    synthetic_input("random-4k", 4096, 1U),
    synthetic_input("random-32k", 32768, 2U),
    repetitive_input("repeat-16k", 16384, "moonbit"),
    text_input("docs-8k", 100),
  ]
}

pub fn benchmark_all(config? : ChunkerConfig = ChunkerConfig::new()) -> Array[BenchmarkResult] {
  let results = []
  for input in benchmark_scenarios() { results.push(run_benchmark(input, config)) }
  results
}

pub fn benchmark_total_input(results : Array[BenchmarkResult]) -> Int {
  let mut total = 0
  for result in results { total = total + result.input() }
  total
}

pub fn benchmark_total_chunks(results : Array[BenchmarkResult]) -> Int {
  let mut total = 0
  for result in results { total = total + result.chunks() }
  total
}

pub fn benchmark_total_manifest_bytes(results : Array[BenchmarkResult]) -> Int {
  let mut total = 0
  for result in results { total = total + result.manifest_size() }
  total
}

pub fn benchmark_report(results : Array[BenchmarkResult]) -> String {
  let out = StringBuilder::new()
  for result in results {
    out.write_string(result.name())
    out.write_string(" input=")
    out.write_string(result.input().to_string())
    out.write_string(" chunks=")
    out.write_string(result.chunks().to_string())
    out.write_string(" restored=")
    out.write_string(result.restored().to_string())
    out.write_string(" checksum=")
    out.write_string(result.checksum())
    out.write_string("\n")
  }
  out.to_string()
}

pub fn comparison_report(result : BenchmarkResult) -> String {
  "{result.name()} reused={result.reused()} uploaded={result.uploaded()} chunks={result.chunks()}"
}

pub fn benchmark_round_trip_ok(result : BenchmarkResult) -> Bool {
  result.input() == result.restored() && result.input() >= 0
}

pub fn benchmark_inputs_are_deterministic() -> Bool {
  let left = synthetic_input("x", 128, 9U)
  let right = synthetic_input("x", 128, 9U)
  digest_equal(digest_bytes(left.bytes()), digest_bytes(right.bytes()))
}

pub fn benchmark_mutation_overlap(config : ChunkerConfig) -> Int {
  let original = synthetic_input("original", 4096, 4U)
  let changed = mutate_region(original, 1700, 16, 99U)
  let left = chunk_bytes(original.bytes(), config)
  let right = chunk_bytes(changed.bytes(), config)
  content_overlap(left, right)
}

pub fn benchmark_insertion_overlap(config : ChunkerConfig) -> Int {
  let original = text_input("original", 80)
  let changed = insert_region(original, 200, [b'x', b'y', b'z'])
  content_overlap(chunk_bytes(original.bytes(), config), chunk_bytes(changed.bytes(), config))
}

pub fn benchmark_deletion_overlap(config : ChunkerConfig) -> Int {
  let original = text_input("original", 80)
  let changed = delete_region(original, 200, 3)
  content_overlap(chunk_bytes(original.bytes(), config), chunk_bytes(changed.bytes(), config))
}

pub fn benchmark_checksum_chain(inputs : Array[BenchmarkInput]) -> String {
  let mut current = ""
  for input in inputs { current = digest_string(current + digest_bytes(input.bytes())) }
  current
}