///|
/// Quality gate policy for baseline/candidate CSV comparisons.
///
/// This is designed for CI and release checks: the drift report explains what
/// changed, while the gate decides whether the candidate CSV should pass.
pub(all) struct CsvQualityGateOptions {
  min_score : Int
  max_score_drop : Int
  max_quality_issues : Int
  allow_new_columns : Bool
  allow_removed_columns : Bool
  allow_type_changes : Bool
  allow_missing_increase : Bool
} derive(Eq, Debug)

///|
/// One policy violation or advisory emitted by the quality gate.
pub(all) struct CsvQualityGateIssue {
  severity : String
  rule : String
  column : String
  message : String
} derive(Eq, Debug)

///|
/// Quality gate decision for a candidate CSV compared with a baseline CSV.
pub(all) struct CsvQualityGateReport {
  passed : Bool
  options : CsvQualityGateOptions
  drift : CsvDriftReport
  issues : Array[CsvQualityGateIssue]
} derive(Eq, Debug)

///|
/// Default policy: practical for data-regression checks in CI.
pub fn quality_gate_default_options() -> CsvQualityGateOptions {
  {
    min_score: 80,
    max_score_drop: 10,
    max_quality_issues: 5,
    allow_new_columns: true,
    allow_removed_columns: false,
    allow_type_changes: false,
    allow_missing_increase: false,
  }
}

///|
/// Strict policy: useful for stable configuration tables and release assets.
pub fn quality_gate_strict_options() -> CsvQualityGateOptions {
  {
    min_score: 90,
    max_score_drop: 5,
    max_quality_issues: 0,
    allow_new_columns: false,
    allow_removed_columns: false,
    allow_type_changes: false,
    allow_missing_increase: false,
  }
}

///|
/// Run a quality gate with explicit options.
pub fn audit_quality_gate(
  baseline_input : String,
  candidate_input : String,
  options : CsvQualityGateOptions,
) -> CsvQualityGateReport {
  let drift = audit_drift(baseline_input, candidate_input)
  let issues : Array[CsvQualityGateIssue] = Array::new()
  gate_collect_score_issues(issues, drift, options)
  gate_collect_candidate_issues(issues, drift, options)
  gate_collect_drift_issues(issues, drift, options)
  { passed: gate_passed(issues), options, drift, issues }
}

///|
/// Run the default quality gate.
pub fn audit_quality_gate_default(
  baseline_input : String,
  candidate_input : String,
) -> CsvQualityGateReport {
  audit_quality_gate(
    baseline_input,
    candidate_input,
    quality_gate_default_options(),
  )
}

///|
/// Run the default quality gate and render Markdown.
pub fn audit_quality_gate_markdown(
  baseline_input : String,
  candidate_input : String,
) -> String {
  audit_quality_gate_report_markdown(
    audit_quality_gate_default(baseline_input, candidate_input),
  )
}

///|
/// Run the default quality gate and render JSON.
pub fn audit_quality_gate_json(
  baseline_input : String,
  candidate_input : String,
) -> String {
  audit_quality_gate_report_json(
    audit_quality_gate_default(baseline_input, candidate_input),
  )
}

///|
/// Render a compact one-line quality gate summary.
pub fn audit_quality_gate_text(report : CsvQualityGateReport) -> String {
  let status = if report.passed { "pass" } else { "fail" }
  "gate: \{status}, baseline_score=\{report.drift.baseline_score.score}, candidate_score=\{report.drift.candidate_score.score}, score_delta=\{report.drift.score_delta}, issues=\{report.issues.length()}"
}

