///|
/// Summary of a validation pass.
pub(all) struct ValidationSummary {
  error_count : Int
  warning_count : Int
  is_valid : Bool
  first_error_code : String
}

///|
fn feature_exists(features : Array[Feature], id : String) -> Bool {
  let mut result = false
  for feature in features {
    if feature.id == id {
      result = true
    }
  }
  result
}

///|
/// Validate observation identity and sample counts against a feature set.
pub fn validate_observations(
  features : Array[Feature],
  observations : Array[Observation],
) -> Array[Diagnostic] {
  let diagnostics = []
  for i, observation in observations {
    if !sample_count_is_valid(observation.sample_count) {
      diagnostics.push(
        error_diagnostic(
          code="observation.samples.invalid",
          message="observation sample count must be positive",
          index=i,
        ),
      )
    }
    if !feature_exists(features, observation.feature_id) {
      diagnostics.push(
        error_diagnostic(
          code="observation.feature.unknown",
          message="observation refers to an unknown feature",
          index=i,
        ),
      )
    }
  }
  diagnostics
}

///|
/// Validate that plan steps refer to the supplied features and summary.
pub fn validate_plan(
  plan : MeasurementPlan,
  features : Array[Feature],
) -> Array[Diagnostic] {
  let diagnostics = []
  for i, step in plan.steps {
    if !feature_exists(features, step.feature_id) {
      diagnostics.push(
        error_diagnostic(
          code="plan.feature.unknown",
          message="plan step refers to an unknown feature",
          index=i,
        ),
      )
    }
    if step.device_id.trim().length() == 0 {
      diagnostics.push(
        error_diagnostic(
          code="plan.device.empty",
          message="plan step has no device identifier",
          index=i,
        ),
      )
    }
    if step.uncertainty < 0.0 {
      diagnostics.push(
        error_diagnostic(
          code="plan.uncertainty.negative",
          message="plan uncertainty must not be negative",
          index=i,
        ),
      )
    }
  }
  if plan.summary.steps != plan.steps.length() {
    diagnostics.push(
      error_diagnostic(
        code="plan.summary.steps.mismatch",
        message="plan summary step count differs from steps",
        index=-1,
      ),
    )
  }
  if plan.summary.coverage < 0.0 || plan.summary.coverage > 1.0 {
    diagnostics.push(
      error_diagnostic(
        code="plan.coverage.invalid",
        message="plan coverage must be between zero and one",
        index=-1,
      ),
    )
  }
  diagnostics
}

///|
/// Validate a batch summary's internal counts and rates.
pub fn validate_batch_summary(summary : BatchSummary) -> Array[Diagnostic] {
  let diagnostics = []
  if summary.count < 0 ||
    summary.pass_count < 0 ||
    summary.fail_count < 0 ||
    summary.unmeasured_count < 0 {
    diagnostics.push(
      error_diagnostic(
        code="batch.count.negative",
        message="batch counts must not be negative",
        index=-1,
      ),
    )
  }
  if summary.pass_count + summary.fail_count + summary.unmeasured_count !=
    summary.count {
    diagnostics.push(
      error_diagnostic(
        code="batch.count.mismatch",
        message="batch verdict counts do not sum to count",
        index=-1,
      ),
    )
  }
  if summary.defect_rate < 0.0 || summary.defect_rate > 1.0 {
    diagnostics.push(
      error_diagnostic(
        code="batch.defect_rate.invalid",
        message="batch defect rate must be between zero and one",
        index=-1,
      ),
    )
  }
  diagnostics
}

///|
/// Summarize severity counts and the first error code.
pub fn validation_summary(diagnostics : Array[Diagnostic]) -> ValidationSummary {
  let error_count = diagnostic_count(diagnostics, DiagnosticSeverity::Error)
  let warning_count = diagnostic_count(diagnostics, DiagnosticSeverity::Warning)
  let mut first_error_code = ""
  for diagnostic in diagnostics {
    if diagnostic.severity == DiagnosticSeverity::Error &&
      first_error_code == "" {
      first_error_code = diagnostic.code
    }
  }
  { error_count, warning_count, is_valid: error_count == 0, first_error_code }
}

///|
/// Return whether diagnostics include a code.
pub fn diagnostics_have_code(
  diagnostics : Array[Diagnostic],
  code : String,
) -> Bool {
  let mut result = false
  for diagnostic in diagnostics {
    if diagnostic.code == code {
      result = true
    }
  }
  result
}

///|
/// Return all diagnostic codes in source order.
pub fn diagnostic_codes(diagnostics : Array[Diagnostic]) -> Array[String] {
  let result = []
  for diagnostic in diagnostics {
    result.push(diagnostic.code)
  }
  result
}

///|
/// Return whether a validation result can be used for planning.
pub fn validation_can_plan(diagnostics : Array[Diagnostic]) -> Bool {
  !has_errors(diagnostics)
}

///|
/// Return a stable validation status label.
pub fn validation_status(diagnostics : Array[Diagnostic]) -> String {
  if has_errors(diagnostics) {
    "invalid"
  } else if diagnostics.length() > 0 {
    "review"
  } else {
    "valid"
  }
}