///|
/// Errors reported by the intentionally small, dependency-free telemetry CSV reader.
///
/// The format is two columns: `time,value`. Quoted fields and locale-specific
/// numeric formats are intentionally out of scope for a portable core library.
pub(all) enum CsvError {
  EmptyInput
  MissingColumns(Int)
  InvalidTime(Int)
  InvalidValue(Int)
} derive(Debug, Eq)

///|
/// Exports a series as portable UTF-8 CSV with a `time,value` header.
pub fn Series::to_csv(self : Series) -> String {
  let mut output = "time,value\n"
  for i = 0; i < self.samples.length(); i = i + 1 {
    let sample = self.samples[i]
    output = output + "\{sample.time},\{sample.value}"
    if i + 1 < self.samples.length() {
      output = output + "\n"
    }
  }
  output
}

///|
/// Imports a two-column `time,value` CSV document.
///
/// Empty lines are ignored. The first non-empty row is treated as a header when
/// `has_header` is true, which is the default and matches `Series::to_csv`.
pub fn Series::from_csv(
  name : String,
  csv : String,
  has_header? : Bool = true,
) -> Result[Series, CsvError] {
  let lines = csv.split("\n").to_array()
  let samples : Array[Sample] = []
  let mut saw_data = false
  let mut header_pending = has_header
  for i = 0; i < lines.length(); i = i + 1 {
    let row = lines[i].to_owned().trim()
    if row == "" {
      continue
    }
    if header_pending {
      header_pending = false
      continue
    }
    saw_data = true
    let columns = row.split(",").to_array()
    if columns.length() < 2 {
      return Err(MissingColumns(i + 1))
    }
    let time = @string.parse_int(columns[0]) catch {
      _ => return Err(InvalidTime(i + 1))
    }
    let value = @string.parse_double(columns[1]) catch {
      _ => return Err(InvalidValue(i + 1))
    }
    samples.push(Sample::new(time, value))
  }
  if !saw_data {
    Err(EmptyInput)
  } else {
    Ok(Series::new(name, samples))
  }
}