///|
/// Plan-level counts useful for effort estimation and audit dashboards.
pub(all) struct PlanMetrics {
  step_count : Int
  single_count : Int
  repeated_count : Int
  scan_count : Int
  device_count : Int
  device_switches : Int
  estimated_samples : Int
  estimated_seconds : Double
  total_uncertainty : Double
  average_uncertainty : Double
  maximum_uncertainty : Double
  minimum_uncertainty : Double
  datum_count : Int
  normal_count : Int
  warning_count : Int
}

///|
fn metric_strategy_sample_count(strategy : Strategy) -> Int {
  match strategy {
    SinglePoint => 1
    RepeatedPoint(count~) => if count > 0 { count } else { 0 }
    Scan(count~) => if count > 0 { count } else { 0 }
  }
}

///|
fn strategy_seconds(strategy : Strategy) -> Double {
  match strategy {
    SinglePoint => 1.0
    RepeatedPoint(count~) => count.to_double() * 1.5
    Scan(count~) => count.to_double() * 2.0
  }
}

///|
fn unique_step_devices(steps : Array[MeasurementStep]) -> Array[String] {
  let result = []
  for step in steps {
    let mut seen = false
    for id in result {
      if id == step.device_id {
        seen = true
      }
    }
    if !seen {
      result.push(step.device_id)
    }
  }
  result
}

///|
/// Calculate deterministic effort metrics for a plan.
pub fn plan_metrics(plan : MeasurementPlan) -> PlanMetrics {
  let mut single_count = 0
  let mut repeated_count = 0
  let mut scan_count = 0
  let mut estimated_samples = 0
  let mut estimated_seconds = 0.0
  let mut total_uncertainty = 0.0
  let mut maximum_uncertainty = 0.0
  let mut minimum_uncertainty = 0.0
  let mut datum_count = 0
  let mut normal_count = 0
  let mut first = true
  for step in plan.steps {
    match step.strategy {
      SinglePoint => single_count += 1
      RepeatedPoint(_) => repeated_count += 1
      Scan(_) => scan_count += 1
    }
    estimated_samples += metric_strategy_sample_count(step.strategy)
    estimated_seconds += strategy_seconds(step.strategy)
    total_uncertainty += step.uncertainty
    if first {
      minimum_uncertainty = step.uncertainty
      maximum_uncertainty = step.uncertainty
      first = false
    } else {
      if step.uncertainty < minimum_uncertainty {
        minimum_uncertainty = step.uncertainty
      }
      if step.uncertainty > maximum_uncertainty {
        maximum_uncertainty = step.uncertainty
      }
    }
    if step.direction == "datum-aligned" {
      datum_count += 1
    } else {
      normal_count += 1
    }
  }
  let step_count = plan.steps.length()
  let average_uncertainty = if step_count == 0 {
    0.0
  } else {
    total_uncertainty / step_count.to_double()
  }
  {
    step_count,
    single_count,
    repeated_count,
    scan_count,
    device_count: unique_step_devices(plan.steps).length(),
    device_switches: device_switch_count(plan.steps),
    estimated_samples,
    estimated_seconds,
    total_uncertainty,
    average_uncertainty,
    maximum_uncertainty,
    minimum_uncertainty,
    datum_count,
    normal_count,
    warning_count: plan.summary.warnings.length(),
  }
}

///|
/// Return the total number of planned samples.
pub fn planned_sample_count(plan : MeasurementPlan) -> Int {
  plan_metrics(plan).estimated_samples
}

///|
/// Return estimated execution time in abstract seconds.
pub fn estimated_execution_seconds(plan : MeasurementPlan) -> Double {
  plan_metrics(plan).estimated_seconds
}

///|
/// Return the fraction of steps that establish datums.
pub fn datum_step_ratio(plan : MeasurementPlan) -> Double {
  let metrics = plan_metrics(plan)
  if metrics.step_count == 0 {
    0.0
  } else {
    metrics.datum_count.to_double() / metrics.step_count.to_double()
  }
}