///|
pub(all) enum AuditFindingCode {
  BalanceResidual
  InvalidStream
  EmptyInlet
  EmptyOutlet
  NegativeDuty
} derive(Debug, Eq)

///|
pub struct AuditFinding {
  code : AuditFindingCode
  message : String
  magnitude : Double
} derive(Debug)

///|
pub struct AuditReport {
  accepted : Bool
  finding_count : Int
  residual : Double
  findings : Array[AuditFinding]
} derive(Debug)

///|
pub fn audit_process(
  inlets : Array[Stream],
  outlets : Array[Stream],
  heat_duty_w : Double,
) -> AuditReport {
  let findings : Array[AuditFinding] = []
  if inlets.length() == 0 {
    findings.push({
      code: EmptyInlet,
      message: "no inlet stream",
      magnitude: 0.0,
    })
  }
  if outlets.length() == 0 {
    findings.push({
      code: EmptyOutlet,
      message: "no outlet stream",
      magnitude: 0.0,
    })
  }
  let validation = validate_process(inlets)
  for issue in validation.issues {
    findings.push({
      code: InvalidStream,
      message: issue.message,
      magnitude: 1.0,
    })
  }
  let result = steady_state_balance(
    inlets,
    outlets,
    terms=BalanceTerms(heat=heat_duty_w),
  )
  let residual = result.residual
  if residual.abs() > 1.0e-6 {
    findings.push({
      code: BalanceResidual,
      message: "energy balance residual exceeds tolerance",
      magnitude: residual.abs(),
    })
  }
  if heat_duty_w < 0.0 && inlets.length() == 0 {
    findings.push({
      code: NegativeDuty,
      message: "cooling duty has no inlet basis",
      magnitude: heat_duty_w.abs(),
    })
  }
  {
    accepted: findings.length() == 0,
    finding_count: findings.length(),
    residual,
    findings,
  }
}

///|
pub fn audit_has_code(report : AuditReport, code : AuditFindingCode) -> Bool {
  for finding in report.findings {
    if finding.code == code {
      return true
    }
  }
  false
}

///|
pub fn audit_summary(report : AuditReport) -> String {
  if report.accepted {
    "audit: accepted, residual=\{report.residual} W"
  } else {
    "audit: rejected, findings=\{report.finding_count}, residual=\{report.residual} W"
  }
}

///|
pub fn audit_maximum_magnitude(report : AuditReport) -> Double {
  let mut maximum = 0.0
  for finding in report.findings {
    maximum = maximum.max(finding.magnitude)
  }
  maximum
}

///|
pub fn audit_reports_accepted(reports : Array[AuditReport]) -> Bool {
  for report in reports {
    if !report.accepted {
      return false
    }
  }
  true
}

///|
pub fn audit_report_count_by_code(
  reports : Array[AuditReport],
  code : AuditFindingCode,
) -> Int {
  let mut total = 0
  for report in reports {
    for finding in report.findings {
      if finding.code == code {
        total += 1
      }
    }
  }
  total
}