///|
/// Export-ready validation summaries for downstream review systems.
pub(all) struct ValidationRecord {
  id : String
  metric : String
  measured : Double
  target : Double
  tolerance : Double
  passed : Bool
  status : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct ValidationReport {
  title : String
  records : Array[ValidationRecord]
  passed : Bool
  pass_rate : Double
  maximum_error : Double
} derive(Debug, Eq, ToJson)

///|
pub fn validation_record(
  id : String,
  metric : String,
  measured : Double,
  target : Double,
  tolerance : Double,
) -> ValidationRecord {
  let error = (measured - target).abs()
  {
    id,
    metric,
    measured,
    target,
    tolerance,
    passed: error <= tolerance,
    status: if error <= tolerance {
      "pass"
    } else if error <= tolerance * 2.0 {
      "notice"
    } else {
      "fail"
    },
  }
}

///|
pub fn make_validation_report(
  title : String,
  records : ArrayView[ValidationRecord],
) -> ValidationReport {
  let mut passed = 0
  let mut maximum_error = 0.0
  for record in records {
    if record.passed {
      passed += 1
    }
    maximum_error = maximum_error.max((record.measured - record.target).abs())
  }
  {
    title,
    records: records.to_owned(),
    passed: passed == records.length(),
    pass_rate: if records.length() == 0 {
      0.0
    } else {
      passed.to_double() / records.length().to_double()
    },
    maximum_error,
  }
}

///|
pub fn validation_report_csv(report : ValidationReport) -> String {
  let lines = ["id,metric,measured,target,tolerance,error,passed,status"]
  for record in report.records {
    lines.push(
      record.id +
      "," +
      record.metric +
      "," +
      record.measured.to_string() +
      "," +
      record.target.to_string() +
      "," +
      record.tolerance.to_string() +
      "," +
      (record.measured - record.target).abs().to_string() +
      "," +
      record.passed.to_string() +
      "," +
      record.status,
    )
  }
  lines.join("\n")
}

///|
pub fn validation_report_markdown(report : ValidationReport) -> String {
  let lines = [
    "# " + report.title,
    "",
    "| ID | Metric | Measured | Target | Tolerance | Error | Status |",
    "|---|---|---:|---:|---:|---:|---|",
  ]
  for record in report.records {
    lines.push(
      "| " +
      record.id +
      " | " +
      record.metric +
      " | " +
      record.measured.to_string() +
      " | " +
      record.target.to_string() +
      " | " +
      record.tolerance.to_string() +
      " | " +
      (record.measured - record.target).abs().to_string() +
      " | " +
      record.status +
      " |",
    )
  }
  lines.push("")
  lines.push("Pass rate: " + report.pass_rate.to_string())
  lines.push("Maximum error: " + report.maximum_error.to_string())
  lines.join("\n")
}

///|
pub fn failed_validation_records(
  report : ValidationReport,
) -> Array[ValidationRecord] {
  let result = []
  for record in report.records {
    if !record.passed {
      result.push(record)
    }
  }
  result
}

///|
pub fn validation_report_status(report : ValidationReport) -> String {
  if report.passed {
    "passed"
  } else if report.pass_rate >= 0.8 {
    "review"
  } else {
    "failed"
  }
}

///|
pub fn validation_report_json(report : ValidationReport) -> String {
  let records = []
  for record in report.records {
    records.push(
      "{\"id\":\"" +
      record.id +
      "\",\"metric\":\"" +
      record.metric +
      "\",\"measured\":" +
      record.measured.to_string() +
      ",\"target\":" +
      record.target.to_string() +
      ",\"tolerance\":" +
      record.tolerance.to_string() +
      ",\"passed\":" +
      record.passed.to_string() +
      "}",
    )
  }
  "{\"title\":\"" +
  report.title +
  "\",\"passed\":" +
  report.passed.to_string() +
  ",\"pass_rate\":" +
  report.pass_rate.to_string() +
  ",\"records\":[" +
  records.join(",") +
  "]}"
}

///|
pub fn merge_validation_reports(
  first : ValidationReport,
  second : ValidationReport,
) -> ValidationReport {
  let records = first.records.copy()
  records.append(second.records)
  make_validation_report(first.title, records)
}

///|
pub fn validation_record_error(record : ValidationRecord) -> Double {
  (record.measured - record.target).abs()
}

///|
pub fn validation_record_relative_error(record : ValidationRecord) -> Double {
  if record.target == 0.0 {
    validation_record_error(record)
  } else {
    (record.measured - record.target).abs() / record.target.abs()
  }
}

///|
pub fn validation_records_by_status(
  report : ValidationReport,
  status : String,
) -> Array[ValidationRecord] {
  let result = []
  for record in report.records {
    if record.status == status {
      result.push(record)
    }
  }
  result
}

///|
pub fn validation_report_has_notices(report : ValidationReport) -> Bool {
  for record in report.records {
    if record.status == "notice" {
      return true
    }
  }
  false
}

///|
pub fn validation_report_recommendation(report : ValidationReport) -> String {
  if report.passed {
    "release"
  } else if report.pass_rate >= 0.9 && !validation_report_has_notices(report) {
    "release-with-monitoring"
  } else if report.pass_rate >= 0.75 {
    "extend-validation"
  } else {
    "recalibrate"
  }
}

///|
pub fn validation_report_summary(report : ValidationReport) -> String {
  "title=" +
  report.title +
  ",status=" +
  validation_report_status(report) +
  ",recommendation=" +
  validation_report_recommendation(report) +
  ",records=" +
  report.records.length().to_string() +
  ",pass_rate=" +
  report.pass_rate.to_string()
}

///|
pub fn validation_report_error_vector(
  report : ValidationReport,
) -> Array[Double] {
  [
    for record in report.records => validation_record_error(record)
  ]
}

///|
pub fn validation_report_relative_error_vector(
  report : ValidationReport,
) -> Array[Double] {
  [
    for record in report.records => validation_record_relative_error(record)
  ]
}

///|
pub fn validation_report_max_relative_error(
  report : ValidationReport,
) -> Double {
  let mut result = 0.0
  for record in report.records {
    result = result.max(validation_record_relative_error(record))
  }
  result
}

///|
pub fn validation_report_min_margin(report : ValidationReport) -> Double {
  if report.records.length() == 0 {
    return 0.0
  }
  let mut margin = 1.0e30
  for record in report.records {
    let current = record.tolerance - validation_record_error(record)
    margin = margin.min(current)
  }
  margin
}

///|
pub fn validation_report_critical(report : ValidationReport) -> Bool {
  report.passed && validation_report_min_margin(report) >= 0.0
}

///|
pub fn validation_report_record(
  report : ValidationReport,
  id : String,
) -> ValidationRecord? {
  for record in report.records {
    if record.id == id {
      return Some(record)
    }
  }
  None
}

///|
pub fn validation_report_metric_names(
  report : ValidationReport,
) -> Array[String] {
  [
    for record in report.records => record.metric
  ]
}

///|
pub fn validation_report_passed_ids(report : ValidationReport) -> Array[String] {
  let ids = []
  for record in report.records {
    if record.passed {
      ids.push(record.id)
    }
  }
  ids
}

///|
pub fn validation_report_status_counts(
  report : ValidationReport,
) -> (Int, Int, Int) {
  let mut pass = 0
  let mut notice = 0
  let mut fail = 0
  for record in report.records {
    match record.status {
      "pass" => pass += 1
      "notice" => notice += 1
      _ => fail += 1
    }
  }
  (pass, notice, fail)
}

///|
pub fn validation_report_is_complete(
  report : ValidationReport,
  expected_count : Int,
) -> Bool {
  expected_count > 0 && report.records.length() == expected_count
}

///|
pub fn validation_report_target_vector(
  report : ValidationReport,
) -> Array[Double] {
  [
    for record in report.records => record.target
  ]
}

///|
pub fn validation_report_measured_vector(
  report : ValidationReport,
) -> Array[Double] {
  [
    for record in report.records => record.measured
  ]
}

///|
pub fn validation_report_target_error_pairs(
  report : ValidationReport,
) -> Array[(Double, Double)] {
  [
    for record in report.records => {
      (record.target, record.measured - record.target)
    }
  ]
}

///|
pub fn validation_report_all_within(
  report : ValidationReport,
  tolerance_multiplier : Double,
) -> Bool {
  for record in report.records {
    if validation_record_error(record) > record.tolerance * tolerance_multiplier {
      return false
    }
  }
  true
}

///|
pub fn validation_report_ready_for_review(report : ValidationReport) -> Bool {
  report.records.length() > 0 &&
  validation_report_status(report) != "failed" &&
  validation_report_all_within(report, 2.0)
}

///|
/// Production validation gate used by release pipelines.
pub fn validation_report_record_count(report : ValidationReport) -> Int {
  report.records.length()
}