///|
pub(all) struct DetailedConstraintAudit {
  name : String
  target : Double
  value : Double
  violation : Double
  margin : Double
  passed : Bool
  severity : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct CandidateAudit {
  candidate : DesignCandidate
  constraints : Array[DetailedConstraintAudit]
  feasible : Bool
  total_violation : Double
  minimum_margin : Double
  status : String
} derive(Debug, Eq, ToJson)

///|
pub fn constraint_target(constraint : DesignConstraint) -> Double {
  match constraint.kind {
    AtLeast(target~) => target
    AtMost(target~) => target
    Equal(target~, ..) => target
  }
}

///|
pub fn detailed_constraint_margin(
  constraint : DesignConstraint,
  value : Double,
) -> Double {
  match constraint.kind {
    AtLeast(target~) => value - target
    AtMost(target~) => target - value
    Equal(target~, tolerance~) => tolerance - (value - target).abs()
  }
}

///|
pub fn audit_constraint(
  constraint : DesignConstraint,
  value : Double,
) -> DetailedConstraintAudit {
  let violation = constraint.violation(value)
  let margin = detailed_constraint_margin(constraint, value)
  {
    name: constraint.name,
    target: constraint_target(constraint),
    value,
    violation,
    margin,
    passed: violation <= 0.0,
    severity: if violation <= 0.0 {
      "pass"
    } else if violation <= constraint_target(constraint).abs().max(1.0) * 0.05 {
      "notice"
    } else {
      "fail"
    },
  }
}

///|
pub fn audit_candidate_detailed(
  candidate : DesignCandidate,
  constraints : ArrayView[DesignConstraint],
) -> CandidateAudit raise @domain.ExtractionError {
  if candidate.constraint_values.length() != constraints.length() {
    raise @domain.ExtractionError::InvalidArgument(
      message="candidate audit dimensions differ",
    )
  }
  let audits = []
  let mut minimum = 1.0e30
  let mut total = 0.0
  let mut feasible = true
  for index in 0.. Bool {
  audit.feasible && audit.minimum_margin >= minimum_margin
}

///|
pub fn audit_violations(
  audit : CandidateAudit,
) -> Array[DetailedConstraintAudit] {
  let result = []
  for constraint in audit.constraints {
    if !constraint.passed {
      result.push(constraint)
    }
  }
  result
}

///|
pub fn audit_passed(audit : CandidateAudit) -> Array[DetailedConstraintAudit] {
  let result = []
  for constraint in audit.constraints {
    if constraint.passed {
      result.push(constraint)
    }
  }
  result
}

///|
pub fn audit_worst_constraint(
  audit : CandidateAudit,
) -> DetailedConstraintAudit? {
  if audit.constraints.length() == 0 {
    None
  } else {
    let mut selected = audit.constraints[0]
    for constraint in audit.constraints {
      if constraint.margin < selected.margin {
        selected = constraint
      }
    }
    Some(selected)
  }
}

///|
pub fn audit_summary(audit : CandidateAudit) -> String {
  "status=" +
  audit.status +
  ",constraints=" +
  audit.constraints.length().to_string() +
  ",passed=" +
  audit_passed(audit).length().to_string() +
  ",violations=" +
  audit_violations(audit).length().to_string() +
  ",total_violation=" +
  audit.total_violation.to_string() +
  ",minimum_margin=" +
  audit.minimum_margin.to_string()
}

///|
pub fn audit_markdown(audit : CandidateAudit) -> String {
  let lines = [
    "| Constraint | Target | Value | Margin | Violation | Passed | Severity |", "|---|---:|---:|---:|---:|---|---|",
  ]
  for constraint in audit.constraints {
    lines.push(
      "| " +
      constraint.name +
      " | " +
      constraint.target.to_string() +
      " | " +
      constraint.value.to_string() +
      " | " +
      constraint.margin.to_string() +
      " | " +
      constraint.violation.to_string() +
      " | " +
      constraint.passed.to_string() +
      " | " +
      constraint.severity +
      " |",
    )
  }
  lines.join("\n")
}

///|
pub fn audit_csv(audit : CandidateAudit) -> String {
  let lines = ["name,target,value,violation,margin,passed,severity"]
  for constraint in audit.constraints {
    lines.push(
      constraint.name +
      "," +
      constraint.target.to_string() +
      "," +
      constraint.value.to_string() +
      "," +
      constraint.violation.to_string() +
      "," +
      constraint.margin.to_string() +
      "," +
      constraint.passed.to_string() +
      "," +
      constraint.severity,
    )
  }
  lines.join("\n")
}

///|
pub fn candidate_audits(
  candidates : ArrayView[DesignCandidate],
  constraints : ArrayView[DesignConstraint],
) -> Array[CandidateAudit] raise @domain.ExtractionError {
  let result = []
  for candidate in candidates {
    result.push(audit_candidate_detailed(candidate, constraints))
  }
  result
}

///|
pub fn feasible_audits(
  audits : ArrayView[CandidateAudit],
) -> Array[CandidateAudit] {
  let result = []
  for audit in audits {
    if audit.feasible {
      result.push(audit)
    }
  }
  result
}

///|
pub fn robust_audits(
  audits : ArrayView[CandidateAudit],
  minimum_margin : Double,
) -> Array[CandidateAudit] {
  let result = []
  for audit in audits {
    if audit_is_robust(audit, minimum_margin) {
      result.push(audit)
    }
  }
  result
}

///|
pub fn minimum_candidate_margin(audits : ArrayView[CandidateAudit]) -> Double {
  if audits.length() == 0 {
    return 0.0
  }
  let mut result = audits[0].minimum_margin
  for audit in audits {
    result = result.min(audit.minimum_margin)
  }
  result
}

///|
pub fn maximum_candidate_violation(
  audits : ArrayView[CandidateAudit],
) -> Double {
  let mut result = 0.0
  for audit in audits {
    result = result.max(audit.total_violation)
  }
  result
}

///|
pub fn audit_objective(audit : CandidateAudit) -> Double {
  audit.candidate.objective + audit.total_violation * 1.0e6
}

///|
pub fn best_audit(audits : ArrayView[CandidateAudit]) -> CandidateAudit? {
  if audits.length() == 0 {
    return None
  }
  let mut best = audits[0]
  for audit in audits {
    if audit_objective(audit) < audit_objective(best) {
      best = audit
    }
  }
  Some(best)
}

///|
pub fn audit_constraint_count(audit : CandidateAudit, passed : Bool) -> Int {
  if passed {
    audit_passed(audit).length()
  } else {
    audit_violations(audit).length()
  }
}

///|
pub fn audit_margin_vector(audit : CandidateAudit) -> Array[Double] {
  [
    for constraint in audit.constraints => constraint.margin
  ]
}

///|
pub fn audit_violation_vector(audit : CandidateAudit) -> Array[Double] {
  [
    for constraint in audit.constraints => constraint.violation
  ]
}

///|
pub fn audit_status_code(audit : CandidateAudit) -> Int {
  match audit.status {
    "feasible" => 0
    "near-feasible" => 1
    _ => 2
  }
}

///|
pub fn audit_has_critical_failure(audit : CandidateAudit) -> Bool {
  for constraint in audit.constraints {
    if constraint.severity == "fail" {
      return true
    }
  }
  false
}

///|
pub fn audit_recommendation(audit : CandidateAudit) -> String {
  if audit.feasible && audit.minimum_margin > 0.1 {
    "accept"
  } else if audit.feasible {
    "monitor"
  } else if audit.total_violation < 0.05 {
    "refine"
  } else {
    "reject"
  }
}