///|
/// A diagnostic quality score derived from profile completeness and validation rows.
/// The score is a dashboard signal, not a replacement for the contract gate.
pub(all) struct QualityScore {
  total_cells : Int
  non_empty_cells : Int
  completeness_percent : Int
  error_row_count : Int
  warning_row_count : Int
  score_percent : Int
  grade : String
} derive(Eq, Debug)

///|
/// Calculate a deterministic score for dashboards and benchmark reports.
pub fn quality_score(
  profile : DatasetProfile,
  report : ValidationReport,
) -> QualityScore {
  let total_cells = profile.row_count * profile.columns.length()
  let non_empty_cells = profile.columns.fold(init=0, fn(total, column) {
    total + column.non_empty_count
  })
  let completeness_percent = if total_cells == 0 {
    100
  } else {
    non_empty_cells * 100 / total_cells
  }
  let error_rows = affected_rows(report, Error)
  let warning_rows = affected_rows(report, Warning)
  let error_penalty = row_penalty(error_rows.length(), report.row_count, 100)
  let warning_penalty = row_penalty(warning_rows.length(), report.row_count, 50)
  let score_percent = clamp_score(
    completeness_percent - error_penalty - warning_penalty,
  )
  {
    total_cells,
    non_empty_cells,
    completeness_percent,
    error_row_count: error_rows.length(),
    warning_row_count: warning_rows.length(),
    score_percent,
    grade: quality_grade(score_percent),
  }
}

///|
fn affected_rows(report : ValidationReport, severity : Severity) -> Array[Int] {
  let rows = []
  for result in report.rule_results {
    if result.severity == severity {
      for row in result.failure_rows {
        if !rows.contains(row) {
          rows.push(row)
        }
      }
    }
  }
  rows.sort()
  rows
}

///|
fn row_penalty(row_count : Int, total_rows : Int, weight : Int) -> Int {
  if total_rows == 0 {
    0
  } else {
    row_count * weight / total_rows
  }
}

///|
fn clamp_score(value : Int) -> Int {
  if value < 0 {
    0
  } else if value > 100 {
    100
  } else {
    value
  }
}

///|
fn quality_grade(score : Int) -> String {
  if score >= 95 {
    "A"
  } else if score >= 85 {
    "B"
  } else if score >= 70 {
    "C"
  } else if score >= 60 {
    "D"
  } else {
    "F"
  }
}