///|
/// Objective used to choose a deterministic schedule policy.
pub(all) enum OptimizationGoal {
  MinimizeDevices
  MinimizeTime
  MinimizeUncertainty
} derive(Eq, Debug)

///|
/// Baseline and optimized schedule with a transparent score.
pub(all) struct OptimizationResult {
  goal : OptimizationGoal
  schedule : ScheduledPlan
  baseline_score : Double
  optimized_score : Double
  improvement : Double
}

///|
/// Return a stable optimization goal label.
pub fn optimization_goal_text(goal : OptimizationGoal) -> String {
  match goal {
    MinimizeDevices => "minimize-devices"
    MinimizeTime => "minimize-time"
    MinimizeUncertainty => "minimize-uncertainty"
  }
}

///|
fn policy_for_goal(goal : OptimizationGoal) -> SchedulePolicy {
  match goal {
    MinimizeDevices =>
      schedule_policy(datum_first=true, group_by_device=true, max_batch_size=32)
    MinimizeTime =>
      schedule_policy(datum_first=true, group_by_device=true, max_batch_size=32)
    MinimizeUncertainty =>
      schedule_policy(
        datum_first=true,
        group_by_device=false,
        max_batch_size=16,
      )
  }
}

///|
/// Score a schedule according to its declared objective.
pub fn optimization_score(result : OptimizationResult) -> Double {
  match result.goal {
    MinimizeDevices => 1.0 / (1.0 + result.schedule.device_switches.to_double())
    MinimizeTime => 1.0 / (1.0 + result.schedule.estimated_seconds)
    MinimizeUncertainty => {
      let metrics = plan_metrics(MeasurementPlan::{
        part_id: result.schedule.part_id,
        steps: result.schedule.steps,
        summary: {
          coverage: 1.0,
          capability: true,
          steps: result.schedule.steps.length(),
          warnings: [],
        },
      })
      1.0 / (1.0 + metrics.average_uncertainty)
    }
  }
}

///|
/// Optimize execution grouping without changing measurements or devices.
pub fn optimize_plan(
  plan : MeasurementPlan,
  goal : OptimizationGoal,
) -> OptimizationResult {
  let baseline = schedule_plan(plan, default_schedule_policy())
  let optimized = schedule_plan(plan, policy_for_goal(goal))
  let baseline_result = OptimizationResult::{
    goal,
    schedule: baseline,
    baseline_score: 0.0,
    optimized_score: 0.0,
    improvement: 0.0,
  }
  let baseline_score = optimization_score(baseline_result)
  let optimized_result = OptimizationResult::{
    goal,
    schedule: optimized,
    baseline_score,
    optimized_score: 0.0,
    improvement: 0.0,
  }
  let optimized_score = optimization_score(optimized_result)
  {
    goal,
    schedule: optimized,
    baseline_score,
    optimized_score,
    improvement: optimized_score - baseline_score,
  }
}

///|
/// Return estimated samples per abstract execution second.
pub fn plan_efficiency(plan : MeasurementPlan) -> Double {
  let metrics = plan_metrics(plan)
  if metrics.estimated_seconds == 0.0 {
    0.0
  } else {
    metrics.estimated_samples.to_double() / metrics.estimated_seconds
  }
}

///|
/// Count plan steps that cannot execute or carry warnings.
pub fn plan_waste_count(plan : MeasurementPlan) -> Int {
  plan.summary.warnings.length()
}

///|
/// Return whether an optimization improves its declared score.
pub fn optimization_improves(result : OptimizationResult) -> Bool {
  result.optimized_score >= result.baseline_score
}

///|
/// Return the number of schedule batches after optimization.
pub fn optimized_batch_count(result : OptimizationResult) -> Int {
  result.schedule.batches.length()
}

///|
/// Return the score difference rounded conceptually as a raw deterministic value.
pub fn optimization_delta(result : OptimizationResult) -> Double {
  result.optimized_score - result.baseline_score
}