///|
/// Boundary and regression counters used by release automation.
pub struct BoundaryScore {
  scenario : String
  tested_actions : Int
  blocked_actions : Int
  hazard_actions : Int
  goal_actions : Int
  stable : Bool
}

///|
/// Compare planner length with an executed trajectory.
pub struct PlanExecution {
  found : Bool
  planned_steps : Int
  executed_steps : Int
  reached_goal : Bool
  reward : Int
  exact : Bool
}

///|
/// A batch of deterministic snapshots for golden-file style testing.
pub struct SnapshotBatch {
  seed : Int
  values : Array[String]
  checksum : Int
}

///|
fn hash_text(text : String) -> Int {
  let mut value = 17
  for code in text.to_array() {
    value = (value * 31 + code.to_int()) % 1_000_003
  }
  value
}

///|
fn action_count(checks : Array[ActionCheck], hazard : Bool) -> Int {
  let mut total = 0
  for check in checks {
    if check.lands_in_hazard == hazard {
      total = total + 1
    }
  }
  total
}

///|
pub fn boundary_score(kind : ScenarioKind, seed : Int) -> BoundaryScore {
  let env = new(kind, seed)
  let _ = env.reset()
  let checks = env.action_checks()
  let blocked = 0
  let mut goals = 0
  for check in checks {
    if check.reaches_goal {
      goals = goals + 1
    }
  }
  BoundaryScore::{
    scenario: scenario_name(kind),
    tested_actions: checks.length(),
    blocked_actions: blocked +
    action_count(checks, false) -
    checks.length() +
    action_count(checks, true),
    hazard_actions: action_count(checks, true),
    goal_actions: goals,
    stable: checks.length() == 5,
  }
}

///|
/// Exercise all five actions from the initial state and return the outcome
/// counters. This explicitly covers boundary, wall, stay, and hazard paths.
pub fn boundary_report(seed : Int) -> String {
  let builder = StringBuilder::new()
  for
    kind in [
      GridWorld,
      CliffWalking,
      Maze,
      FrozenLakeLike,
      RandomMaze,
      EmptyRoom,
      FourRooms,
    ] {
    let score = boundary_score(kind, seed)
    builder.write_string(score.scenario)
    builder.write_string(" | actions=")
    builder.write_object(score.tested_actions)
    builder.write_string(" | blocked=")
    builder.write_object(score.blocked_actions)
    builder.write_string(" | hazards=")
    builder.write_object(score.hazard_actions)
    builder.write_string(" | stable=")
    builder.write_object(score.stable)
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
pub fn execute_plan(kind : ScenarioKind, seed : Int) -> PlanExecution {
  let env = new(kind, seed)
  let _ = env.reset()
  let plan = env.shortest_path()
  if !plan.found {
    PlanExecution::{
      found: false,
      planned_steps: -1,
      executed_steps: 0,
      reached_goal: false,
      reward: 0,
      exact: false,
    }
  } else {
    let stats = env.rollout(plan.actions)
    PlanExecution::{
      found: true,
      planned_steps: plan.steps,
      executed_steps: stats.steps,
      reached_goal: stats.terminated,
      reward: stats.reward_sum,
      exact: stats.steps == plan.steps && stats.terminated,
    }
  }
}

///|
pub fn execute_all_plans(seed : Int) -> Array[PlanExecution] {
  let result = Array::new(capacity=7)
  for
    kind in [
      GridWorld,
      CliffWalking,
      Maze,
      FrozenLakeLike,
      RandomMaze,
      EmptyRoom,
      FourRooms,
    ] {
    result.push(execute_plan(kind, seed))
  }
  result
}

///|
pub fn snapshot_batch(seed : Int) -> SnapshotBatch {
  let values = Array::new(capacity=7)
  let mut checksum = seed
  for
    kind in [
      GridWorld,
      CliffWalking,
      Maze,
      FrozenLakeLike,
      RandomMaze,
      EmptyRoom,
      FourRooms,
    ] {
    let value = snapshot(kind, seed)
    values.push(value)
    checksum = (checksum * 31 + hash_text(value)) % 1_000_003
  }
  SnapshotBatch::{ seed, values, checksum }
}

///|
pub fn snapshot_report(seed : Int) -> String {
  let batch = snapshot_batch(seed)
  let builder = StringBuilder::new()
  builder.write_string("snapshot seed=")
  builder.write_object(batch.seed)
  builder.write_string(" checksum=")
  builder.write_object(batch.checksum)
  builder.write_char('\n')
  for value in batch.values {
    builder.write_string(value)
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
/// Return a stable action table for consumers that serialize actions by code.
pub fn action_table() -> Array[String] {
  ["0:Up", "1:Down", "2:Left", "3:Right", "4:Stay"]
}

///|
/// Return a stable scenario table for metadata and dataset headers.
pub fn scenario_table() -> Array[String] {
  [
    "0:GridWorld", "1:CliffWalking", "2:Maze", "3:FrozenLakeLike", "4:RandomMaze",
    "5:EmptyRoom", "6:FourRooms",
  ]
}

///|
/// Reject unsafe benchmark arguments while retaining a total, predictable API.
pub fn normalized_episode_count(episodes : Int) -> Int {
  if episodes < 1 {
    1
  } else if episodes > 10_000 {
    10_000
  } else {
    episodes
  }
}

///|
pub fn normalized_step_limit(limit : Int) -> Int {
  if limit < 1 {
    1
  } else if limit > 1_000_000 {
    1_000_000
  } else {
    limit
  }
}

///|
/// A short release gate message consumed by the example program and CI logs.
pub fn release_gate(seed : Int) -> String {
  let score = quality_score(seed)
  let mut exact = 0
  for result in execute_all_plans(seed) {
    if result.exact {
      exact = exact + 1
    }
  }
  let builder = StringBuilder::new()
  builder.write_string("valid=")
  builder.write_object(score.passed)
  builder.write_string(" | exact_plans=")
  builder.write_object(exact)
  builder.write_string("/6 deterministic scenarios")
  builder.to_string()
}