///|
/// A tabular report that can be exported to CSV or rendered by a dashboard.
pub(all) struct ReportTable {
  title : String
  columns : Array[String]
  rows : Array[Array[String]]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Build a table from a headline analysis report.
pub fn build_report_table(report : AnalysisReport) -> ReportTable {
  let rows = []
  rows.push(["raw_count", report.raw_count.to_string(), "beats"])
  rows.push([
    "clean_count",
    report.cleaned_intervals.length().to_string(),
    "beats",
  ])
  rows.push(["mean_rr", report.metrics.mean_rr.to_string(), "ms"])
  rows.push(["mean_hr", report.metrics.mean_hr.to_string(), "bpm"])
  rows.push(["sdnn", report.metrics.sdnn.to_string(), "ms"])
  rows.push(["rmssd", report.metrics.rmssd.to_string(), "ms"])
  rows.push(["pnn50", report.metrics.pnn50.to_string(), "%"])
  rows.push(["poincare_sd1", report.poincare.sd1.to_string(), "ms"])
  rows.push(["poincare_sd2", report.poincare.sd2.to_string(), "ms"])
  rows.push([
    "triangular_index",
    report.geometric.triangular_index.to_string(),
    "ratio",
  ])
  rows.push(["tinn", report.geometric.tinn.to_string(), "ms"])
  rows.push(["vlf_power", report.frequency.vlf.power.to_string(), "ms2"])
  rows.push(["lf_power", report.frequency.lf.power.to_string(), "ms2"])
  rows.push(["hf_power", report.frequency.hf.power.to_string(), "ms2"])
  rows.push(["lf_hf_ratio", report.frequency.lf_hf_ratio.to_string(), "ratio"])
  rows.push([
    "sample_entropy",
    report.nonlinear.sample_entropy.to_string(),
    "nats",
  ])
  rows.push(["quality_grade", quality_grade(report.raw_validation), ""])
  { title: "HRV analysis report", columns: ["metric", "value", "unit"], rows }
}

///|
/// Serialize a table with one header row.
pub fn report_table_to_csv(table : ReportTable) -> String {
  let grid = [table.columns]
  for row in table.rows {
    grid.push(row)
  }
  to_csv(grid)
}

///|
/// Return the first matching row by its metric key.
pub fn report_table_find(table : ReportTable, key : String) -> Array[String] {
  for row in table.rows {
    if row.length() > 0 && row[0] == key {
      return row
    }
  }
  []
}

///|
/// Return a table with rows selected by a predicate.
pub fn report_table_filter(
  table : ReportTable,
  predicate : (Array[String]) -> Bool,
) -> ReportTable {
  let rows = []
  for row in table.rows {
    if predicate(row) {
      rows.push(row)
    }
  }
  { title: table.title, columns: table.columns, rows }
}

///|
/// Add a row while preserving the table schema.
pub fn report_table_append(
  table : ReportTable,
  row : Array[String],
) -> ReportTable {
  let rows = []
  for existing in table.rows {
    rows.push(existing)
  }
  if row.length() == table.columns.length() {
    rows.push(row)
  }
  { title: table.title, columns: table.columns, rows }
}

///|
/// Return the number of data rows.
pub fn report_table_size(table : ReportTable) -> Int {
  table.rows.length()
}

///|
/// Convert a report table to a stable JSON-like key/value grid.
pub fn report_table_grid(table : ReportTable) -> Array[Array[String]] {
  let grid = [table.columns]
  for row in table.rows {
    grid.push(row)
  }
  grid
}

///|
/// Build a table containing one row per segment.
pub fn segment_report_table(summaries : Array[SegmentSummary]) -> ReportTable {
  let rows = []
  for summary in summaries {
    rows.push([
      summary.range.ordinal.to_string(),
      summary.mean_rr.to_string(),
      summary.sdnn.to_string(),
      summary.rmssd.to_string(),
      summary.quality_ratio.to_string(),
    ])
  }
  {
    title: "Segment report",
    columns: ["segment", "mean_rr", "sdnn", "rmssd", "quality"],
    rows,
  }
}

///|
/// Return a report table summary line for logs.
pub fn report_table_summary(table : ReportTable) -> String {
  "\{table.title}: \{table.rows.length()} rows, \{table.columns.length()} columns"
}