///|
fn workload(
  count : Int32,
  shape : String,
  changed : Bool,
) -> (Array[String], Array[@trace.SidecarInput]) {
  let files : Array[String] = []
  let sources = [
    @trace.SidecarInput::new(
      "bold.json",
      if changed {
        "{\"RepetitionTime\":3.0,\"vendor\":[null,-0,9007199254740993,1e999]}"
      } else {
        "{\"RepetitionTime\":2.0,\"vendor\":[null,-0,9007199254740993,1e999]}"
      },
    ),
  ]
  for i in 0.. Int32 raise {
  match phase {
    "build" =>
      @trace.DatasetIndex::from_manifest(files, sources).data_paths().length()
    "audit" => {
      let report = index.audit_all()
      assert_eq(report.error_count(), 0)
      report.entries().length()
    }
    "compare" => {
      let report = index.compare(after)
      assert_eq(report.error_count(), 0)
      report.changed_count()
    }
    "serialize" => audit.to_json().length()
    _ => abort("validated phase")
  }
}

///|
fn argument_error(message : String) -> Unit raise {
  // Release Wasm may strip failure strings; retain one complete diagnostic.
  println(
    Json::object({
      "status": Json::string("error"),
      "code": Json::string("invalid_benchmark_arguments"),
      "message": Json::string(message),
    }).stringify(),
  )
  fail(message)
}

///|
#cfg(target="js")
fn benchmark_args(raw : Array[String]) -> Array[String] {
  // core/env exposes Node's [node, script, ...], unlike native/WASI argv.
  if raw.length() >= 2 {
    raw[1:].to_owned()
  } else {
    raw
  }
}

///|
#cfg(not(target="js"))
fn benchmark_args(raw : Array[String]) -> Array[String] {
  raw
}

///|
fn main raise {
  let args = benchmark_args(@env.args())
  guard args.length() == 5 else {
    argument_error(
      "Usage: benchmark build|audit|compare|serialize|evidence sparse|scoped|root-heavy SCANS SAMPLES",
    )
    return
  }
  let phase = args[1]
  let shape = args[2]
  guard ["build", "audit", "compare", "serialize", "evidence"].contains(phase) &&
    ["sparse", "scoped", "root-heavy"].contains(shape) else {
    argument_error("Unknown phase or workload shape")
    return
  }
  let count = @string.parse_int(args[3]) catch {
    _ => {
      argument_error("SCANS must be an integer")
      0
    }
  }
  let samples = @string.parse_int(args[4]) catch {
    _ => {
      argument_error("SAMPLES must be an integer")
      0
    }
  }
  guard count > 0 && count <= 5000 && samples >= 3 && samples <= 31 else {
    argument_error("SCANS must be 1..5000; SAMPLES must be 3..31")
    return
  }
  let (files, sources) = workload(count, shape, false)
  let index = @trace.DatasetIndex::from_manifest(files, sources)
  let after = if phase == "compare" || phase == "evidence" {
    let (paths, inputs) = workload(count, shape, true)
    @trace.DatasetIndex::from_manifest(paths, inputs)
  } else {
    index
  }
  let audit = if phase == "serialize" || phase == "evidence" {
    index.audit_all()
  } else {
    index.audit([])
  }
  if phase == "evidence" {
    println(
      "{\"audit\":" +
      audit.to_json() +
      ",\"impact\":" +
      index.compare(after).to_json() +
      ",\"manifest\":" +
      index.to_manifest_json() +
      "}",
    )
    return
  }
  ignore(execute(phase, files, sources, index, after, audit)) // one untimed warmup
  let elapsed : Array[Double] = []
  let mut checksum = 0L
  for _ in 0.. Json::number(value))),
      "median_us": Json::number(sorted[sorted.length() / 2]),
      "checksum": Json::string(checksum.to_string()),
    }).stringify(),
  )
}