///|
pub struct Scorecard {
  name : String
  expected : Double
  observed : Double
  tolerance : Double
} derive(Debug, Eq)

///|
pub fn Scorecard::new(
  name : String,
  expected : Double,
  observed : Double,
  tolerance : Double,
) -> Scorecard {
  {
    name,
    expected,
    observed,
    tolerance: if tolerance < 0.0 {
      0.0
    } else {
      tolerance
    },
  }
}

///|
pub fn Scorecard::passed(self : Scorecard) -> Bool {
  (self.observed - self.expected).abs() <= self.tolerance
}

///|
pub fn Scorecard::error(self : Scorecard) -> Double {
  (self.observed - self.expected).abs()
}

///|
pub fn Scorecard::text(self : Scorecard) -> String {
  let status = if self.passed() { "PASS" } else { "FAIL" }
  "[\{status}] \{self.name}: expected=\{self.expected},observed=\{self.observed},error=\{self.error()}"
}

///|
pub struct ScorecardSet {
  items : Array[Scorecard]
} derive(Debug)

///|
pub fn ScorecardSet::new() -> ScorecardSet {
  { items: [] }
}

///|
pub fn ScorecardSet::add(self : ScorecardSet, score : Scorecard) -> Unit {
  self.items.push(score)
}

///|
pub fn ScorecardSet::passed(self : ScorecardSet) -> Bool {
  let mut result = true
  for score in self.items {
    if !score.passed() {
      result = false
    }
  }
  result
}

///|
pub fn ScorecardSet::failed_count(self : ScorecardSet) -> Int {
  let mut total = 0
  for score in self.items {
    if !score.passed() {
      total = total + 1
    }
  }
  total
}

///|
pub fn ScorecardSet::to_text(self : ScorecardSet) -> String {
  let mut output = ""
  for score in self.items {
    output = output + score.text() + "\n"
  }
  output + "failed=" + self.failed_count().to_string()
}

///|
pub struct EnvironmentAudit {
  states : Int
  actions : Int
  reset_state : Int
  invalid_action_state : Int
  render_nonempty : Bool
} derive(Debug, Eq)

///|
pub fn EnvironmentAudit::gridworld() -> EnvironmentAudit {
  let env = GridWorldEnv::new()
  let start = env.reset()
  let invalid = env.step(-1)
  {
    states: env.state_space().length(),
    actions: env.actions().length(),
    reset_state: start,
    invalid_action_state: invalid.next_state(),
    render_nonempty: env.render().length() > 0,
  }
}

///|
pub fn EnvironmentAudit::cliff() -> EnvironmentAudit {
  let env = CliffWalkingEnv::new()
  let start = env.reset()
  let invalid = env.step(-1)
  {
    states: env.state_space().length(),
    actions: env.actions().length(),
    reset_state: start,
    invalid_action_state: invalid.next_state(),
    render_nonempty: env.render().length() > 0,
  }
}

///|
pub fn EnvironmentAudit::random_walk() -> EnvironmentAudit {
  let env = RandomWalkEnv::new(19, 29)
  let start = env.reset()
  let invalid = env.step(99)
  {
    states: env.state_space().length(),
    actions: env.actions().length(),
    reset_state: start,
    invalid_action_state: invalid.next_state(),
    render_nonempty: env.render().length() > 0,
  }
}

///|
pub fn EnvironmentAudit::bandit() -> EnvironmentAudit {
  let env = BanditEnv::new([0.1, 0.2, 0.3, 0.4, 0.5], 31)
  let start = env.reset()
  let invalid = env.step(99)
  {
    states: env.state_space().length(),
    actions: env.actions().length(),
    reset_state: start,
    invalid_action_state: invalid.next_state(),
    render_nonempty: env.render().length() > 0,
  }
}

///|
pub fn audit_score(
  audit : EnvironmentAudit,
  expected_states : Int,
  expected_actions : Int,
) -> ScorecardSet {
  let scores = ScorecardSet::new()
  scores.add(
    Scorecard::new(
      "state_count",
      expected_states.to_double(),
      audit.states.to_double(),
      0.0,
    ),
  )
  scores.add(
    Scorecard::new(
      "action_count",
      expected_actions.to_double(),
      audit.actions.to_double(),
      0.0,
    ),
  )
  scores.add(
    Scorecard::new(
      "reset_state",
      audit.reset_state.to_double(),
      audit.reset_state.to_double(),
      0.0,
    ),
  )
  scores.add(
    Scorecard::new(
      "invalid_action_state",
      audit.invalid_action_state.to_double(),
      audit.invalid_action_state.to_double(),
      0.0,
    ),
  )
  scores.add(
    Scorecard::new(
      "render_nonempty",
      1.0,
      if audit.render_nonempty {
        1.0
      } else {
        0.0
      },
      0.0,
    ),
  )
  scores
}

///|
pub fn all_environment_audits() -> String {
  let grid = audit_score(EnvironmentAudit::gridworld(), 16, 4)
  let cliff = audit_score(EnvironmentAudit::cliff(), 48, 4)
  let random = audit_score(EnvironmentAudit::random_walk(), 19, 2)
  let bandit = audit_score(EnvironmentAudit::bandit(), 1, 5)
  "gridworld\n\{grid.to_text()}\ncliff\n\{cliff.to_text()}\nrandom-walk\n\{random.to_text()}\nbandit\n\{bandit.to_text()}"
}

///|
pub struct SeedBank {
  seeds : Array[Int]
} derive(Debug, Eq)

///|
pub fn SeedBank::new(first : Int, count : Int) -> SeedBank {
  let size = if count < 0 { 0 } else { count }
  let result = Array::make(size, 0)
  for i in 0.. Int {
  self.seeds.length()
}

///|
pub fn SeedBank::at(self : SeedBank, index : Int) -> Int? {
  if index < 0 || index >= self.seeds.length() {
    None
  } else {
    Some(self.seeds[index])
  }
}

///|
pub fn SeedBank::distinct(self : SeedBank) -> Bool {
  let mut unique = true
  for i in 0.. String {
  let mut output = "index,seed\n"
  for i, seed in self.seeds {
    output = output + "\{i},\{seed}\n"
  }
  output
}

///|
pub fn seed_sensitivity(episodes : Int) -> String {
  let bank = SeedBank::new(20260711, 5)
  let mut output = "seed,solve_rate,tail_reward\n"
  for seed in bank.seeds {
    let config = EvaluationConfig::new(episodes, 40, seed)
    let result = run_random_walk_benchmark(config).result
    output = output +
      "\{seed},\{result.solve_rate()},\{result.tail_average(10)}\n"
  }
  output
}

///|
pub fn risk_flags() -> Array[String] {
  [
    "verify remote credentials are absent before publication", "verify default branch contains the final commit",
    "verify mooncakes package visibility after publication", "verify CI latest run after final push",
    "verify README examples on a clean checkout", "verify third-party source and fixture licensing",
  ]
}

///|
pub fn release_checklist_text() -> String {
  let mut output = "Release checklist\n"
  for i, flag in risk_flags() {
    output = output + "\{i + 1}. \{flag}\n"
  }
  output
}