///|
/// Deterministic scenario generation for what-if model validation.
///
/// A scenario is a named vector of integer parameters. The engine supports
/// bounded Cartesian expansion, overrides, deltas, and reproducible scoring;
/// it is useful for capacity planning and regression matrices in CI.
pub struct ScenarioValue {
  name : String
  value : Int
}

///|
/// Create a named scenario value.
pub fn scenario_value(name : String, value : Int) -> ScenarioValue {
  { name, value }
}

///|
/// A named scenario.
pub struct WhatIfScenario {
  name : String
  values : Array[ScenarioValue]
}

///|
/// Create a scenario.
pub fn what_if_scenario(
  name : String,
  values : Array[ScenarioValue],
) -> WhatIfScenario {
  { name, values: values.copy() }
}

///|
/// Read a scenario parameter.
pub fn WhatIfScenario::get(self : WhatIfScenario, name : String) -> Int? {
  for value in self.values {
    if value.name == name {
      return Some(value.value)
    }
  }
  None
}

///|
/// Set or replace a scenario parameter.
pub fn WhatIfScenario::set(
  self : WhatIfScenario,
  name : String,
  value : Int,
) -> Bool {
  for index, current in self.values {
    if current.name == name {
      self.values[index] = scenario_value(name, value)
      return true
    }
  }
  self.values.push(scenario_value(name, value))
  true
}

///|
/// Return copied values.
pub fn WhatIfScenario::values(self : WhatIfScenario) -> Array[ScenarioValue] {
  self.values.copy()
}

///|
/// Return a scenario with a delta applied.
pub fn WhatIfScenario::delta(
  self : WhatIfScenario,
  name : String,
  amount : Int,
) -> WhatIfScenario {
  let result = what_if_scenario(self.name, self.values)
  match result.get(name) {
    Some(value) => ignore(result.set(name, value + amount))
    None => ignore(result.set(name, amount))
  }
  result
}

///|
/// Return a stable scenario key.
pub fn WhatIfScenario::signature(self : WhatIfScenario) -> Int {
  let mut result = 31
  for value in self.values {
    for character in value.name {
      result = result * 37 + character.to_int()
    }
    result = result * 41 + value.value
  }
  result
}

///|
/// A parameter axis for Cartesian scenario generation.
pub struct ScenarioAxis {
  name : String
  values : Array[Int]
}

///|
/// Create an axis.
pub fn scenario_axis(name : String, values : Array[Int]) -> ScenarioAxis {
  { name, values: values.copy() }
}

///|
/// Generate all combinations up to a hard limit.
pub fn scenario_product(
  axes : Array[ScenarioAxis],
  limit : Int,
) -> Array[WhatIfScenario] {
  let result : Array[WhatIfScenario] = []
  let cap = if limit < 1 { 1 } else { limit }
  scenario_product_rec(axes, 0, [], result, cap)
  result
}

///|
/// Cartesian expansion helper.
fn scenario_product_rec(
  axes : Array[ScenarioAxis],
  index : Int,
  values : Array[ScenarioValue],
  result : Array[WhatIfScenario],
  limit : Int,
) -> Unit {
  if result.length() >= limit {
    return
  }
  if index >= axes.length() {
    result.push(what_if_scenario("scenario_\{result.length()}", values))
    return
  }
  for value in axes[index].values {
    let next = values.copy()
    next.push(scenario_value(axes[index].name, value))
    scenario_product_rec(axes, index + 1, next, result, limit)
    if result.length() >= limit {
      return
    }
  }
}

///|
/// Return the number of combinations, capped at a limit.
pub fn scenario_product_size(axes : Array[ScenarioAxis], limit : Int) -> Int {
  let mut result = 1
  let cap = if limit < 1 { 1 } else { limit }
  for axis in axes {
    result *= axis.values.length()
    if result >= cap {
      return cap
    }
  }
  result
}

///|
/// A scenario evaluation.
pub struct ScenarioEvaluation {
  scenario : WhatIfScenario
  score : Int
  feasible : Bool
}

///|
/// Evaluate a scenario with a callback.
pub fn evaluate_scenario(
  scenario : WhatIfScenario,
  score : Int,
  feasible : Bool,
) -> ScenarioEvaluation {
  { scenario, score, feasible }
}

///|
/// Return whether evaluation is feasible.
pub fn ScenarioEvaluation::feasible(self : ScenarioEvaluation) -> Bool {
  self.feasible
}

///|
/// Return score.
pub fn ScenarioEvaluation::score(self : ScenarioEvaluation) -> Int {
  self.score
}

///|
/// Return scenario name.
pub fn ScenarioEvaluation::name(self : ScenarioEvaluation) -> String {
  self.scenario.name
}

///|
/// Select the best feasible evaluation.
pub fn best_scenario(
  evaluations : Array[ScenarioEvaluation],
  minimize : Bool,
) -> ScenarioEvaluation? {
  let mut result : ScenarioEvaluation? = None
  for evaluation in evaluations {
    if !evaluation.feasible {
      continue
    }
    match result {
      None => result = Some(evaluation)
      Some(current) => {
        let better = if minimize {
          evaluation.score < current.score
        } else {
          evaluation.score > current.score
        }
        if better {
          result = Some(evaluation)
        }
      }
    }
  }
  result
}

///|
/// Return the feasible evaluations ordered by score.
pub fn sort_scenario_evaluations(
  evaluations : Array[ScenarioEvaluation],
  minimize : Bool,
) -> Array[ScenarioEvaluation] {
  let result = evaluations.copy()
  for left in 0.. result[left].score
      }
      if better {
        let temporary = result[left]
        result[left] = result[right]
        result[right] = temporary
      }
    }
  }
  result
}

///|
/// Return the number of feasible evaluations.
pub fn feasible_scenario_count(evaluations : Array[ScenarioEvaluation]) -> Int {
  let mut result = 0
  for evaluation in evaluations {
    if evaluation.feasible {
      result += 1
    }
  }
  result
}

///|
/// Return score spread among evaluations.
pub fn scenario_score_spread(evaluations : Array[ScenarioEvaluation]) -> Int {
  if evaluations.length() == 0 {
    return 0
  }
  let mut low = evaluations[0].score
  let mut high = low
  for evaluation in evaluations {
    if evaluation.score < low {
      low = evaluation.score
    }
    if evaluation.score > high {
      high = evaluation.score
    }
  }
  high - low
}

///|
/// Return a stable evaluation fingerprint.
pub fn scenario_evaluation_signature(evaluation : ScenarioEvaluation) -> Int {
  evaluation.scenario.signature() * 37 +
  evaluation.score * 7 +
  (if evaluation.feasible { 1 } else { 0 })
}