src/lightspeed/ops/convention_profile_harness.gleam

//// Deterministic convention-profile conformance harness for M45.

import gleam/int
import gleam/list
import lightspeed/platform/convention_profile

pub const snapshot_version = 1

/// M45 conformance scenarios.
pub type Scenario {
  DefaultProfileQuickStart
  RuntimeDataTemplateSurfaceReduction
  ExplicitEscapeHatchStability
  DeterministicReferenceFixtureCertification
}

/// One M45 scenario outcome.
pub type ScenarioOutcome {
  ScenarioOutcome(
    scenario: Scenario,
    passed: Bool,
    deterministic: Bool,
    signature: String,
  )
}

/// Full M45 report.
pub type Report {
  Report(
    outcomes: List(ScenarioOutcome),
    failed_scenarios: Int,
    nondeterministic_failures: Int,
  )
}

/// Run all M45 scenarios.
pub fn run_matrix() -> Report {
  let outcomes =
    [
      DefaultProfileQuickStart,
      RuntimeDataTemplateSurfaceReduction,
      ExplicitEscapeHatchStability,
      DeterministicReferenceFixtureCertification,
    ]
    |> list.map(run_scenario)

  Report(
    outcomes: outcomes,
    failed_scenarios: count_failed(outcomes),
    nondeterministic_failures: count_nondeterministic(outcomes),
  )
}

/// Run one scenario twice and require deterministic parity.
pub fn run_scenario(scenario: Scenario) -> ScenarioOutcome {
  let #(first_passed, first_signature) = evaluate(scenario)
  let #(second_passed, second_signature) = evaluate(scenario)
  let deterministic =
    first_passed == second_passed && first_signature == second_signature
  let passed = first_passed && second_passed && deterministic

  ScenarioOutcome(
    scenario: scenario,
    passed: passed,
    deterministic: deterministic,
    signature: first_signature,
  )
}

/// Scenario label.
pub fn scenario_label(scenario: Scenario) -> String {
  case scenario {
    DefaultProfileQuickStart -> "default_profile_quick_start"
    RuntimeDataTemplateSurfaceReduction ->
      "runtime_data_template_surface_reduction"
    ExplicitEscapeHatchStability -> "explicit_escape_hatch_stability"
    DeterministicReferenceFixtureCertification ->
      "deterministic_reference_fixture_certification"
  }
}

/// Stable pass/fail label.
pub fn pass_fail_label(outcome: ScenarioOutcome) -> String {
  case outcome.passed {
    True -> "pass"
    False -> "fail"
  }
}

/// Scenario signature accessor.
pub fn signature(outcome: ScenarioOutcome) -> String {
  outcome.signature
}

/// Scenario accessor.
pub fn scenario(outcome: ScenarioOutcome) -> Scenario {
  outcome.scenario
}

/// Determinism accessor.
pub fn deterministic(outcome: ScenarioOutcome) -> Bool {
  outcome.deterministic
}

/// Report outcomes accessor.
pub fn outcomes(report: Report) -> List(ScenarioOutcome) {
  report.outcomes
}

/// Failed scenario count.
pub fn failed_scenarios(report: Report) -> Int {
  report.failed_scenarios
}

/// Nondeterministic scenario count.
pub fn nondeterministic_failures(report: Report) -> Int {
  report.nondeterministic_failures
}

/// Stable report signature.
pub fn report_signature(report: Report) -> String {
  let entries =
    list.map(report.outcomes, fn(outcome) {
      scenario_label(outcome.scenario)
      <> "="
      <> pass_fail_label(outcome)
      <> ":deterministic="
      <> bool_label(outcome.deterministic)
      <> ":"
      <> outcome.signature
    })

  join_with(";", entries)
}

/// Deterministic snapshot signature for M45 fixture drift gates.
pub fn snapshot_signature() -> String {
  "m45.snapshot.v"
  <> int.to_string(snapshot_version)
  <> "|"
  <> report_signature(run_matrix())
}

/// Deterministic markdown report for M45 fixture scripts.
pub fn snapshot_report_markdown() -> String {
  let report = run_matrix()
  let failed = failed_scenarios(report)
  let nondeterministic = nondeterministic_failures(report)
  let status = case failed == 0 && nondeterministic == 0 {
    True -> "OK"
    False -> "FAIL"
  }

  "# Convention Profile Fixture Report\n\n"
  <> "snapshot_version: "
  <> int.to_string(snapshot_version)
  <> "\n"
  <> "status: "
  <> status
  <> "\n"
  <> "failed_scenarios: "
  <> int.to_string(failed)
  <> "\n"
  <> "nondeterministic_failures: "
  <> int.to_string(nondeterministic)
  <> "\n\n"
  <> "snapshot_signature: "
  <> snapshot_signature()
  <> "\n\n"
  <> "report_signature: "
  <> report_signature(report)
  <> "\n"
}

