use crate::error::TaxError;
use crate::path::TaxPath;
use crate::segment::TaxSegment;
use crate::segment_pattern::SegmentPattern;

/// A query pattern for matching against stored `TaxPath` values.
///
/// Syntax:
///
/// | Form     | Semantics |
/// |----------|-----------|
/// | `A/B`    | A then B as a contiguous sub-path, anywhere in the value |
/// | `/A/B`   | value starts with A then B (start-anchored) |
/// | `A/B$`   | value ends with A then B (end-anchored) |
/// | `/A/B$`  | value is exactly A then B (fully anchored) |
/// | `A@x/B`  | A with rank `x`, followed by B with any rank |
///
/// A segment pattern without `@` matches any segment with that name regardless
/// of its stored rank.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaxPattern {
    start_anchored: bool,
    end_anchored:   bool,
    segments:       Vec<SegmentPattern>,
}

impl TaxPattern {
    pub fn parse(s: &str) -> Result<Self, TaxError> {
        let s = s.trim();

        let start_anchored = s.starts_with('/');
        let s = if start_anchored { &s[1..] } else { s };

        let end_anchored = s.ends_with('$');
        let s = if end_anchored { &s[..s.len() - 1] } else { s };

        if s.is_empty() {
            return Err(TaxError::EmptyPattern);
        }

        let segments = s.split('/')
            .map(SegmentPattern::parse)
            .collect::<Result<Vec<_>, _>>()?;

        Ok(Self { start_anchored, end_anchored, segments })
    }

    /// True if this pattern matches `path` according to the anchor flags.
    ///
    /// The pattern must match a contiguous run of segments in the path.
    /// Start/end anchors restrict where that run may begin or end.
    pub fn matches(&self, path: &TaxPath) -> bool {
        let n = self.segments.len();
        let m = path.depth();

        if n > m { return false; }

        let segs = path.segments();
        match (self.start_anchored, self.end_anchored) {
            (true,  true)  => n == m && self.window_matches(segs, 0),
            (true,  false) => self.window_matches(segs, 0),
            (false, true)  => self.window_matches(segs, m - n),
            (false, false) => (0..=(m - n)).any(|i| self.window_matches(segs, i)),
        }
    }

    fn window_matches(&self, segs: &[TaxSegment], start: usize) -> bool {
        self.segments.iter()
            .zip(segs[start..start + self.segments.len()].iter())
            .all(|(pat, seg)| pat.matches(seg))
    }
}
