///|
/// Iterative long-run semantics.

///|
pub fn[T, S] build_policy_from_metric_symbols(
  objectives : Array[ObjectiveMetricSymbol[T, S]],
  constraints? : Array[ConstraintMetricSymbol[T, S]] = [],
  min_score_delta? : Double = 0.0,
) -> ImprovementPolicy {
  let built_objectives : Array[ImprovementObjective] = []
  for row in objectives {
    built_objectives.push({
      key: row.key,
      direction: row.direction,
      weight: row.weight,
    })
  }

  let built_constraints : Array[ImprovementConstraint] = []
  for row in constraints {
    built_constraints.push({
      key: row.key,
      comparator: row.comparator,
      value: row.value,
      source: row.source,
    })
  }

  {
    objectives: built_objectives,
    constraints: built_constraints,
    min_score_delta,
  }
}

///|
pub fn[T, S] collect_metric_snapshot_by_symbols(
  candidate : ImprovementCandidate[T],
  iteration : Int,
  state : S,
  objectives : Array[ObjectiveMetricSymbol[T, S]],
  constraints? : Array[ConstraintMetricSymbol[T, S]] = [],
) -> Result[MetricSnapshot, String] {
  let metrics : Map[String, Double] = {}

  for objective in objectives {
    let value = match (objective.read)(candidate, iteration, state) {
      Ok(v) => v
      Err(message) => return Err(message)
    }
    metrics.set(objective.key, value)
  }

  for constraint in constraints {
    let value = match (constraint.read)(candidate, iteration, state) {
      Ok(v) => v
      Err(message) => return Err(message)
    }
    metrics.set(constraint.key, value)
  }

  Ok({ metrics, gates: {} })
}

///|
pub fn[T, S] run_long_improvement_loop(
  baseline : MetricSnapshot,
  policy : ImprovementPolicy,
  initial_state : S,
  max_iterations : Int,
  stop_when_no_accept : Bool,
  generate_candidates : (LongRunIterationContext[T, S]) -> Array[
    ImprovementCandidate[T],
  ],
  evaluate : (ImprovementCandidate[T], LongRunIterationContext[T, S]) -> Result[
    MetricSnapshot,
    String,
  ],
  on_accepted : (ImprovementResult[T], S) -> S,
) -> LongRunImprovementReport[T, S] {
  let mut current_baseline = baseline
  let mut current_baseline_score = score_snapshot(baseline, policy)
  let mut current_state = initial_state

  let rounds : Array[ImprovementReport[T]] = []
  let accepted_history : Array[ImprovementResult[T]] = []

  let upper = if max_iterations < 0 { 0 } else { max_iterations }
  for iteration in 0..
        match best.snapshot {
          Some(snapshot) => {
            current_baseline = snapshot
            current_baseline_score = match best.score {
              Some(v) => v
              None => score_snapshot(snapshot, policy)
            }
            current_state = on_accepted(best, current_state)
          }
          None => ()
        }
      None => if stop_when_no_accept { break }
    }
  }

  {
    rounds,
    accepted_history,
    final_baseline: current_baseline,
    final_baseline_score: current_baseline_score,
    final_state: current_state,
  }
}