// Validation logic for placeholder/value pairs against casing conventions
//
// This module validates that placeholder names and their values match one of the
// supported casing conventions (PascalCase, camelCase, snake_case, etc.) using
// regex pattern matching.

use lazy_static::lazy_static;
use regex::Regex;

// Pre-compiled regex patterns for each casing convention
// Patterns are tested in order (top to bottom), first match wins

lazy_static! {
    /// PascalCase: Must start with uppercase and contain at least one lowercase letter
    /// Examples: ByItem, UserId, Item (but not A, HELLO)
    /// Pattern: Starts with uppercase, has at least one lowercase somewhere
    pub static ref PASCAL_CASE_PATTERN: Regex = 
        Regex::new(r"^[A-Z][a-zA-Z0-9]*[a-z]+[a-zA-Z0-9]*$|^[A-Z][a-z]+[a-zA-Z0-9]*$").unwrap();

    /// camelCase: Must contain at least one uppercase letter after the first character
    /// Examples: byItem, userId (but not item, highT needs uppercase)
    pub static ref CAMEL_CASE_PATTERN: Regex = 
        Regex::new(r"^[a-z][a-z0-9]*[A-Z]([a-z0-9]*[A-Z])*[a-z0-9]*$").unwrap();

    /// lower_snake_case: Must contain at least one underscore
    /// Examples: by_item, user_id (but not item)
    pub static ref LOWER_SNAKE_CASE_PATTERN: Regex = 
        Regex::new(r"^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)+$").unwrap();

    /// SCREAMING_SNAKE_CASE: Must contain at least one underscore
    /// Examples: BY_ITEM, USER_ID (but not ITEM)
    pub static ref SCREAMING_SNAKE_CASE_PATTERN: Regex = 
        Regex::new(r"^[A-Z][A-Z0-9]*(_[A-Z][A-Z0-9]*)+$").unwrap();

    /// kebab-case: Must contain at least one hyphen
    /// Examples: by-item, user-id (but not item)
    pub static ref KEBAB_CASE_PATTERN: Regex = 
        Regex::new(r"^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)+$").unwrap();

    /// UPPERCASE: All letters uppercase, no separators, no lowercase letters allowed
    /// Examples: BYITEM, HELLO, ITEM
    pub static ref UPPERCASE_PATTERN: Regex = 
        Regex::new(r"^[A-Z][A-Z0-9]*$").unwrap();

    /// lowercase: All letters lowercase, no separators, no uppercase letters allowed
    /// Examples: byitem, hello, item
    pub static ref LOWERCASE_PATTERN: Regex = 
        Regex::new(r"^[a-z][a-z0-9]*$").unwrap();
}

use crate::case_transform::CasingConvention;

/// Detect the casing convention of a placeholder name or value
/// Tests patterns in order (first match wins): PascalCase, camelCase, snake_case, etc.
pub fn detect_convention(text: &str) -> Option<CasingConvention> {
    // Test patterns in order from spec (multi-word before single-word)
    // PascalCase: starts with uppercase, contains at least one lowercase, no separators
    if text.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
        && text.chars().any(|c| c.is_lowercase())
        && !text.contains('_')
        && !text.contains('-')
        && text.chars().all(|c| c.is_alphanumeric())
    {
        return Some(CasingConvention::PascalCase);
    }
    if CAMEL_CASE_PATTERN.is_match(text) {
        return Some(CasingConvention::CamelCase);
    }
    if LOWER_SNAKE_CASE_PATTERN.is_match(text) {
        return Some(CasingConvention::LowerSnakeCase);
    }
    if SCREAMING_SNAKE_CASE_PATTERN.is_match(text) {
        return Some(CasingConvention::ScreamingSnakeCase);
    }
    if KEBAB_CASE_PATTERN.is_match(text) {
        return Some(CasingConvention::KebabCase);
    }
    if UPPERCASE_PATTERN.is_match(text) {
        return Some(CasingConvention::Uppercase);
    }
    if LOWERCASE_PATTERN.is_match(text) {
        return Some(CasingConvention::Lowercase);
    }
    
    // No pattern matched
    None
}

