///|
fn parse_int_field(
  raw : StringView,
  field : String,
) -> Int raise VisionFormatError {
  @string.parse_int(raw.trim()) catch {
    _ => raise VisionFormatError::InvalidNumber("\{field}=\{raw}")
  }
}

///|
fn parse_double_field(
  raw : StringView,
  field : String,
) -> Double raise VisionFormatError {
  @string.parse_double(raw.trim()) catch {
    _ => raise VisionFormatError::InvalidNumber("\{field}=\{raw}")
  }
}

///|
fn split_csv_line(line : String) -> Array[String] {
  Array::from_iter(line.split(",").map(fn(part) { part.trim().to_owned() }))
}

///|
fn non_empty_lines(text : String) -> Array[String] {
  Array::from_iter(
    text
    .split("\n")
    .filter_map(fn(line) {
      let clean = line.trim().to_owned()
      if clean.is_empty() || clean.has_prefix("#") {
        None
      } else {
        Some(clean)
      }
    }),
  )
}

///|
fn require_columns(
  row : Array[String],
  count : Int,
  context : String,
) -> Unit raise VisionFormatError {
  if row.length() < count {
    raise VisionFormatError::InvalidRow(context)
  }
}

///|
pub fn parse_stamp(
  sec : StringView,
  nsec : StringView,
) -> Stamp raise VisionFormatError {
  let s = parse_int_field(sec, "sec")
  let ns = parse_int_field(nsec, "nsec")
  if ns < 0 || ns >= 1000000000 {
    raise VisionFormatError::InvalidNumber("nsec=\{nsec}")
  }
  { sec: s, nsec: ns }
}

///|
pub fn parse_stamp_text(text : String) -> Stamp raise VisionFormatError {
  match text.split_once(".") {
    Some((sec, frac)) => {
      let padded = frac.to_owned().pad_end(9, '0')
      let nsec_text = padded.unsafe_substring(start=0, end=9)
      parse_stamp(sec, nsec_text[:])
    }
    None => parse_stamp(text[:], "0"[:])
  }
}

///|
fn[T] ensure_monotonic(
  items : Array[T],
  stamp_of : (T) -> Stamp,
  name : String,
) -> Unit raise VisionFormatError {
  for i in 1.. 0 {
      raise VisionFormatError::OutOfOrder("\{name} at row \{i + 1}")
    }
  }
}

///|
fn abs_i64(value : Int64) -> Int64 {
  if value < 0L {
    0L - value
  } else {
    value
  }
}