use std::fmt;
use std::str::FromStr;

use crate::error::TaxError;
use crate::segment::TaxSegment;

/// The prefix that marks a metadata value as a taxonomy path.
pub const PREFIX: &str = "taxonomy:/";

/// A rooted, `/`-separated taxonomy path with optional per-segment rank annotations.
///
/// Stored form: `taxonomy:/seg1@rank1/seg2/seg3@rank3`
/// The leading `taxonomy:/` is the discriminator; the remainder is one or more
/// `/`-separated segments, each of the form `name` or `name@rank`.
///
/// `@` is reserved and may not appear in segment names or rank names.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaxPath {
    segments: Vec<TaxSegment>,
}

impl TaxPath {
    pub fn parse(s: &str) -> Result<Self, TaxError> {
        let tail = s.strip_prefix(PREFIX).ok_or(TaxError::MissingPrefix)?;
        if tail.is_empty() {
            return Err(TaxError::EmptyPath);
        }
        let segments = tail.split('/')
            .map(TaxSegment::parse)
            .collect::<Result<Vec<_>, _>>()?;
        Ok(Self { segments })
    }

    /// True if `self` is an ancestor of — or equal to — `other`.
    ///
    /// Comparison is by segment name only; rank annotations are ignored.
    /// `self` must be a prefix of `other` at segment granularity.
    pub fn is_ancestor_of(&self, other: &TaxPath) -> bool {
        self.segments.len() <= other.segments.len()
            && self.segments.iter().zip(other.segments.iter())
                .all(|(a, b)| a.name() == b.name())
    }

    /// Returns the name of the first segment whose rank equals `rank`, if any.
    pub fn name_at_rank(&self, rank: &str) -> Option<&str> {
        self.segments.iter()
            .find(|s| s.rank() == Some(rank))
            .map(|s| s.name())
    }

    /// True if any segment has the given rank.
    pub fn has_rank(&self, rank: &str) -> bool {
        self.segments.iter().any(|s| s.rank() == Some(rank))
    }

    /// True if the path contains a segment with both the given rank and name.
    pub fn matches_rank(&self, rank: &str, name: &str) -> bool {
        self.segments.iter().any(|s| s.rank() == Some(rank) && s.name() == name)
    }

    pub fn segments(&self) -> &[TaxSegment] { &self.segments }
    pub fn depth(&self)    -> usize          { self.segments.len() }
    pub fn is_empty(&self) -> bool           { self.segments.is_empty() }
}

impl fmt::Display for TaxPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", PREFIX)?;
        let mut first = true;
        for seg in &self.segments {
            if !first { write!(f, "/")?; }
            write!(f, "{seg}")?;
            first = false;
        }
        Ok(())
    }
}

impl FromStr for TaxPath {
    type Err = TaxError;
    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::parse(s) }
}
