///|
fn csv_option(value : Double?) -> String {
  match value {
    None => ""
    Some(x) => x.to_string()
  }
}

///|
fn summary_columns(q : RqaSummary) -> String {
  "\{q.recurrent},\{q.eligible},\{csv_option(q.recurrence_rate)},\{q.determinism},\{q.laminarity},\{csv_option(q.mean_diagonal)},\{q.longest_diagonal},\{csv_option(q.divergence)},\{q.diagonal_entropy},\{csv_option(q.trapping_time)},\{q.longest_vertical},\{q.vertical_entropy},\{csv_option(q.mean_return_gap)}"
}

///|
fn summary_header() -> String {
  "recurrent,eligible,recurrence_rate,determinism,laminarity,mean_diagonal,longest_diagonal,divergence,diagonal_entropy,trapping_time,longest_vertical,vertical_entropy,mean_return_gap"
}

///|
/// Numeric-only tables do not accept user-controlled formulas or field labels.
pub fn windows_csv(windows : Array[WindowSummary]) -> Result[String, RqaError] {
  if windows.length() > 4096 {
    return Err(Limit("window report rows"))
  }
  let out = StringBuilder()
  out.write_string("start,stop,\{summary_header()}\n")
  for window in windows {
    match window.summary.validate() {
      Err(e) => return Err(e)
      Ok(_) => ()
    }
    if window.start < 0 || window.stop <= window.start || window.stop > 4096 {
      return Err(Invalid("window report positions"))
    }
    out.write_string(
      "\{window.start},\{window.stop},\{summary_columns(window.summary)}\n",
    )
  }
  Ok(out.to_string())
}

///|
pub fn thresholds_csv(
  scan : Array[ThresholdSummary],
) -> Result[String, RqaError] {
  if scan.length() > 256 {
    return Err(Limit("threshold report rows"))
  }
  let out = StringBuilder()
  out.write_string("threshold,\{summary_header()}\n")
  for row in scan {
    match row.summary.validate() {
      Err(e) => return Err(e)
      Ok(_) => ()
    }
    if !finite(row.threshold) || row.threshold < 0.0 {
      return Err(Invalid("threshold report value"))
    }
    out.write_string("\{row.threshold},\{summary_columns(row.summary)}\n")
  }
  Ok(out.to_string())
}

///|
pub fn lags_csv(profile : Array[LagPoint]) -> Result[String, RqaError] {
  if profile.length() > 8193 {
    return Err(Limit("lag report rows"))
  }
  let out = StringBuilder()
  out.write_string("lag,recurrent,eligible,rate\n")
  for row in profile {
    if row.lag < -4096 ||
      row.lag > 4096 ||
      row.recurrent < 0 ||
      row.eligible < row.recurrent ||
      row.eligible > 4096 {
      return Err(Invalid("lag report counts"))
    }
    // Recompute ratio to avoid trusting externally constructed summary fields.
    let rate = if row.eligible == 0 {
      None
    } else {
      Some(row.recurrent.to_double() / row.eligible.to_double())
    }
    out.write_string(
      "\{row.lag},\{row.recurrent},\{row.eligible},\{csv_option(rate)}\n",
    )
  }
  Ok(out.to_string())
}