use std::fmt;

use crate::error::TaxError;

/// A single node in a taxonomy path: a name and an optional rank.
///
/// Neither `name` nor `rank` may contain `@` (reserved separator).
/// Serialised form: `name` or `name@rank`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaxSegment {
    name: String,
    rank: Option<String>,
}

impl TaxSegment {
    pub fn parse(raw: &str) -> Result<Self, TaxError> {
        let parts: Vec<&str> = raw.splitn(3, '@').collect();

        let (name_raw, rank_raw) = match parts.as_slice() {
            [name]        => (*name, None),
            [name, rank]  => (*name, Some(*rank)),
            _             => return Err(TaxError::AmbiguousRank { segment: raw.to_string() }),
        };

        if name_raw.is_empty() {
            return Err(TaxError::EmptySegmentName);
        }

        let rank = match rank_raw {
            None     => None,
            Some("") => return Err(TaxError::EmptyRankName { segment: raw.to_string() }),
            Some(r)  => Some(r.to_string()),
        };

        Ok(Self { name: name_raw.to_string(), rank })
    }

    pub fn name(&self) -> &str { &self.name }
    pub fn rank(&self) -> Option<&str> { self.rank.as_deref() }
}

impl fmt::Display for TaxSegment {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.rank {
            None    => write!(f, "{}", self.name),
            Some(r) => write!(f, "{}@{}", self.name, r),
        }
    }
}
