///|
/// One auditable planned operation and its observed verdict.
pub(all) struct AuditEntry {
  feature_id : String
  device_id : String
  strategy : Strategy
  uncertainty : Double
  verdict : Verdict
}

///|
/// Trace of a complete offline planning and observation pass.
pub(all) struct AuditTrace {
  part_id : String
  entries : Array[AuditEntry]
  warnings : Array[String]
  coverage : Double
  capability : Bool
}

///|
fn has_observation(
  feature_id : String,
  observations : Array[Observation],
) -> Bool {
  let mut result = false
  for observation in observations {
    if observation.feature_id == feature_id {
      result = true
    }
  }
  result
}

///|
/// Build an audit trace when only step IDs and observations are available.
pub fn build_audit_trace(
  plan~ : MeasurementPlan,
  observations~ : Array[Observation],
) -> AuditTrace {
  let entries = []
  for step in plan.steps {
    let verdict = if has_observation(step.feature_id, observations) {
      Verdict::Pass
    } else {
      Verdict::Unmeasured
    }
    entries.push({
      feature_id: step.feature_id,
      device_id: step.device_id,
      strategy: step.strategy,
      uncertainty: step.uncertainty,
      verdict,
    })
  }
  {
    part_id: plan.part_id,
    entries,
    warnings: plan.summary.warnings,
    coverage: plan.summary.coverage,
    capability: plan.summary.capability,
  }
}

///|
/// Build a trace with full feature-aware classification.
pub fn build_audit_trace_with_features(
  plan~ : MeasurementPlan,
  features~ : Array[Feature],
  observations~ : Array[Observation],
) -> AuditTrace {
  let entries = []
  for step in plan.steps {
    let mut verdict = Verdict::Unmeasured
    for feature in features {
      if feature.id == step.feature_id {
        let mut observation = None
        for item in observations {
          if item.feature_id == feature.id && observation is None {
            observation = Some(item)
          }
        }
        verdict = classify(feature, observation)
      }
    }
    entries.push({
      feature_id: step.feature_id,
      device_id: step.device_id,
      strategy: step.strategy,
      uncertainty: step.uncertainty,
      verdict,
    })
  }
  {
    part_id: plan.part_id,
    entries,
    warnings: plan.summary.warnings,
    coverage: plan.summary.coverage,
    capability: plan.summary.capability,
  }
}

///|
/// Count passing, failing, and unmeasured entries in a trace.
pub fn audit_verdict_counts(trace : AuditTrace) -> (Int, Int, Int) {
  let mut pass = 0
  let mut fail = 0
  let mut unmeasured = 0
  for entry in trace.entries {
    match entry.verdict {
      Pass => pass += 1
      Fail(_) => fail += 1
      Unmeasured => unmeasured += 1
    }
  }
  (pass, fail, unmeasured)
}