//! Path pattern parsing and validation
//!
//! Converts `:param` / `*` syntax into matchit's `{param}` / `{*catchall}`
//! syntax and validates the pattern at registration time.

use std::collections::HashSet;
use std::fmt;

/// Errors that can occur during pattern parsing.
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum PatternError {
    #[error("Pattern cannot be empty")]
    EmptyPattern,
    #[error("Invalid parameter: {0}")]
    InvalidParameter(String),
    #[error("Duplicate parameter name: {0}")]
    DuplicateParameter(String),
    #[error("Invalid wildcard: {0}")]
    InvalidWildcard(String),
    #[error("Invalid syntax: {0}")]
    InvalidSyntax(String),
}

/// A parsed and validated route pattern.
///
/// Stores the original `:param` form for display and the matchit-compatible
/// `{param}` form for insertion into the radix tree.
#[derive(Debug, Clone)]
pub struct Pattern {
    original: String,
    matchit_pattern: String,
}

impl Pattern {
    /// Parse and validate a pattern string.
    ///
    /// Accepts:
    /// - Exact: `/users`
    /// - Parameterised: `/users/:id`, `/users/:id/posts/:post_id`
    /// - Prefix wildcard: `/api/*`
    /// - Catch-all: `*`
    pub fn parse(pattern: &str) -> Result<Self, PatternError> {
        if pattern.is_empty() {
            return Err(PatternError::EmptyPattern);
        }

        let param_names = Self::extract_params(pattern)?;
        Self::validate_no_duplicates(&param_names)?;
        Self::validate_wildcards(pattern)?;
        let matchit_pattern = Self::convert_to_matchit(pattern)?;

        Ok(Self {
            original: pattern.to_string(),
            matchit_pattern,
        })
    }

    /// The matchit-compatible pattern string used for radix-tree insertion.
    pub fn matchit_pattern(&self) -> &str {
        &self.matchit_pattern
    }

    // ── private helpers ──────────────────────────────────────────────────────

    fn extract_params(pattern: &str) -> Result<Vec<String>, PatternError> {
        let mut params = Vec::new();
        for segment in pattern.split('/') {
            if let Some(param) = segment.strip_prefix(':') {
                if param.is_empty() {
                    return Err(PatternError::InvalidParameter(
                        "Parameter name cannot be empty".to_string(),
                    ));
                }
                if !param
                    .chars()
                    .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
                {
                    return Err(PatternError::InvalidParameter(format!(
                        "Parameter name '{}' contains invalid characters (use only alphanumeric, _, -)",
                        param
                    )));
                }
                params.push(param.to_string());
            }
        }
        Ok(params)
    }

    fn validate_no_duplicates(params: &[String]) -> Result<(), PatternError> {
        let mut seen = HashSet::new();
        for param in params {
            if !seen.insert(param) {
                return Err(PatternError::DuplicateParameter(param.clone()));
            }
        }
        Ok(())
    }

    fn validate_wildcards(pattern: &str) -> Result<(), PatternError> {
        if pattern.contains('*') && pattern != "*" && !pattern.ends_with("/*") {
            if let Some(pos) = pattern.find('*') {
                if pos < pattern.len() - 1 && !pattern[pos..].starts_with("/*") {
                    return Err(PatternError::InvalidWildcard(
                        "Wildcard (*) must be at the end of the pattern".to_string(),
                    ));
                }
            }
        }
        Ok(())
    }

    fn convert_to_matchit(pattern: &str) -> Result<String, PatternError> {
        if pattern == "*" {
            return Ok("{*catchall}".to_string());
        }
        if pattern.ends_with("/*") {
            let prefix = pattern.trim_end_matches("/*");
            return Ok(if prefix.is_empty() {
                "{*catchall}".to_string()
            } else {
                format!("{}/{{*catchall}}", prefix)
            });
        }
        let converted = pattern
            .split('/')
            .map(|seg| {
                if let Some(param) = seg.strip_prefix(':') {
                    Ok(format!("{{{}}}", param))
                } else {
                    Ok(seg.to_string())
                }
            })
            .collect::<Result<Vec<_>, PatternError>>()?
            .join("/");
        Ok(converted)
    }
}

