use chematic_core::{CipCode, Molecule};
use chematic_iupac::IupacError;
pub fn iupac_name_stereo(mol: &Molecule) -> Result<String, IupacError> {
let base = chematic_iupac::name(mol)?;
let cip = crate::assign_cip(mol);
let mut rs_pairs: Vec<(u32, &'static str)> = cip
.assignments
.iter()
.filter_map(|(idx, code)| match code {
CipCode::R => Some((idx.0, "R")),
CipCode::S => Some((idx.0, "S")),
_ => None,
})
.collect();
if rs_pairs.is_empty() {
return Ok(base);
}
rs_pairs.sort_by_key(|(i, _)| *i);
let prefix = if rs_pairs.len() == 1 {
format!("({})", rs_pairs[0].1)
} else {
let parts: Vec<String> = rs_pairs
.iter()
.map(|(atom_idx, code)| format!("{}{}", atom_idx + 1, code))
.collect();
format!("({})", parts.join(","))
};
Ok(format!("{prefix}-{base}"))
}
#[cfg(test)]
mod tests {
use super::*;
use chematic_smiles::parse;
#[test]
fn achiral_butane_no_prefix() {
let mol = parse("CCCC").unwrap();
let name = iupac_name_stereo(&mol).unwrap();
assert!(!name.starts_with('('), "achiral molecule: no stereo prefix, got {name}");
}
#[test]
fn r_2_butanol_has_r_prefix() {
let mol = parse("[C@@H](O)(CC)C").unwrap();
let name = iupac_name_stereo(&mol);
if let Ok(n) = name {
assert!(
n.starts_with("(R)") || n.starts_with("(S)"),
"chiral 2-butanol should carry stereo prefix, got {n}"
);
}
}
#[test]
fn achiral_cyclohexane_no_prefix() {
let mol = parse("C1CCCCC1").unwrap();
let name = iupac_name_stereo(&mol).unwrap();
assert!(!name.starts_with('('), "achiral cyclohexane: no prefix, got {name}");
}
}