///|
/// Render a quality gate report as Markdown.
pub fn audit_quality_gate_report_markdown(
  report : CsvQualityGateReport,
) -> String {
  let out = StringBuilder()
  out.write_string("# CSV Quality Gate Report\n\n")
  out.write_string("## Summary\n\n")
  out.write_string("- Status: ")
  out.write_string(if report.passed { "pass" } else { "fail" })
  out.write_char('\n')
  out.write_string(
    "- Baseline score: \{report.drift.baseline_score.score} (\{report.drift.baseline_score.grade})\n",
  )
  out.write_string(
    "- Candidate score: \{report.drift.candidate_score.score} (\{report.drift.candidate_score.grade})\n",
  )
  out.write_string(
    "- Score delta: \{gate_signed_int(report.drift.score_delta)}\n",
  )
  out.write_string("- Gate issues: \{report.issues.length()}\n")
  out.write_string("\n## Policy\n\n")
  out.write_string("- Minimum candidate score: \{report.options.min_score}\n")
  out.write_string(
    "- Maximum allowed score drop: \{report.options.max_score_drop}\n",
  )
  out.write_string(
    "- Maximum candidate quality issues: \{report.options.max_quality_issues}\n",
  )
  out.write_string(
    "- Allow new columns: \{gate_bool_text(report.options.allow_new_columns)}\n",
  )
  out.write_string(
    "- Allow removed columns: \{gate_bool_text(report.options.allow_removed_columns)}\n",
  )
  out.write_string(
    "- Allow type changes: \{gate_bool_text(report.options.allow_type_changes)}\n",
  )
  out.write_string(
    "- Allow missing-value increases: \{gate_bool_text(report.options.allow_missing_increase)}\n",
  )
  out.write_string("\n## Gate Issues\n\n")
  if report.issues.length() == 0 {
    out.write_string("No quality gate violations.\n")
  } else {
    out.write_string("| severity | rule | column | message |\n")
    out.write_string("| --- | --- | --- | --- |\n")
    for issue in report.issues {
      out.write_string("| ")
      gate_write_markdown_cell(out, issue.severity)
      out.write_string(" | ")
      gate_write_markdown_cell(out, issue.rule)
      out.write_string(" | ")
      gate_write_markdown_cell(out, issue.column)
      out.write_string(" | ")
      gate_write_markdown_cell(out, issue.message)
      out.write_string(" |\n")
    }
  }
  out.to_string()
}

///|
/// Render a quality gate report as compact JSON.
pub fn audit_quality_gate_report_json(report : CsvQualityGateReport) -> String {
  let out = StringBuilder()
  out.write_char('{')
  out.write_string("\"passed\":")
  gate_write_json_bool(out, report.passed)
  out.write_string(",\"summary\":{")
  out.write_string("\"baseline_score\":\{report.drift.baseline_score.score}")
  out.write_string(",\"candidate_score\":\{report.drift.candidate_score.score}")
  out.write_string(",\"score_delta\":\{report.drift.score_delta}")
  out.write_string(
    ",\"baseline_rows\":\{report.drift.baseline.table.rows.length()}",
  )
  out.write_string(
    ",\"candidate_rows\":\{report.drift.candidate.table.rows.length()}",
  )
  out.write_string(",\"row_delta\":\{report.drift.row_delta}")
  out.write_string(
    ",\"baseline_columns\":\{report.drift.baseline.table.headers.length()}",
  )
  out.write_string(
    ",\"candidate_columns\":\{report.drift.candidate.table.headers.length()}",
  )
  out.write_string(",\"column_delta\":\{report.drift.column_delta}")
  out.write_string(",\"gate_issue_count\":\{report.issues.length()}")
  out.write_char('}')
  out.write_string(",\"options\":")
  gate_write_options_json(out, report.options)
  out.write_string(",\"issues\":")
  gate_write_issues_json(out, report.issues)
  out.write_string(",\"drift_issue_count\":\{report.drift.issues.length()}")
  out.write_char('}')
  out.to_string()
}

///|
fn gate_collect_score_issues(
  issues : Array[CsvQualityGateIssue],
  drift : CsvDriftReport,
  options : CsvQualityGateOptions,
) -> Unit {
  if drift.candidate_score.score < options.min_score {
    issues.push({
      severity: "error",
      rule: "min_score",
      column: "*",
      message: "Candidate quality score \{drift.candidate_score.score} is below the required minimum \{options.min_score}.",
    })
  }
  if drift.score_delta < 0 - options.max_score_drop {
    issues.push({
      severity: "error",
      rule: "max_score_drop",
      column: "*",
      message: "Candidate quality score dropped by \{0 - drift.score_delta} point(s), exceeding the allowed drop of \{options.max_score_drop}.",
    })
  }
}

///|
fn gate_collect_candidate_issues(
  issues : Array[CsvQualityGateIssue],
  drift : CsvDriftReport,
  options : CsvQualityGateOptions,
) -> Unit {
  if drift.candidate.parse_issues.length() > 0 {
    issues.push({
      severity: "error",
      rule: "parse_issues",
      column: "*",
      message: "Candidate CSV has \{drift.candidate.parse_issues.length()} parse issue(s); fix structural issues before release.",
    })
  }
  if drift.candidate.quality_issues.length() > options.max_quality_issues {
    issues.push({
      severity: "error",
      rule: "max_quality_issues",
      column: "*",
      message: "Candidate CSV has \{drift.candidate.quality_issues.length()} quality issue(s), exceeding the allowed maximum \{options.max_quality_issues}.",
    })
  }
}

