///|
/// Parsed facts used by local smoke tests and example applications.
pub struct FixtureFact {
  name : String
  ok : Bool
  signals : Int
  changes : Int
  source_bytes : Int
  round_trip : Bool
} derive(Debug, Eq)

///|
/// Parse all valid generated fixtures and retain observable facts.
pub fn fixture_facts() -> Array[FixtureFact] {
  let sources : Array[(String, String)] = [
    ("minimal", minimal_fixture()),
    ("mixed", mixed_fixture()),
    ("four-state", four_state_fixture()),
    ("hierarchy", hierarchy_fixture()),
    ("edges", edge_fixture()),
  ]
  sources.map(fn(item) {
    match parse(item.1) {
      Ok(trace) =>
        {
          name: item.0,
          ok: true,
          signals: trace.signals.length(),
          changes: trace.changes.length(),
          source_bytes: item.1.length(),
          round_trip: grouped_export_preserves_shape(trace),
        }
      Err(_) =>
        {
          name: item.0,
          ok: false,
          signals: 0,
          changes: 0,
          source_bytes: item.1.length(),
          round_trip: false,
        }
    }
  })
}

///|
/// Return the number of valid fixtures in the built-in catalog.
pub fn valid_fixture_count(facts : Array[FixtureFact]) -> Int {
  facts.count_if(fn(fact) { fact.ok })
}

///|
/// Return the number of round-trip-safe fixtures.
pub fn round_trip_fixture_count(facts : Array[FixtureFact]) -> Int {
  facts.count_if(fn(fact) { fact.round_trip })
}

///|
/// Render fixture facts as a stable CSV table.
pub fn fixture_facts_csv(facts : Array[FixtureFact]) -> String {
  let out : Array[String] = ["name,ok,signals,changes,source_bytes,round_trip"]
  for fact in facts {
    out.push(
      fact.name +
      "," +
      fact.ok.to_string() +
      "," +
      fact.signals.to_string() +
      "," +
      fact.changes.to_string() +
      "," +
      fact.source_bytes.to_string() +
      "," +
      fact.round_trip.to_string(),
    )
  }
  out.join("\n")
}

///|
/// Render a short fixture validation report.
pub fn fixture_validation_report() -> String {
  let facts = fixture_facts()
  "fixtures=" +
  facts.length().to_string() +
  " valid=" +
  valid_fixture_count(facts).to_string() +
  " round_trip=" +
  round_trip_fixture_count(facts).to_string()
}

///|
/// Return whether the built-in fixtures satisfy their minimum structural facts.
pub fn fixtures_meet_expectations() -> Bool {
  let facts = fixture_facts()
  for fact in facts {
    let expected = fixture_expectations(fact.name)
    if !fact.ok || fact.signals < expected.0 || fact.changes < expected.1 {
      return false
    }
  }
  true
}

///|
/// Return a workload summary for the generated benchmark corpus.
pub fn fixture_workload_report() -> String {
  let large = multi_signal_fixture(16, 128)
  let hierarchy = hierarchy_fixture()
  "multi_signal_bytes=" +
  large.length().to_string() +
  " hierarchy_bytes=" +
  hierarchy.length().to_string() +
  " total_bytes=" +
  (large.length() + hierarchy.length()).to_string()
}

///|
/// Return fixture names in the documented verification order.
pub fn fixture_names() -> Array[String] {
  ["minimal", "mixed", "four-state", "hierarchy", "edges"]
}

///|
/// Render one fixture fact as JSON for smoke-test output.
pub fn fixture_fact_json(fact : FixtureFact) -> String {
  "{\"name\":\"" +
  escape_text(fact.name) +
  "\",\"ok\":" +
  fact.ok.to_string() +
  ",\"signals\":" +
  fact.signals.to_string() +
  ",\"changes\":" +
  fact.changes.to_string() +
  ",\"source_bytes\":" +
  fact.source_bytes.to_string() +
  ",\"round_trip\":" +
  fact.round_trip.to_string() +
  "}"
}

///|
/// Render every fixture fact as a JSON array.
pub fn fixture_facts_json(facts : Array[FixtureFact]) -> String {
  "[" + facts.map(fixture_fact_json).join(",") + "]"
}