use crate::foundation::{GeoError, Result};
use indexmap::IndexMap;
use std::path::Path;
pub type LoadedPoints = (Vec<[f64; 3]>, IndexMap<String, Vec<f64>>);
pub fn load(path: &Path, x: &str, y: &str, z: &str) -> Result<LoadedPoints> {
let mut rdr =
csv::Reader::from_path(path).map_err(|e| GeoError::Parse(format!("points CSV: {e}")))?;
let headers: Vec<String> = rdr
.headers()
.map_err(|e| GeoError::Parse(format!("points CSV: bad header: {e}")))?
.iter()
.map(str::to_string)
.collect();
let find = |col: &str| {
headers
.iter()
.position(|h| h == col)
.ok_or_else(|| GeoError::NotFound(format!("points CSV: column '{col}'")))
};
let (xi, yi, zi) = (find(x)?, find(y)?, find(z)?);
let attr_cols: Vec<usize> = (0..headers.len())
.filter(|i| *i != xi && *i != yi && *i != zi)
.collect();
let mut coords: Vec<[f64; 3]> = Vec::new();
let mut raw: Vec<Option<Vec<f64>>> = vec![Some(Vec::new()); attr_cols.len()];
let parse = |rec: &csv::StringRecord, idx: usize, what: &str, row: usize| -> Result<f64> {
let cell = rec
.get(idx)
.ok_or_else(|| GeoError::Parse(format!("points CSV: row {row}: missing {what}")))?;
cell.trim().parse::<f64>().map_err(|e| {
GeoError::Parse(format!("points CSV: row {row}: bad {what} '{cell}': {e}"))
})
};
for (row, rec) in rdr.records().enumerate() {
let rec = rec.map_err(|e| GeoError::Parse(format!("points CSV: row {row}: {e}")))?;
let xv = parse(&rec, xi, x, row)?;
let yv = parse(&rec, yi, y, row)?;
let zv = parse(&rec, zi, z, row)?;
coords.push([xv, yv, zv]);
for (slot, &col) in raw.iter_mut().zip(&attr_cols) {
let Some(buf) = slot else { continue };
match rec.get(col).map(str::trim).map(str::parse::<f64>) {
Some(Ok(v)) => buf.push(v),
_ => *slot = None, }
}
}
let mut attrs = IndexMap::new();
for (slot, &col) in raw.into_iter().zip(&attr_cols) {
if let Some(buf) = slot {
attrs.insert(headers[col].clone(), buf);
}
}
Ok((coords, attrs))
}