///|
fn require_unit(
  result : Result[Unit, @moonflowgraph.GraphError],
  action : String,
) -> Unit {
  match result {
    Ok(_) => ()
    Err(err) => abort("\{action}: \{err.message()}")
  }
}

///|
fn require_plan(
  result : Result[@moonflowgraph.ExecutionPlan, @moonflowgraph.GraphError],
) -> @moonflowgraph.ExecutionPlan {
  match result {
    Ok(plan) => plan
    Err(err) => abort("plan: \{err.message()}")
  }
}

///|
fn require_json(
  result : Result[String, @moonflowgraph.SnapshotError],
  action : String,
) -> String {
  match result {
    Ok(value) => value
    Err(err) => abort("\{action}: \{err.message()}")
  }
}

///|
fn require_graph(
  result : Result[@moonflowgraph.FlowGraph, @moonflowgraph.WorkflowSpecError],
  action : String,
) -> @moonflowgraph.FlowGraph {
  match result {
    Ok(graph) => graph
    Err(err) => abort("\{action}: \{err.message()}")
  }
}

///|
fn layered_dag(layer_count : Int, width : Int) -> @moonflowgraph.FlowGraph {
  let graph = @moonflowgraph.FlowGraph::new()
  for layer = 0; layer < layer_count; layer = layer + 1 {
    for column = 0; column < width; column = column + 1 {
      let id = "l\{layer}_n\{column}"
      require_unit(
        graph.add_task(
          @moonflowgraph.TaskNode::new(id, "Layer \{layer} node \{column}"),
        ),
        "add task",
      )
      if layer > 0 {
        require_unit(
          graph.add_dependency(
            @moonflowgraph.TaskId::new("l\{layer - 1}_n\{column}"),
            @moonflowgraph.TaskId::new(id),
          ),
          "add dependency",
        )
      }
    }
  }
  graph
}

///|
fn bench_case(
  name : String,
  nodes : Int,
  edges : Int,
  op : () -> Unit,
) -> String {
  let start = @core_bench.monotonic_clock_start()
  op()
  let elapsed_us = @core_bench.monotonic_clock_end(start)
  "{\"name\":\"\{name}\",\"elapsed_us\":\{elapsed_us},\"nodes\":\{nodes},\"edges\":\{edges}}"
}

///|
fn main {
  let graph = layered_dag(8, 64)
  let trace = @moonflowgraph.Trace::new()
  let plan = require_plan(graph.plan())
  let workflow_json = graph.to_workflow_spec().to_json()
  let snapshot_json = require_json(
    graph.to_json_checked(plan, trace),
    "checked JSON",
  )
  let cases = [
    bench_case(
      "validate_layered_512",
      graph.task_count(),
      graph.dependency_count(),
      () => require_unit(graph.validate(), "validate"),
    ),
    bench_case(
      "plan_layered_512",
      graph.task_count(),
      graph.dependency_count(),
      () => ignore(require_plan(graph.plan())),
    ),
    bench_case(
      "to_json_checked_layered_512",
      graph.task_count(),
      graph.dependency_count(),
      () => ignore(require_json(graph.to_json_checked(plan, trace), "json")),
    ),
    bench_case(
      "from_workflow_json_layered_512",
      graph.task_count(),
      graph.dependency_count(),
      () => {
        ignore(
          require_graph(
            @moonflowgraph.FlowGraph::from_workflow_json(workflow_json),
            "workflow import",
          ),
        )
      },
    ),
  ]
  let out = StringBuilder()
  out <+ "{\n"
  out <+ "  \"schema_version\": 1,\n"
  out <+ "  \"snapshot_json_bytes\": \{snapshot_json.length()},\n"
  out <+ "  \"cases\": [\n"
  for i = 0; i < cases.length(); i = i + 1 {
    if i > 0 {
      out <+ ",\n"
    }
    out <+ "    \{cases[i]}"
  }
  out <+ "\n"
  out <+ "  ]\n"
  out <+ "}\n"
  println(out.to_string())
}