///|
fn write_json_string(builder : StringBuilder, value : String) -> Unit {
  builder.write_char('"')
  for character in value.iter() {
    match character {
      '"' => builder.write("\\\"")
      '\\' => builder.write("\\\\")
      '\n' => builder.write("\\n")
      '\r' => builder.write("\\r")
      '\t' => builder.write("\\t")
      '\u{0008}' => builder.write("\\b")
      '\u{000c}' => builder.write("\\f")
      _ => builder.write_char(character)
    }
  }
  builder.write_char('"')
}

///|
fn write_int_array(builder : StringBuilder, values : Array[Int]) -> Unit {
  builder.write_char('[')
  for index, value in values {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write(value)
  }
  builder.write_char(']')
}

///|
fn write_int_matrix(
  builder : StringBuilder,
  values : Array[Array[Int]],
) -> Unit {
  builder.write_char('[')
  for index, row in values {
    if index > 0 {
      builder.write_char(',')
    }
    write_int_array(builder, row)
  }
  builder.write_char(']')
}

///|
/// Produces a stable line-oriented dataset audit report.
pub fn dataset_profile_text(profile : DatasetProfile) -> String {
  let builder = StringBuilder()
  builder.write("dataset: \{profile.name()}\n")
  builder.write("rows: \{profile.row_count()}\n")
  builder.write("features: \{profile.feature_count()}\n")
  builder.write("classes: \{profile.class_count()}\n")
  builder.write("minimum_squared_norm: \{profile.minimum_squared_norm()}\n")
  builder.write("maximum_squared_norm: \{profile.maximum_squared_norm()}\n")
  builder.write("mean_squared_norm: \{profile.mean_squared_norm()}\n")
  for feature in profile.features() {
    builder.write(
      "feature \{feature.feature_index()}: minimum=\{feature.minimum()}, maximum=\{feature.maximum()}, mean=\{feature.mean()}, variance=\{feature.variance()}, standard_deviation=\{feature.standard_deviation()}, constant=\{feature.is_constant()}\n",
    )
  }
  for class_profile in profile.classes() {
    builder.write(
      "class \{class_profile.label()}: count=\{class_profile.count()}, proportion=\{class_profile.proportion()}\n",
    )
  }
  builder.to_string()
}

///|
/// Produces deterministic JSON for a dataset profile.
pub fn dataset_profile_json(profile : DatasetProfile) -> String {
  let builder = StringBuilder()
  builder.write("{\"name\":")
  write_json_string(builder, profile.name())
  builder.write(",\"row_count\":\{profile.row_count()}")
  builder.write(",\"feature_count\":\{profile.feature_count()}")
  builder.write(",\"class_count\":\{profile.class_count()}")
  builder.write(",\"minimum_squared_norm\":\{profile.minimum_squared_norm()}")
  builder.write(",\"maximum_squared_norm\":\{profile.maximum_squared_norm()}")
  builder.write(",\"mean_squared_norm\":\{profile.mean_squared_norm()}")
  builder.write(",\"centroid\":[")
  for index, value in profile.centroid() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write(value)
  }
  builder.write("],\"feature_profiles\":[")
  for index, feature in profile.features() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write("{\"feature_index\":\{feature.feature_index()}")
    builder.write(",\"minimum\":\{feature.minimum()}")
    builder.write(",\"maximum\":\{feature.maximum()}")
    builder.write(",\"mean\":\{feature.mean()}")
    builder.write(",\"variance\":\{feature.variance()}")
    builder.write(",\"standard_deviation\":\{feature.standard_deviation()}")
    builder.write(",\"constant\":\{feature.is_constant()}}")
  }
  builder.write("],\"class_profiles\":[")
  for index, class_profile in profile.classes() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write("{\"label\":\{class_profile.label()}")
    builder.write(",\"count\":\{class_profile.count()}")
    builder.write(",\"proportion\":\{class_profile.proportion()}}")
  }
  builder.write("]}")
  builder.to_string()
}

///|
/// Produces a stable human-readable classification report.
pub fn classification_metrics_text(metrics : ClassificationMetrics) -> String {
  let builder = StringBuilder()
  builder.write("observations: \{metrics.observation_count()}\n")
  builder.write("accuracy: \{metrics.accuracy()}\n")
  builder.write("error_rate: \{metrics.error_rate()}\n")
  builder.write("balanced_accuracy: \{metrics.balanced_accuracy()}\n")
  builder.write("macro_precision: \{metrics.macro_precision()}\n")
  builder.write("macro_recall: \{metrics.macro_recall()}\n")
  builder.write("macro_f1: \{metrics.macro_f1()}\n")
  builder.write("micro_precision: \{metrics.micro_precision()}\n")
  builder.write("micro_recall: \{metrics.micro_recall()}\n")
  builder.write("micro_f1: \{metrics.micro_f1()}\n")
  builder.write("weighted_precision: \{metrics.weighted_precision()}\n")
  builder.write("weighted_recall: \{metrics.weighted_recall()}\n")
  builder.write("weighted_f1: \{metrics.weighted_f1()}\n")
  for class_metric in metrics.per_class() {
    builder.write(
      "class \{class_metric.label()}: support=\{class_metric.support()}, true_positives=\{class_metric.true_positives()}, false_positives=\{class_metric.false_positives()}, false_negatives=\{class_metric.false_negatives()}, precision=\{class_metric.precision()}, recall=\{class_metric.recall()}, f1=\{class_metric.f1()}\n",
    )
  }
  builder.to_string()
}

