///|
pub(all) struct AnalysisSummary {
  total : Int
  errors : Int
  warnings : Int
  infos : Int
  structural : Int
  harmonic : Int
  score : Int
} derive(Debug, Eq, ToJson)

///|
pub fn summarize(analysis : Analysis) -> AnalysisSummary {
  let mut errors = 0
  let mut warnings = 0
  let mut infos = 0
  let mut structural = 0
  let mut harmonic = 0
  for finding in analysis.findings {
    match finding.severity {
      Error => errors += 1
      Warning => warnings += 1
      Info => infos += 1
    }
    if rule_is_structural(finding.rule) {
      structural += 1
    }
    if rule_is_harmonic(finding.rule) {
      harmonic += 1
    }
  }
  let total = errors + warnings + infos
  let score = 100 - errors * 25 - warnings * 5 - infos
  {
    total,
    errors,
    warnings,
    infos,
    structural,
    harmonic,
    score: if score < 0 {
      0
    } else {
      score
    },
  }
}

///|
pub fn AnalysisSummary::passed(self : AnalysisSummary) -> Bool {
  self.errors == 0
}

///|
pub fn AnalysisSummary::grade(self : AnalysisSummary) -> String {
  if self.errors > 0 {
    "fail"
  } else if self.warnings > 0 {
    "review"
  } else {
    "pass"
  }
}

///|
pub fn AnalysisSummary::severity_total(self : AnalysisSummary) -> Int {
  self.errors * 2 + self.warnings + self.infos
}

///|
pub fn finding_count(analysis : Analysis, rule : RuleKind) -> Int {
  let mut result = 0
  for finding in analysis.findings {
    if finding.rule == rule {
      result += 1
    }
  }
  result
}

///|
pub fn findings_for(analysis : Analysis, severity : Severity) -> Array[Finding] {
  let result : Array[Finding] = []
  for finding in analysis.findings {
    if finding.severity == severity {
      result.push(finding)
    }
  }
  result
}

///|
pub fn findings_for_rule(
  analysis : Analysis,
  rule : RuleKind,
) -> Array[Finding] {
  let result : Array[Finding] = []
  for finding in analysis.findings {
    if finding.rule == rule {
      result.push(finding)
    }
  }
  result
}

///|
pub fn highest_severity(analysis : Analysis) -> Severity? {
  let mut result : Severity? = None
  for finding in analysis.findings {
    match result {
      None => result = Some(finding.severity)
      Some(current) =>
        if severity_rank(finding.severity) > severity_rank(current) {
          result = Some(finding.severity)
        }
    }
  }
  result
}

///|
pub fn analysis_is_empty(analysis : Analysis) -> Bool {
  analysis.findings.length() == 0
}

///|
pub fn analysis_has_rule(analysis : Analysis, rule : RuleKind) -> Bool {
  finding_count(analysis, rule) > 0
}

///|
pub fn analysis_indices(analysis : Analysis) -> Array[Int] {
  let result : Array[Int] = []
  for finding in analysis.findings {
    result.push(finding.index)
  }
  result
}

///|
pub fn score_for(analysis : Analysis, profile : RuleProfileConfig) -> Int {
  let summary = summarize(analysis)
  let strictness = if profile.is_valid() {
    profile.max_consecutive_perfect
  } else {
    1
  }
  let raw = 100 - summary.errors * 20 - summary.warnings * strictness
  if raw < 0 {
    0
  } else {
    raw
  }
}

///|
pub fn compare_scores(
  left : Analysis,
  right : Analysis,
  profile : RuleProfileConfig,
) -> Int {
  score_for(left, profile) - score_for(right, profile)
}