///|
/// Stable text exports for dashboards, audit logs, and command-line use.

///|
/// One serializable result row.
pub(all) struct ExportRow {
  key : String
  value : String
  unit : String
  status : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Export document with version and provenance.
pub(all) struct ExportDocument {
  schema : String
  generated_by : String
  rows : Array[ExportRow]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Create one row.
pub fn export_row(
  key : String,
  value : Double,
  unit : String,
  status : String,
) -> ExportRow {
  { key, value: value.to_string(), unit, status }
}

///|
/// Convert an analysis report to a stable row set.
pub fn analysis_export_rows(report : AnalysisReport) -> Array[ExportRow] {
  [
    export_row("raw_count", report.raw_count.to_double(), "beats", "ok"),
    export_row(
      "cleaned_count",
      report.cleaned_intervals.length().to_double(),
      "beats",
      "ok",
    ),
    export_row("mean_rr", report.metrics.mean_rr, "ms", "ok"),
    export_row("mean_hr", report.metrics.mean_hr, "bpm", "ok"),
    export_row("sdnn", report.metrics.sdnn, "ms", "ok"),
    export_row("rmssd", report.metrics.rmssd, "ms", "ok"),
    export_row("pnn50", report.metrics.pnn50, "%", "ok"),
    export_row(
      "frequency_total_power",
      report.frequency.total_power,
      "ms2",
      "ok",
    ),
    export_row(
      "frequency_centroid",
      report.frequency.spectral_centroid_hz,
      "hz",
      "ok",
    ),
    export_row("sample_entropy", report.nonlinear.sample_entropy, "", "ok"),
    export_row(
      "feature_count",
      report.feature_vector.length().to_double(),
      "features",
      "ok",
    ),
    export_row(
      "quality_ratio",
      report.quality.clean_ratio,
      "ratio",
      if report.quality.is_low_quality {
        "review"
      } else {
        "ok"
      },
    ),
  ]
}

///|
/// Create a document for a report.
pub fn analysis_export_document(report : AnalysisReport) -> ExportDocument {
  {
    schema: "hrvkit.report.v1",
    generated_by: "moonbit-hrvkit",
    rows: analysis_export_rows(report),
  }
}

///|
/// Escape a value for a simple key-value export.
pub fn export_escape(value : String) -> String {
  let result = StringBuilder::new()
  for character in value {
    if character == '\\' {
      result.write_string("\\\\")
    } else if character == '"' {
      result.write_string("\\\"")
    } else if character == '\n' {
      result.write_string("\\n")
    } else {
      result.write_char(character)
    }
  }
  result.to_string()
}

///|
/// Export rows as a compact JSON-like line protocol without external state.
pub fn export_rows_ndjson(rows : Array[ExportRow]) -> String {
  let result = StringBuilder::new()
  for row in rows {
    result.write_string("{\"key\":\"")
    result.write_string(export_escape(row.key))
    result.write_string("\",\"value\":\"")
    result.write_string(export_escape(row.value))
    result.write_string("\",\"unit\":\"")
    result.write_string(export_escape(row.unit))
    result.write_string("\",\"status\":\"")
    result.write_string(export_escape(row.status))
    result.write_string("\"}\n")
  }
  result.to_string()
}

///|
/// Export a document as a CSV table.
pub fn export_document_csv(document : ExportDocument) -> String {
  let grid = [["key", "value", "unit", "status"]]
  for row in document.rows {
    grid.push([row.key, row.value, row.unit, row.status])
  }
  to_csv(grid)
}

///|
/// Export a report as a Markdown table.
pub fn analysis_report_markdown(report : AnalysisReport) -> String {
  let result = StringBuilder::new()
  result.write_string("# HRV analysis report\n\n")
  result.write_string("Generated by moonbit-hrvkit.\n\n")
  result.write_string(
    "| Metric | Value | Unit | Status |\n| --- | ---: | --- | --- |\n",
  )
  for row in analysis_export_rows(report) {
    result.write_string("| ")
    result.write_string(row.key)
    result.write_string(" | ")
    result.write_string(row.value)
    result.write_string(" | ")
    result.write_string(row.unit)
    result.write_string(" | ")
    result.write_string(row.status)
    result.write_string(" |\n")
  }
  result.to_string()
}

///|
/// Create a Markdown report with the quality summary and feature vector size.
pub fn analysis_report_markdown_with_quality(report : AnalysisReport) -> String {
  let result = analysis_report_markdown(report)
  result + "\nQuality: " + analysis_quality_summary(report) + "\n"
}

///|
/// Export a batch of observations as CSV.
pub fn session_observations_csv(
  observations : Array[SessionObservation],
) -> String {
  let grid = [
    [
      "session_id", "date", "duration_minutes", "rmssd", "sdnn", "mean_hr", "quality_score",
      "training_load",
    ],
  ]
  for observation in observations {
    grid.push([
      observation.session_id,
      observation.date,
      observation.duration_minutes.to_string(),
      observation.rmssd.to_string(),
      observation.sdnn.to_string(),
      observation.mean_hr.to_string(),
      observation.quality_score.to_string(),
      observation.training_load.to_string(),
    ])
  }
  to_csv(grid)
}

///|
/// Add a provenance footer to a text export.
pub fn export_with_provenance(content : String, source : String) -> String {
  content +
  "\n# source=" +
  export_escape(source) +
  "\n# schema=hrvkit.report.v1\n"
}

///|
/// Return whether an export row contains a finite numeric value.
pub fn export_row_is_numeric(row : ExportRow) -> Bool {
  let parsed = @strconv.from_str(row.value) catch { _ => 0.0 }
  let failed = @strconv.from_str(row.value) catch { _ => -1.0 }
  parsed == parsed && !parsed.is_inf() && row.value != "" && failed != -1.0
}

///|
/// Return the number of rows marked for review.
pub fn export_review_count(rows : Array[ExportRow]) -> Int {
  let mut count = 0
  for row in rows {
    if row.status == "review" {
      count += 1
    }
  }
  count
}

///|
/// Return a compact one-line summary for logs.
pub fn export_log_line(report : AnalysisReport) -> String {
  "hrvkit raw=" +
  report.raw_count.to_string() +
  " clean=" +
  report.cleaned_intervals.length().to_string() +
  " rmssd=" +
  report.metrics.rmssd.to_string() +
  " quality=" +
  report.quality.clean_ratio.to_string()
}