///|
/// Deterministic STEP workload generator used by the local benchmark command.
/// It creates real IFC wall products, placements, representations and
/// extrusions instead of timing a synthetic string operation.
pub fn benchmark_fixture(product_count : Int) -> String {
  let count = if product_count < 0 { 0 } else { product_count }
  let output = StringBuilder()
  output <+
    #|ISO-10303-21;
  output <+
    #|HEADER;
  output <+
    #|FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
  output <+
    #|FILE_NAME('benchmark.ifc','2026-08-19T00:00:00',('moonbit-ifc'),(''),'moonbit-ifc','moonbit-ifc','');
  output <+
    #|FILE_SCHEMA(('IFC4'));
  output <+
    #|ENDSEC;
  output <+
    #|DATA;
  output <+
    #|#1=IFCPROJECT('0000000000000000000000',#2,'Benchmark project',$,$,$,$,(#10),#11);
  output <+
    #|#2=IFCOWNERHISTORY($,$,$,.ADDED.,$,$,$,0);
  output <+
    #|#10=IFCUNITASSIGNMENT((#12));
  output <+
    #|#11=IFCCONTEXT($,'Model','ViewDefinition [CoordinationView]','',#13,$,$,(#14),$);
  output <+
    #|#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
  output <+
    #|#13=IFCAXIS2PLACEMENT3D(#15,$,$);
  output <+
    #|#14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-05,#13,$,$,(#14),$);
  output <+
    #|#15=IFCCARTESIANPOINT((0.0,0.0,0.0));
  for ordinal in 0.. BenchmarkCase raise ParseError {
  let source = benchmark_fixture(product_count)
  {
    name,
    iterations: if iterations < 1 {
      1
    } else {
      iterations
    },
    source_length: source.length(),
    entity_count: parse(source).entities.length(),
  }
}

///|
fn elapsed_millis(start : UInt64, finish : UInt64) -> UInt64 {
  if finish >= start {
    finish - start
  } else {
    0UL
  }
}

///|
fn operations_per_second(iterations : Int, elapsed : UInt64) -> Double {
  if elapsed == 0UL {
    0.0
  } else {
    iterations.to_double() * 1000.0 / elapsed.to_double()
  }
}

///|
fn benchmark_product_count(case : BenchmarkCase) -> Int {
  if case.entity_count <= 8 {
    0
  } else {
    (case.entity_count - 8) / 6
  }
}

///|
pub fn benchmark_parse_case(
  case : BenchmarkCase,
) -> BenchmarkResult raise ParseError {
  let source = if case.name == "smoke" {
    minimal_fixture()
  } else {
    benchmark_fixture(benchmark_product_count(case))
  }
  let start = @env.now()
  let mut allocated = 0
  for _ in 0.. BenchmarkResult raise ParseError {
  let source = benchmark_fixture(benchmark_product_count(case))
  let start = @env.now()
  let mut allocated = 0
  for _ in 0.. BenchmarkResult {
  let source = benchmark_fixture(benchmark_product_count(case))
  let start = @env.now()
  let mut allocated = 0
  for _ in 0.. Array[BenchmarkCase] raise ParseError {
  [
    benchmark_case("small-8-walls", 8, 8),
    benchmark_case("medium-64-walls", 64, 3),
    benchmark_case("large-256-walls", 256, 1),
  ]
}

///|
pub fn run_benchmark_report() -> BenchmarkReport raise ParseError {
  let cases : Array[BenchmarkResult] = []
  for case in benchmark_cases() {
    cases.push(benchmark_parse_case(case))
    cases.push(benchmark_stream_case(case))
    if case.entity_count <= 500 {
      cases.push(benchmark_pipeline_case(case))
    }
  }
  {
    toolchain: "MoonBit stable toolchain; timing source: @env.now()",
    cases,
    notes: [
      "Each case parses the generated ISO 10303-21 source before measuring.", "Elapsed time is wall-clock milliseconds on the local machine.",
      "Run the command again on the target machine before publishing numbers.",
    ],
  }
}

///|
fn benchmark_result_json(result : BenchmarkResult) -> String {
  "{\"name\":" +
  json_string(result.name) +
  ",\"iterations\":" +
  result.iterations.to_string() +
  ",\"elapsed_millis\":" +
  result.elapsed_millis.to_string() +
  ",\"operations_per_second\":" +
  result.operations_per_second.to_string() +
  ",\"source_length\":" +
  result.source_length.to_string() +
  ",\"entity_count\":" +
  result.entity_count.to_string() +
  ",\"allocated_entities\":" +
  result.allocated_entities.to_string() +
  "}"
}

///|
pub fn BenchmarkReport::to_json(self : BenchmarkReport) -> String {
  "{\"toolchain\":" +
  json_string(self.toolchain) +
  ",\"cases\":[" +
  self.cases.map(benchmark_result_json).join(",") +
  "],\"notes\":[" +
  self.notes.map(json_string).join(",") +
  "]}"
}

///|
pub fn BenchmarkReport::to_markdown(self : BenchmarkReport) -> String {
  let rows = self.cases
    .map(result => {
      "| " +
      result.name +
      " | " +
      result.iterations.to_string() +
      " | " +
      result.source_length.to_string() +
      " | " +
      result.entity_count.to_string() +
      " | " +
      result.elapsed_millis.to_string() +
      " | " +
      result.operations_per_second.to_string() +
      " |"
    })
    .join("\n")
  "| Case | Iterations | Source chars | Entities | Elapsed ms | Ops/s |\n" +
  "|---|---:|---:|---:|---:|---:|\n" +
  rows
}

///|
pub fn BenchmarkReport::headline(self : BenchmarkReport) -> String {
  "benchmark cases=" +
  self.cases.length().to_string() +
  ", toolchain=" +
  self.toolchain
}

///|
pub fn BenchmarkResult::is_measured(self : BenchmarkResult) -> Bool {
  self.elapsed_millis > 0UL && self.operations_per_second > 0.0
}

///|
pub fn BenchmarkResult::bytes_per_operation(self : BenchmarkResult) -> Double {
  if self.iterations == 0 {
    0.0
  } else {
    self.source_length.to_double() / self.iterations.to_double()
  }
}

///|
pub fn benchmark_result_summary(result : BenchmarkResult) -> String {
  result.name +
  ": " +
  result.elapsed_millis.to_string() +
  " ms, " +
  result.operations_per_second.to_string() +
  " ops/s, " +
  result.source_length.to_string() +
  " chars, " +
  result.entity_count.to_string() +
  " entities"
}

///|
pub fn benchmark_smoke(source : String) -> BenchmarkResult raise ParseError {
  benchmark_parse_case({
    name: "smoke",
    iterations: 1,
    source_length: source.length(),
    entity_count: parse(source).entities.length(),
  })
}