impl fmt::Display for Pattern {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.original)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // ── valid patterns ────────────────────────────────────────────────────────

    #[test]
    fn test_exact() {
        let p = Pattern::parse("/users").unwrap();
        assert_eq!(p.matchit_pattern(), "/users");
        assert_eq!(format!("{p}"), "/users");
    }

    #[test]
    fn test_single_param() {
        let p = Pattern::parse("/users/:id").unwrap();
        assert_eq!(p.matchit_pattern(), "/users/{id}");
    }

    #[test]
    fn test_multiple_params() {
        let p = Pattern::parse("/users/:id/posts/:post_id").unwrap();
        assert_eq!(p.matchit_pattern(), "/users/{id}/posts/{post_id}");
    }

    #[test]
    fn test_param_with_underscore() {
        let p = Pattern::parse("/users/:user_id").unwrap();
        assert_eq!(p.matchit_pattern(), "/users/{user_id}");
    }

    #[test]
    fn test_param_with_dash() {
        let p = Pattern::parse("/users/:user-id").unwrap();
        assert_eq!(p.matchit_pattern(), "/users/{user-id}");
    }

    #[test]
    fn test_prefix_wildcard() {
        let p = Pattern::parse("/api/*").unwrap();
        assert_eq!(p.matchit_pattern(), "/api/{*catchall}");
    }

    #[test]
    fn test_catchall() {
        let p = Pattern::parse("*").unwrap();
        assert_eq!(p.matchit_pattern(), "{*catchall}");
    }

    #[test]
    fn test_root() {
        let p = Pattern::parse("/").unwrap();
        assert_eq!(p.matchit_pattern(), "/");
    }

    #[test]
    fn test_nested_path() {
        let p = Pattern::parse("/api/v1/users").unwrap();
        assert_eq!(p.matchit_pattern(), "/api/v1/users");
    }

    #[test]
    fn test_mixed_params_and_static() {
        let p = Pattern::parse("/api/v1/users/:id/profile").unwrap();
        assert_eq!(p.matchit_pattern(), "/api/v1/users/{id}/profile");
    }

    #[test]
    fn test_many_params() {
        let p = Pattern::parse("/a/:b/c/:d/e/:f/g/:h").unwrap();
        assert_eq!(p.matchit_pattern(), "/a/{b}/c/{d}/e/{f}/g/{h}");
    }

    // ── error cases ───────────────────────────────────────────────────────────

    #[test]
    fn test_empty_pattern() {
        assert_eq!(Pattern::parse("").unwrap_err(), PatternError::EmptyPattern);
    }

    #[test]
    fn test_empty_param_name() {
        let err = Pattern::parse("/users/:").unwrap_err();
        assert!(matches!(err, PatternError::InvalidParameter(_)));
    }

    #[test]
    fn test_invalid_param_chars() {
        let err = Pattern::parse("/users/:id@123").unwrap_err();
        assert!(matches!(err, PatternError::InvalidParameter(_)));
    }

    #[test]
    fn test_duplicate_params() {
        let err = Pattern::parse("/users/:id/posts/:id").unwrap_err();
        assert!(matches!(err, PatternError::DuplicateParameter(ref n) if n == "id"));
    }

    #[test]
    fn test_wildcard_in_middle() {
        let err = Pattern::parse("/api/*/users").unwrap_err();
        assert!(matches!(err, PatternError::InvalidWildcard(_)));
    }

    // ── error display ─────────────────────────────────────────────────────────

    #[test]
    fn test_error_display() {
        assert_eq!(
            PatternError::EmptyPattern.to_string(),
            "Pattern cannot be empty"
        );
        assert_eq!(
            PatternError::InvalidParameter("x".into()).to_string(),
            "Invalid parameter: x"
        );
        assert_eq!(
            PatternError::DuplicateParameter("id".into()).to_string(),
            "Duplicate parameter name: id"
        );
        assert_eq!(
            PatternError::InvalidWildcard("x".into()).to_string(),
            "Invalid wildcard: x"
        );
        assert_eq!(
            PatternError::InvalidSyntax("x".into()).to_string(),
            "Invalid syntax: x"
        );
    }
}
