///|
/// Evaluate a batch of already computed reviews with the strict default gate.
pub fn evaluate_audit_gate(reviews : Array[HeaderReview]) -> AuditGateResult {
  evaluate_audit_gate_with_policy(reviews, AuditGatePolicy::strict())
}

///|
/// Lint headers and evaluate them with the strict default gate.
pub fn evaluate_headers_gate(headers : Array[String]) -> AuditGateResult {
  evaluate_audit_gate(lint_headers(headers))
}

///|
/// Lint headers with a filename policy and evaluate them with an audit policy.
pub fn evaluate_headers_gate_with_policy(
  headers : Array[String],
  filename_policy : FilenamePolicy,
  audit_policy : AuditGatePolicy,
) -> AuditGateResult {
  evaluate_audit_gate_with_policy(
    lint_headers_with_policy(headers, filename_policy),
    audit_policy,
  )
}

///|
/// Evaluate a batch of reviews against explicit warning, error and repair limits.
pub fn evaluate_audit_gate_with_policy(
  reviews : Array[HeaderReview],
  policy : AuditGatePolicy,
) -> AuditGateResult {
  let summary = summarize_reviews(reviews)
  let reasons : Array[String] = []
  if !policy.allow_rejected_headers && summary.rejected > 0 {
    reasons.push("batch contains rejected headers")
  }
  if summary.errors > policy.max_errors {
    reasons.push("error count exceeds policy limit")
  }
  if summary.warnings > policy.max_warnings {
    reasons.push("warning count exceeds policy limit")
  }
  if !policy.allow_needs_attention && summary.needs_attention > 0 {
    reasons.push("headers need manual attention")
  }
  if !policy.allow_filename_changes && summary.filenames_changed > 0 {
    reasons.push("filenames changed during normalization")
  }
  let accepted = reasons.length() == 0
  let status = if (!policy.allow_rejected_headers && summary.rejected > 0) ||
    summary.errors > policy.max_errors {
    Rejected
  } else if accepted {
    Accepted
  } else {
    NeedsAttention
  }
  { accepted, status, summary, reasons }
}

///|
pub fn AuditGateResult::reason_text(self : AuditGateResult) -> String {
  if self.reasons.is_empty() {
    "ok"
  } else {
    join_strings(self.reasons, "; ")
  }
}

///|
pub fn AuditGateResult::to_text(self : AuditGateResult) -> String {
  let buf = StringBuilder(size_hint=192)
  buf.write_string("accepted=")
  buf.write_string(if self.accepted { "true" } else { "false" })
  buf.write_string(", status=")
  buf.write_string(self.status.name())
  buf.write_string(", ")
  buf.write_string(self.summary.to_text())
  buf.write_string(", reasons=")
  buf.write_string(self.reason_text())
  buf.to_string()
}

///|
pub fn AuditSummary::is_clean(self : AuditSummary) -> Bool {
  self.rejected == 0 &&
  self.needs_attention == 0 &&
  self.warnings == 0 &&
  self.errors == 0 &&
  self.filenames_changed == 0
}

///|
pub fn AuditSummary::has_failures(self : AuditSummary) -> Bool {
  self.rejected > 0 || self.errors > 0
}

///|
pub fn AuditSummary::has_attention_items(self : AuditSummary) -> Bool {
  self.needs_attention > 0 || self.warnings > 0 || self.filenames_changed > 0
}

///|
pub fn AuditSummary::clean_count(self : AuditSummary) -> Int {
  self.accepted
}

///|
pub fn reviews_by_status(
  reviews : Array[HeaderReview],
  status : ReviewStatus,
) -> Array[HeaderReview] {
  let found : Array[HeaderReview] = []
  for review in reviews {
    if review.status == status {
      found.push(review)
    }
  }
  found
}

///|
pub fn reviews_with_filename_changes(
  reviews : Array[HeaderReview],
) -> Array[HeaderReview] {
  let found : Array[HeaderReview] = []
  for review in reviews {
    match review.filename {
      Some(report) => if report.changed { found.push(review) }
      None => ()
    }
  }
  found
}

///|
pub fn reviews_with_recommendation(
  reviews : Array[HeaderReview],
  code : RecommendationCode,
) -> Array[HeaderReview] {
  let found : Array[HeaderReview] = []
  for review in reviews {
    if review.has_recommendation(code) {
      found.push(review)
    }
  }
  found
}

///|
pub fn HeaderReview::has_recommendation(
  self : HeaderReview,
  code : RecommendationCode,
) -> Bool {
  for recommendation in self.recommendations {
    if recommendation.code == code {
      return true
    }
  }
  false
}

///|
pub fn HeaderReview::recommendations_by_severity(
  self : HeaderReview,
  severity : Severity,
) -> Array[Recommendation] {
  let found : Array[Recommendation] = []
  for recommendation in self.recommendations {
    if recommendation.severity == severity {
      found.push(recommendation)
    }
  }
  found
}

///|
fn join_strings(values : Array[String], separator : String) -> String {
  let buf = StringBuilder(size_hint=values.length() * 24)
  let mut first = true
  for value in values {
    if first {
      first = false
    } else {
      buf.write_string(separator)
    }
    buf.write_string(value)
  }
  buf.to_string()
}