///|
fn gate_collect_drift_issues(
  issues : Array[CsvQualityGateIssue],
  drift : CsvDriftReport,
  options : CsvQualityGateOptions,
) -> Unit {
  for issue in drift.issues {
    if issue.category == "schema" {
      if issue.message.contains("was removed") && !options.allow_removed_columns {
        gate_push_from_drift(issues, "error", "no_removed_columns", issue)
      } else if issue.message.contains("was added") &&
        !options.allow_new_columns {
        gate_push_from_drift(issues, "error", "no_new_columns", issue)
      }
    } else if issue.category == "type" && !options.allow_type_changes {
      gate_push_from_drift(issues, "error", "no_type_changes", issue)
    } else if issue.category == "missing" &&
      issue.message.contains("increased") &&
      !options.allow_missing_increase {
      gate_push_from_drift(issues, "error", "no_missing_increase", issue)
    }
  }
}

///|
fn gate_push_from_drift(
  issues : Array[CsvQualityGateIssue],
  severity : String,
  rule : String,
  issue : CsvDriftIssue,
) -> Unit {
  issues.push({ severity, rule, column: issue.column, message: issue.message })
}

///|
fn gate_passed(issues : Array[CsvQualityGateIssue]) -> Bool {
  for issue in issues {
    if issue.severity == "error" {
      return false
    }
  }
  true
}

///|
fn gate_write_options_json(
  out : StringBuilder,
  options : CsvQualityGateOptions,
) -> Unit {
  out.write_char('{')
  out.write_string("\"min_score\":\{options.min_score}")
  out.write_string(",\"max_score_drop\":\{options.max_score_drop}")
  out.write_string(",\"max_quality_issues\":\{options.max_quality_issues}")
  out.write_string(",\"allow_new_columns\":")
  gate_write_json_bool(out, options.allow_new_columns)
  out.write_string(",\"allow_removed_columns\":")
  gate_write_json_bool(out, options.allow_removed_columns)
  out.write_string(",\"allow_type_changes\":")
  gate_write_json_bool(out, options.allow_type_changes)
  out.write_string(",\"allow_missing_increase\":")
  gate_write_json_bool(out, options.allow_missing_increase)
  out.write_char('}')
}

///|
fn gate_write_issues_json(
  out : StringBuilder,
  issues : Array[CsvQualityGateIssue],
) -> Unit {
  out.write_char('[')
  for i in 0.. 0 {
      out.write_char(',')
    }
    let issue = issues[i]
    out.write_char('{')
    out.write_string("\"severity\":")
    gate_write_json_string(out, issue.severity)
    out.write_string(",\"rule\":")
    gate_write_json_string(out, issue.rule)
    out.write_string(",\"column\":")
    gate_write_json_string(out, issue.column)
    out.write_string(",\"message\":")
    gate_write_json_string(out, issue.message)
    out.write_char('}')
  }
  out.write_char(']')
}

///|
fn gate_write_json_bool(out : StringBuilder, value : Bool) -> Unit {
  out.write_string(if value { "true" } else { "false" })
}

///|
fn gate_bool_text(value : Bool) -> String {
  if value {
    "yes"
  } else {
    "no"
  }
}

///|
fn gate_signed_int(value : Int) -> String {
  if value > 0 {
    "+\{value}"
  } else {
    value.to_string()
  }
}

///|
fn gate_write_markdown_cell(out : StringBuilder, value : String) -> Unit {
  for ch in value.iter() {
    if ch == '|' {
      out.write_string("\\|")
    } else if ch == '\n' || ch == '\r' {
      out.write_char(' ')
    } else {
      out.write_char(ch)
    }
  }
}

///|
fn gate_write_json_string(out : StringBuilder, value : String) -> Unit {
  out.write_char('"')
  for ch in value.iter() {
    if ch == '"' {
      out.write_string("\\\"")
    } else if ch == '\\' {
      out.write_string("\\\\")
    } else if ch == '\n' {
      out.write_string("\\n")
    } else if ch == '\r' {
      out.write_string("\\r")
    } else if ch == '\t' {
      out.write_string("\\t")
    } else {
      out.write_char(ch)
    }
  }
  out.write_char('"')
}