///|
/// Produces deterministic JSON including confusion and per-class evidence.
pub fn classification_metrics_json(metrics : ClassificationMetrics) -> String {
  let builder = StringBuilder()
  builder.write("{\"observation_count\":\{metrics.observation_count()}")
  builder.write(",\"accuracy\":\{metrics.accuracy()}")
  builder.write(",\"error_rate\":\{metrics.error_rate()}")
  builder.write(",\"balanced_accuracy\":\{metrics.balanced_accuracy()}")
  builder.write(",\"macro_precision\":\{metrics.macro_precision()}")
  builder.write(",\"macro_recall\":\{metrics.macro_recall()}")
  builder.write(",\"macro_f1\":\{metrics.macro_f1()}")
  builder.write(",\"micro_precision\":\{metrics.micro_precision()}")
  builder.write(",\"micro_recall\":\{metrics.micro_recall()}")
  builder.write(",\"micro_f1\":\{metrics.micro_f1()}")
  builder.write(",\"weighted_precision\":\{metrics.weighted_precision()}")
  builder.write(",\"weighted_recall\":\{metrics.weighted_recall()}")
  builder.write(",\"weighted_f1\":\{metrics.weighted_f1()}")
  builder.write(",\"classes\":")
  write_int_array(builder, metrics.classes())
  builder.write(",\"confusion_matrix\":")
  write_int_matrix(builder, metrics.confusion_matrix())
  builder.write(",\"per_class\":[")
  for index, class_metric in metrics.per_class() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write("{\"label\":\{class_metric.label()}")
    builder.write(",\"support\":\{class_metric.support()}")
    builder.write(",\"true_positives\":\{class_metric.true_positives()}")
    builder.write(",\"false_positives\":\{class_metric.false_positives()}")
    builder.write(",\"false_negatives\":\{class_metric.false_negatives()}")
    builder.write(",\"precision\":\{class_metric.precision()}")
    builder.write(",\"recall\":\{class_metric.recall()}")
    builder.write(",\"f1\":\{class_metric.f1()}}")
  }
  builder.write("]}")
  builder.to_string()
}

///|
/// Produces stable binary support-vector and convergence evidence.
pub fn binary_diagnostics_text(
  diagnostics : BinaryModelDiagnostics,
  convergence : ConvergenceReport,
) -> String {
  let builder = StringBuilder()
  builder.write("support_vectors: \{diagnostics.support_vectors()}\n")
  builder.write(
    "negative_support_vectors: \{diagnostics.negative_support_vectors()}\n",
  )
  builder.write(
    "positive_support_vectors: \{diagnostics.positive_support_vectors()}\n",
  )
  builder.write("minimum_alpha: \{diagnostics.minimum_alpha()}\n")
  builder.write("maximum_alpha: \{diagnostics.maximum_alpha()}\n")
  builder.write("mean_alpha: \{diagnostics.mean_alpha()}\n")
  builder.write("iterations: \{convergence.iterations()}\n")
  builder.write("pair_updates: \{convergence.pair_updates()}\n")
  builder.write("maximum_kkt_violation: \{convergence.max_kkt_violation()}\n")
  builder.write("dual_objective: \{convergence.dual_objective()}\n")
  builder.write("converged: \{convergence.converged()}\n")
  builder.to_string()
}

///|
/// Produces deterministic JSON for binary model diagnostics.
pub fn binary_diagnostics_json(
  diagnostics : BinaryModelDiagnostics,
  convergence : ConvergenceReport,
) -> String {
  let builder = StringBuilder()
  builder.write("{\"support_vectors\":\{diagnostics.support_vectors()}")
  builder.write(
    ",\"negative_support_vectors\":\{diagnostics.negative_support_vectors()}",
  )
  builder.write(
    ",\"positive_support_vectors\":\{diagnostics.positive_support_vectors()}",
  )
  builder.write(",\"minimum_alpha\":\{diagnostics.minimum_alpha()}")
  builder.write(",\"maximum_alpha\":\{diagnostics.maximum_alpha()}")
  builder.write(",\"mean_alpha\":\{diagnostics.mean_alpha()}")
  builder.write(",\"iterations\":\{convergence.iterations()}")
  builder.write(",\"pair_updates\":\{convergence.pair_updates()}")
  builder.write(",\"maximum_kkt_violation\":\{convergence.max_kkt_violation()}")
  builder.write(",\"dual_objective\":\{convergence.dual_objective()}")
  builder.write(",\"converged\":\{convergence.converged()}}")
  builder.to_string()
}