fn evaluate(scenario: Scenario) -> #(Bool, String) {
  case scenario {
    DefaultProfileQuickStart -> evaluate_default_profile_quick_start()
    RuntimeDataTemplateSurfaceReduction ->
      evaluate_runtime_data_template_surface_reduction()
    ExplicitEscapeHatchStability -> evaluate_explicit_escape_hatch_stability()
    DeterministicReferenceFixtureCertification ->
      evaluate_deterministic_reference_fixture_certification()
  }
}

fn evaluate_default_profile_quick_start() -> #(Bool, String) {
  let profile = convention_profile.default_profile()
  let passed =
    convention_profile.quick_start_ready(profile)
    && !convention_profile.hidden_runtime_behavior(profile)
    && convention_profile.configuration_surface_size(profile) == 6

  #(passed, convention_profile.signature(profile))
}

fn evaluate_runtime_data_template_surface_reduction() -> #(Bool, String) {
  let baseline = convention_profile.default_profile()
  let baseline_surface = convention_profile.configuration_surface_size(baseline)
  let expanded =
    baseline
    |> convention_profile.with_escape_hatch(
      convention_profile.websocket_fallback_hatch(),
    )
    |> convention_profile.with_escape_hatch(
      convention_profile.data_partition_hatch(),
    )
    |> convention_profile.with_escape_hatch(
      convention_profile.template_diagnostics_hatch(),
    )
  let expanded_surface = convention_profile.configuration_surface_size(expanded)
  let passed =
    baseline_surface == 6
    && expanded_surface == 9
    && expanded_surface > baseline_surface

  #(
    passed,
    "baseline_surface="
      <> int.to_string(baseline_surface)
      <> "|expanded_surface="
      <> int.to_string(expanded_surface)
      <> "|baseline_signature="
      <> convention_profile.configuration_surface_signature(baseline),
  )
}

fn evaluate_explicit_escape_hatch_stability() -> #(Bool, String) {
  let profile =
    convention_profile.default_profile()
    |> convention_profile.with_escape_hatch(
      convention_profile.websocket_fallback_hatch(),
    )
    |> convention_profile.with_escape_hatch(
      convention_profile.data_partition_hatch(),
    )
    |> convention_profile.with_escape_hatch(
      convention_profile.template_diagnostics_hatch(),
    )
    |> convention_profile.with_escape_hatch(
      convention_profile.websocket_fallback_hatch(),
    )

  let passed =
    convention_profile.has_stable_override_seams(profile)
    && list.length(convention_profile.escape_hatches(profile)) == 3

  #(passed, convention_profile.override_seam_signature(profile))
}

fn evaluate_deterministic_reference_fixture_certification() -> #(Bool, String) {
  let first = convention_profile.snapshot_signature()
  let second = convention_profile.snapshot_signature()
  let fixtures = convention_profile.fixture_snapshots()
  let passed = first == second && list.length(fixtures) == 3
  let fixture_entries =
    list.map(fixtures, fn(entry) {
      let #(label, signature) = entry
      label <> "=" <> signature
    })

  #(passed, first <> "|fixtures=" <> join_with(";", fixture_entries))
}

fn count_failed(outcomes: List(ScenarioOutcome)) -> Int {
  case outcomes {
    [] -> 0
    [outcome, ..rest] ->
      case outcome.passed {
        True -> count_failed(rest)
        False -> 1 + count_failed(rest)
      }
  }
}

fn count_nondeterministic(outcomes: List(ScenarioOutcome)) -> Int {
  case outcomes {
    [] -> 0
    [outcome, ..rest] ->
      case outcome.deterministic {
        True -> count_nondeterministic(rest)
        False -> 1 + count_nondeterministic(rest)
      }
  }
}

fn bool_label(value: Bool) -> String {
  case value {
    True -> "true"
    False -> "false"
  }
}

fn join_with(separator: String, values: List(String)) -> String {
  case values {
    [] -> ""
    [value] -> value
    [value, ..rest] -> value <> separator <> join_with(separator, rest)
  }
}