///|
/// Ordered results for all requested features.
pub(all) struct ResultCollection {
  feature_ids : Array[String]
  results : Array[FeatureResult]
  pass_count : Int
  fail_count : Int
  unmeasured_count : Int
}

///|
fn first_observation(
  feature_id : String,
  observations : Array[Observation],
) -> Observation? {
  let mut result = None
  for observation in observations {
    if observation.feature_id == feature_id && result is None {
      result = Some(observation)
    }
  }
  result
}

///|
/// Collect one feature result per input feature in source order.
pub fn collect_feature_results(
  features : Array[Feature],
  observations : Array[Observation],
) -> ResultCollection {
  let feature_ids = []
  let results = []
  let mut pass_count = 0
  let mut fail_count = 0
  let mut unmeasured_count = 0
  for feature in features {
    let result = feature_result(
      feature~,
      observation=first_observation(feature.id, observations),
    )
    feature_ids.push(feature.id)
    results.push(result)
    match result.verdict {
      Pass => pass_count += 1
      Fail(_) => fail_count += 1
      Unmeasured => unmeasured_count += 1
    }
  }
  { feature_ids, results, pass_count, fail_count, unmeasured_count }
}

///|
/// Find the first result for a feature identifier.
pub fn result_for_feature(
  collection : ResultCollection,
  feature_id : String,
) -> FeatureResult? {
  let mut result = None
  for item in collection.results {
    if item.feature_id == feature_id && result is None {
      result = Some(item)
    }
  }
  result
}

///|
/// Return result coverage as a fraction of requested features.
pub fn result_coverage(collection : ResultCollection) -> Double {
  let total = collection.results.length()
  if total == 0 {
    0.0
  } else {
    (collection.pass_count + collection.fail_count).to_double() /
    total.to_double()
  }
}

///|
/// Return whether every feature has a classified result.
pub fn result_is_complete(collection : ResultCollection) -> Bool {
  collection.results.length() > 0 && collection.unmeasured_count == 0
}

///|
/// Return whether every classified feature passed.
pub fn result_is_clean(collection : ResultCollection) -> Bool {
  result_is_complete(collection) && collection.fail_count == 0
}

///|
/// Return the worst result by fail/unmeasured/pass priority.
pub fn worst_feature_result(collection : ResultCollection) -> FeatureResult? {
  let mut result = None
  for item in collection.results {
    match result {
      None => result = Some(item)
      Some(current) => {
        let worse = worse_verdict(current.verdict, item.verdict)
        if verdict_code(worse) == verdict_code(item.verdict) {
          result = Some(item)
        }
      }
    }
  }
  result
}

///|
/// Return the number of high-risk feature results.
pub fn result_high_risk_count(collection : ResultCollection) -> Int {
  let mut result = 0
  for item in collection.results {
    if item.risk == RiskLevel::High {
      result += 1
    }
  }
  result
}

///|
/// Render stable collection counts.
pub fn render_result_collection(collection : ResultCollection) -> String {
  "features=" +
  collection.results.length().to_string() +
  " pass=" +
  collection.pass_count.to_string() +
  " fail=" +
  collection.fail_count.to_string() +
  " unmeasured=" +
  collection.unmeasured_count.to_string() +
  " coverage=" +
  result_coverage(collection).to_string()
}

///|
/// Return whether a collection has a hard failure.
pub fn result_has_failure(collection : ResultCollection) -> Bool {
  collection.fail_count > 0
}

///|
/// Return the number of result records.
pub fn result_count(collection : ResultCollection) -> Int {
  collection.results.length()
}