///|
/// Produces a line-oriented threshold report with confusion evidence.
pub fn threshold_analysis_text(analysis : BinaryThresholdAnalysis) -> String {
  let builder = StringBuilder()
  builder.write("positive_count: \{analysis.positive_count()}\n")
  builder.write("negative_count: \{analysis.negative_count()}\n")
  builder.write("roc_auc: \{analysis.roc_auc()}\n")
  builder.write("average_precision: \{analysis.average_precision()}\n")
  for point in analysis.points() {
    builder.write(
      "threshold \{point.threshold()}: true_positives=\{point.true_positives()}, false_positives=\{point.false_positives()}, true_negatives=\{point.true_negatives()}, false_negatives=\{point.false_negatives()}, true_positive_rate=\{point.true_positive_rate()}, false_positive_rate=\{point.false_positive_rate()}, precision=\{point.precision()}, recall=\{point.recall()}\n",
    )
  }
  builder.to_string()
}

///|
/// Produces deterministic JSON for ROC and precision-recall points.
pub fn threshold_analysis_json(analysis : BinaryThresholdAnalysis) -> String {
  let builder = StringBuilder()
  builder.write("{\"positive_count\":\{analysis.positive_count()}")
  builder.write(",\"negative_count\":\{analysis.negative_count()}")
  builder.write(",\"roc_auc\":\{analysis.roc_auc()}")
  builder.write(",\"average_precision\":\{analysis.average_precision()}")
  builder.write(",\"points\":[")
  for index, point in analysis.points() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write("{\"threshold\":\{point.threshold()}")
    builder.write(",\"true_positives\":\{point.true_positives()}")
    builder.write(",\"false_positives\":\{point.false_positives()}")
    builder.write(",\"true_negatives\":\{point.true_negatives()}")
    builder.write(",\"false_negatives\":\{point.false_negatives()}")
    builder.write(",\"true_positive_rate\":\{point.true_positive_rate()}")
    builder.write(",\"false_positive_rate\":\{point.false_positive_rate()}")
    builder.write(",\"precision\":\{point.precision()}")
    builder.write(",\"recall\":\{point.recall()}}")
  }
  builder.write("]}")
  builder.to_string()
}

///|
/// Produces stable repeated-validation summary text.
pub fn repeated_validation_text(report : RepeatedValidationReport) -> String {
  let builder = StringBuilder()
  builder.write("repetitions: \{report.repetition_count()}\n")
  builder.write("folds_per_repetition: \{report.fold_count()}\n")
  builder.write("mean_accuracy: \{report.mean_accuracy()}\n")
  builder.write("minimum_accuracy: \{report.minimum_accuracy()}\n")
  builder.write("maximum_accuracy: \{report.maximum_accuracy()}\n")
  builder.write(
    "accuracy_standard_deviation: \{report.accuracy_standard_deviation()}\n",
  )
  builder.write("mean_macro_f1: \{report.mean_macro_f1()}\n")
  builder.write("minimum_macro_f1: \{report.minimum_macro_f1()}\n")
  builder.write("maximum_macro_f1: \{report.maximum_macro_f1()}\n")
  builder.write(
    "macro_f1_standard_deviation: \{report.macro_f1_standard_deviation()}\n",
  )
  for index, round in report.reports() {
    builder.write(
      "round \{index}: accuracy=\{round.metrics().accuracy()}, macro_f1=\{round.metrics().macro_f1()}, observations=\{round.observation_count()}\n",
    )
  }
  builder.to_string()
}

///|
/// Produces deterministic JSON for repeated validation variability.
pub fn repeated_validation_json(report : RepeatedValidationReport) -> String {
  let builder = StringBuilder()
  builder.write("{\"repetitions\":\{report.repetition_count()}")
  builder.write(",\"folds_per_repetition\":\{report.fold_count()}")
  builder.write(",\"mean_accuracy\":\{report.mean_accuracy()}")
  builder.write(",\"minimum_accuracy\":\{report.minimum_accuracy()}")
  builder.write(",\"maximum_accuracy\":\{report.maximum_accuracy()}")
  builder.write(
    ",\"accuracy_standard_deviation\":\{report.accuracy_standard_deviation()}",
  )
  builder.write(",\"mean_macro_f1\":\{report.mean_macro_f1()}")
  builder.write(",\"minimum_macro_f1\":\{report.minimum_macro_f1()}")
  builder.write(",\"maximum_macro_f1\":\{report.maximum_macro_f1()}")
  builder.write(
    ",\"macro_f1_standard_deviation\":\{report.macro_f1_standard_deviation()}",
  )
  builder.write(",\"rounds\":[")
  for index, round in report.reports() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write("{\"index\":\{index}")
    builder.write(",\"accuracy\":\{round.metrics().accuracy()}")
    builder.write(",\"macro_f1\":\{round.metrics().macro_f1()}")
    builder.write(",\"observations\":\{round.observation_count()}}")
  }
  builder.write("]}")
  builder.to_string()
}