///|
fn csv_field(text : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for c in text.iter() {
    if c == '"' {
      out.write_char('"')
    }
    out.write_char(c)
  }
  out.write_char('"')
  out.to_string()
}

///|
/// CSV with quoted mnemonic/unit header, CRLF records and empty missing cells.
/// Intended for numerical pipelines, not spreadsheet formulas or LAS roundtrips.
pub fn write_csv(
  log : Log,
  include_units? : Bool = true,
) -> String raise WellError {
  validate(log)
  let out = StringBuilder()
  for i in 0.. 0 {
      out.write_string(",")
    }
    let c = log.curves[i]
    out.write_string(
      csv_field(c.name + (if include_units { " [" + c.unit + "]" } else { "" })),
    )
  }
  out.write_string("\r\n")
  for row in log.rows {
    for i in 0.. 0 {
        out.write_string(",")
      }
      if row[i] is Some(value) {
        out.write_string(value.to_string())
      }
    }
    out.write_string("\r\n")
  }
  out.to_string()
}

///|
/// Long-form provenance CSV; null cells omitted and synthetic depth has no run owner.
pub fn provenance_csv(assembly : Assembly) -> String raise WellError {
  validate_assembly(assembly)
  let out = StringBuilder()
  out.write_string("depth,curve,run,interpolated,value\r\n")
  for i in 0.. Unit raise WellError {
  validate(assembly.log)
  if assembly.sources.length() != assembly.log.rows.length() {
    fail("source row count mismatch")
  }
  for i in 0..
          if source.run < 0 || source.run >= assembly.run_names.length() {
            fail("invalid source run index")
          }
        (None, None) => ()
        _ => fail("source and sample presence differ")
      }
    }
  }
  for conflict in assembly.conflicts {
    if conflict.row < 0 ||
      conflict.row >= assembly.log.rows.length() ||
      conflict.kept_run < 0 ||
      conflict.kept_run >= assembly.run_names.length() ||
      conflict.incoming_run < 0 ||
      conflict.incoming_run >= assembly.run_names.length() ||
      !finite(conflict.depth) ||
      !finite(conflict.kept_value) ||
      !finite(conflict.incoming_value) {
      fail("invalid conflict record")
    }
  }
}