/// Validate that a placeholder name and value match the same convention
/// Returns the matched convention if valid, or None if no match
pub fn validate_placeholder_value_pair(
    placeholder_name: &str,
    value: &str,
) -> Option<CasingConvention> {
    // Strip __ delimiters if present to get the inner name
    let inner_name = placeholder_name.trim_start_matches("__").trim_end_matches("__");
    
    // Detect convention from placeholder inner name
    let name_convention = detect_convention(inner_name)?;
    
    // Test if value matches the same convention's pattern
    let value_convention = detect_convention(value);
    
    match value_convention {
        Some(conv) if conv == name_convention => Some(conv),
        _ => {
            // Special case: lowercase placeholders can use fallback for any value
            if name_convention == CasingConvention::Lowercase {
                Some(CasingConvention::Fallback)
            } else {
                None
            }
        }
    }
}

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

    #[test]
    fn test_detect_pascal_case() {
        assert_eq!(detect_convention("ByItem"), Some(CasingConvention::PascalCase));
        assert_eq!(detect_convention("UserEmail"), Some(CasingConvention::PascalCase));
        assert_eq!(detect_convention("Item"), Some(CasingConvention::PascalCase));
        
        // Should NOT match PascalCase (no lowercase)
        assert_ne!(detect_convention("HELLO"), Some(CasingConvention::PascalCase));
    }

    #[test]
    fn test_detect_camel_case() {
        assert_eq!(detect_convention("byItem"), Some(CasingConvention::CamelCase));
        assert_eq!(detect_convention("userEmail"), Some(CasingConvention::CamelCase));
        
        // Should NOT match camelCase (no uppercase)
        assert_ne!(detect_convention("item"), Some(CasingConvention::CamelCase));
    }

    #[test]
    fn test_detect_snake_case() {
        assert_eq!(detect_convention("by_item"), Some(CasingConvention::LowerSnakeCase));
        assert_eq!(detect_convention("user_email"), Some(CasingConvention::LowerSnakeCase));
        
        // Should NOT match snake_case (no underscore)
        assert_ne!(detect_convention("item"), Some(CasingConvention::LowerSnakeCase));
    }

    #[test]
    fn test_detect_screaming_snake_case() {
        assert_eq!(detect_convention("BY_ITEM"), Some(CasingConvention::ScreamingSnakeCase));
        assert_eq!(detect_convention("USER_EMAIL"), Some(CasingConvention::ScreamingSnakeCase));
    }

    #[test]
    fn test_detect_kebab_case() {
        assert_eq!(detect_convention("by-item"), Some(CasingConvention::KebabCase));
        assert_eq!(detect_convention("user-email"), Some(CasingConvention::KebabCase));
    }

    #[test]
    fn test_detect_uppercase() {
        assert_eq!(detect_convention("BYITEM"), Some(CasingConvention::Uppercase));
        assert_eq!(detect_convention("HELLO"), Some(CasingConvention::Uppercase));
    }

    #[test]
    fn test_detect_lowercase() {
        assert_eq!(detect_convention("byitem"), Some(CasingConvention::Lowercase));
        assert_eq!(detect_convention("hello"), Some(CasingConvention::Lowercase));
    }

    #[test]
    fn test_validate_matching_pair() {
        // PascalCase placeholder with PascalCase value
        assert_eq!(
            validate_placeholder_value_pair("AliasName", "ByItem"),
            Some(CasingConvention::PascalCase)
        );
        
        // snake_case placeholder with snake_case value
        assert_eq!(
            validate_placeholder_value_pair("index_name", "user_email"),
            Some(CasingConvention::LowerSnakeCase)
        );
    }

    #[test]
    fn test_validate_mismatched_pair() {
        // PascalCase placeholder with lowercase value (should fail)
        assert_eq!(
            validate_placeholder_value_pair("AliasName", "byitem"),
            None
        );
    }

    #[test]
    fn test_validate_fallback_for_lowercase() {
        // Lowercase placeholder with non-matching value should use fallback
        assert_eq!(
            validate_placeholder_value_pair("aliasname", "by-item"),
            Some(CasingConvention::Fallback)
        );
    }
}
