use std::path::{Path, PathBuf};
use dspk_core::DspResult;
use dspk_io::container::zarr::{open_rw_store, read_node_json, write_group};
use serde_json::{json, Map};
pub const DERIVED_VERSION: u32 = 1;
pub fn derived_store(recording: &Path) -> PathBuf {
recording.with_file_name(format!("{}.dspk.zarr", stem(recording)))
}
pub fn stem(recording: &Path) -> String {
let name = recording.file_name().map_or_else(|| "recording".into(), |n| n.to_string_lossy().into_owned());
name.trim_end_matches('/').trim_end_matches(".zarr").trim_end_matches(".nwb").trim_end_matches(".bin").trim_end_matches(".cbin").to_string()
}
pub fn events_dir(recording: &Path) -> PathBuf {
derived_store(recording).join("events")
}
pub fn events_group(recording: &Path, name: &str) -> PathBuf {
let safe: String = name.chars().map(|c| if c.is_alphanumeric() || matches!(c, '-' | '_' | '.') { c } else { '_' }).collect();
events_dir(recording).join(if safe.is_empty() { "events".into() } else { safe })
}
pub fn ensure(recording: &Path, groups: &[&str]) -> DspResult<PathBuf> {
let dir = derived_store(recording);
let store = open_rw_store(&dir)?;
if read_node_json(&dir, "/").is_none() {
let mut attrs = Map::new();
attrs.insert("dspk_derived".into(), json!(DERIVED_VERSION));
attrs.insert("recording".into(), json!(recording.file_name().map(|n| n.to_string_lossy().into_owned())));
write_group(&store, &dir, "/", attrs)?;
}
for g in groups {
if read_node_json(&dir, g).is_none() {
write_group(&store, &dir, g, Map::new())?;
}
}
Ok(dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paths_of_a_recording() {
let rec = Path::new("/data/m1/rec.ap.bin");
assert_eq!(derived_store(rec), Path::new("/data/m1/rec.ap.dspk.zarr"));
assert_eq!(derived_store(Path::new("/data/x.zarr/")), Path::new("/data/x.dspk.zarr"));
assert_eq!(events_group(rec, "biceps onsets"), Path::new("/data/m1/rec.ap.dspk.zarr/events/biceps_onsets"));
}
}