///|
/// Convert a vector to one CSV row.
pub fn vector_to_csv(values : Array[Double]) -> String {
  let mut result = ""
  for i, value in values {
    if i > 0 {
      result = result + ","
    }
    result = result + value.to_string()
  }
  result
}

///|
pub fn matrix_to_csv(matrix : Matrix) -> String {
  let mut result = ""
  for i in 0.. 0 {
      result = result + "\n"
    }
    result = result + vector_to_csv(matrix.row(i))
  }
  result
}

///|
pub fn estimates_to_csv(
  estimates : Array[Array[Double]],
  truth : Array[Array[Double]],
) -> String {
  let mut result = "index,estimate,truth,error\n"
  let count = if estimates.length() < truth.length() {
    estimates.length()
  } else {
    truth.length()
  }
  for i in 0.. String {
  "count,rmse,mae,max_error,final_error\n" +
  metrics.count().to_string() +
  "," +
  metrics.rmse().to_string() +
  "," +
  metrics.mae().to_string() +
  "," +
  metrics.max_error().to_string() +
  "," +
  metrics.final_error().to_string() +
  "\n"
}

///|
/// Produce a concise Markdown table suitable for a CI artifact or README.
pub fn metrics_to_markdown(metrics : ErrorMetrics) -> String {
  "| Metric | Value |\n| --- | ---: |\n" +
  "| samples | " +
  metrics.count().to_string() +
  " |\n" +
  "| RMSE | " +
  metrics.rmse().to_string() +
  " |\n" +
  "| MAE | " +
  metrics.mae().to_string() +
  " |\n" +
  "| maximum error | " +
  metrics.max_error().to_string() +
  " |\n" +
  "| final error | " +
  metrics.final_error().to_string() +
  " |\n"
}

///|
pub fn update_result_to_string(result : UpdateResult) -> String {
  match result {
    Accepted => "accepted"
    RejectedByGate => "rejected_by_gate"
    InvalidMeasurement => "invalid_measurement"
    SingularInnovation => "singular_innovation"
    MissingMeasurement => "missing_measurement"
  }
}

///|
pub fn fusion_events_to_csv(events : Array[FusionEvent]) -> String {
  let mut result = "timestamp,sensor,result,nis,state\n"
  for event in events {
    result = result + event.timestamp().to_string() + ","
    result = result + event.sensor() + ","
    result = result + update_result_to_string(event.result()) + ","
    result = result + event.nis().to_string() + ","
    result = result + vector_to_csv(event.state()) + "\n"
  }
  result
}

///|
pub fn covariance_to_markdown(covariance : Matrix, digits? : Int) -> String {
  let precision = match digits {
    None => 6
    Some(value) => if value < 0 { 0 } else if value > 12 { 12 } else { value }
  }
  let _ = precision
  let mut result = "| |"
  for j in 0.. String {
  let mut result = "timestamp,truth,measurement,missing,outlier\n"
  for sample in samples {
    result = result + sample.timestamp().to_string() + ","
    result = result + vector_to_csv(sample.truth()) + ","
    result = result + vector_to_csv(sample.measurement()) + ","
    result = result + (if sample.missing() { "true" } else { "false" }) + ","
    result = result + (if sample.outlier() { "true" } else { "false" }) + "\n"
  }
  result
}

///|
pub fn report_line(label : String, value : Double, unit : String) -> String {
  label + ": " + value.to_string() + " " + unit
}

///|
pub fn report_lines(lines : Array[(String, Double, String)]) -> String {
  let mut result = ""
  for line in lines {
    result = result + report_line(line.0, line.1, line.2) + "\n"
  }
  result
}