///|
pub enum DiagnosticSeverity {
  DiagHint
  DiagWarning
  DiagError
  DiagBlocker
} derive(Debug, Eq)

///|
pub struct Diagnostic {
  code : String
  severity : DiagnosticSeverity
  path : String
  message : String
  remediation : String
} derive(Debug, Eq)

///|
pub fn diagnostic(
  code : String,
  severity : DiagnosticSeverity,
  path : String,
  message : String,
  remediation : String,
) -> Diagnostic {
  { code, severity, path, message, remediation }
}

///|
pub fn Diagnostic::is_blocking(self : Diagnostic) -> Bool {
  self.severity is DiagError || self.severity is DiagBlocker
}

///|
pub fn Diagnostic::level(self : Diagnostic) -> String {
  match self.severity {
    DiagHint => "hint"
    DiagWarning => "warning"
    DiagError => "error"
    DiagBlocker => "blocker"
  }
}

///|
pub struct DiagnosticReport {
  diagnostics : Array[Diagnostic]
  passed : Bool
} derive(Debug, Eq)

///|
pub fn diagnostic_report(diagnostics : Array[Diagnostic]) -> DiagnosticReport {
  let mut passed = true
  for item in diagnostics {
    if item.is_blocking() {
      passed = false
    }
  }
  { diagnostics, passed }
}

///|
pub fn DiagnosticReport::count(self : DiagnosticReport) -> Int {
  self.diagnostics.length()
}

///|
pub fn DiagnosticReport::blocking_count(self : DiagnosticReport) -> Int {
  let mut count = 0
  for item in self.diagnostics {
    if item.is_blocking() {
      count = count + 1
    }
  }
  count
}

///|
pub fn DiagnosticReport::warnings(self : DiagnosticReport) -> Array[Diagnostic] {
  let result : Array[Diagnostic] = []
  for item in self.diagnostics {
    if item.severity is DiagWarning {
      result.push(item)
    }
  }
  result
}

///|
pub fn DiagnosticReport::errors(self : DiagnosticReport) -> Array[Diagnostic] {
  let result : Array[Diagnostic] = []
  for item in self.diagnostics {
    if item.is_blocking() {
      result.push(item)
    }
  }
  result
}

///|
pub fn DiagnosticReport::to_markdown(self : DiagnosticReport) -> String {
  let lines : Array[String] = [
    "| Code | Level | Path | Message | Remediation |", "| --- | --- | --- | --- | --- |",
  ]
  for item in self.diagnostics {
    lines.push(
      "| \{item.code} | \{item.level()} | \{item.path} | \{item.message} | \{item.remediation} |",
    )
  }
  lines.join("\n")
}

///|
pub fn diagnose_report(report : ChemReport) -> DiagnosticReport {
  let diagnostics : Array[Diagnostic] = []
  for issue in report.validate() {
    diagnostics.push(
      diagnostic(
        "REPORT-VALIDATION",
        DiagError,
        issue.path,
        issue.message,
        "complete the missing report field",
      ),
    )
  }
  for warning in report.warnings {
    if warning.severity is Critical {
      diagnostics.push(
        diagnostic(
          warning.code,
          DiagBlocker,
          "warnings",
          warning.summary,
          warning.action,
        ),
      )
    } else if warning.severity is Warning {
      diagnostics.push(
        diagnostic(
          warning.code,
          DiagWarning,
          "warnings",
          warning.summary,
          warning.action,
        ),
      )
    }
  }
  diagnostic_report(diagnostics)
}

///|
pub fn ChemReport::diagnostics(self : ChemReport) -> DiagnosticReport {
  diagnose_report(self)
}

///|
pub fn ChemReport::has_blocking_diagnostics(self : ChemReport) -> Bool {
  !self.diagnostics().passed
}

///|
pub fn compare_reports(
  left : ChemReport,
  right : ChemReport,
) -> Array[Diagnostic] {
  let result : Array[Diagnostic] = []
  if left.metadata.process_unit != right.metadata.process_unit {
    result.push(
      diagnostic(
        "DIFF-PROCESS-UNIT",
        DiagHint,
        "metadata.process_unit",
        "process units differ",
        "compare only like-for-like reports",
      ),
    )
  }
  if left.results.length() != right.results.length() {
    result.push(
      diagnostic(
        "DIFF-RESULT-COUNT",
        DiagWarning,
        "results",
        "result counts differ",
        "review changed calculation scope",
      ),
    )
  }
  if left.warnings.length() != right.warnings.length() {
    result.push(
      diagnostic(
        "DIFF-WARNING-COUNT",
        DiagWarning,
        "warnings",
        "warning counts differ",
        "review new risk signals",
      ),
    )
  }
  result
}

///|
pub fn regression_gate(baseline : ChemReport, candidate : ChemReport) -> Bool {
  compare_reports(baseline, candidate)
  .filter(fn(item) {
    item.severity is DiagBlocker || item.severity is DiagError
  })
  .length() ==
  0
}

///|
pub fn required_sections(report : ChemReport) -> Array[String] {
  let result : Array[String] = []
  if report.assumptions.length() == 0 {
    result.push("assumptions")
  }
  if report.formulas.length() == 0 {
    result.push("formulas")
  }
  if report.sources.length() == 0 {
    result.push("sources")
  }
  if report.sections.length() == 0 {
    result.push("sections")
  }
  result
}

///|
pub fn completeness_score(report : ChemReport) -> Float {
  let total : Float = 8.0
  let mut present : Float = 0.0
  if report.metadata.title.trim().length() > 0 {
    present = present + 1.0
  }
  if report.summary.trim().length() > 0 {
    present = present + 1.0
  }
  if report.assumptions.length() > 0 {
    present = present + 1.0
  }
  if report.inputs.length() > 0 {
    present = present + 1.0
  }
  if report.formulas.length() > 0 {
    present = present + 1.0
  }
  if report.results.length() > 0 {
    present = present + 1.0
  }
  if report.warnings.length() > 0 {
    present = present + 1.0
  }
  if report.sources.length() > 0 {
    present = present + 1.0
  }
  present / total
}