// Case transformation module for placeholder variant generation
//
// This module provides case transformation capabilities for dtox placeholders.
// When a placeholder is defined with a specific casing convention (e.g., __AliasName__),
// this module automatically generates variants in other casing conventions
// (e.g., __alias_name__, __alias-name__, __ALIAS_NAME__, etc.).

use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

pub mod detection;
pub mod transform;
pub mod validation;
pub mod variants;

// Re-export public types
pub use detection::*;
pub use transform::*;
pub use validation::*;
pub use variants::*;

/// Represents the casing convention detected for a placeholder name or value
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CasingConvention {
    /// Each word starts with uppercase, no separators (e.g., `ByItem`)
    PascalCase,
    /// First word lowercase, subsequent words capitalized (e.g., `byItem`)
    CamelCase,
    /// Lowercase words separated by underscores (e.g., `by_item`)
    LowerSnakeCase,
    /// Uppercase words separated by underscores (e.g., `BY_ITEM`)
    ScreamingSnakeCase,
    /// Lowercase words separated by hyphens (e.g., `by-item`)
    KebabCase,
    /// All uppercase, no separators (e.g., `BYITEM`)
    Uppercase,
    /// All lowercase, no separators (e.g., `byitem`)
    Lowercase,
    /// Matches any value (for lowercase placeholders only)
    Fallback,
}

/// A single definition of a placeholder with its source location and detected convention
#[derive(Debug, Clone)]
pub struct PlaceholderDefinition {
    /// The full placeholder name (e.g., `__AliasName__`)
    pub placeholder_name: String,
    /// The name without delimiters (e.g., `AliasName`)
    pub inner_name: String,
    /// Normalized form for conflict detection (e.g., `aliasname`)
    pub normalized_name: String,
    /// The extracted value (e.g., `ByItem`)
    pub value: String,
    /// The detected convention
    pub convention: CasingConvention,
    /// File where defined
    pub source_file: PathBuf,
    /// Line number in source file
    pub line_number: usize,
}

/// Validated placeholder metadata with cached transformations
#[derive(Debug, Clone)]
pub struct PlaceholderMetadata {
    /// Original placeholder name (e.g., `__AliasName__`)
    pub placeholder_name: String,
    /// Original value (e.g., `ByItem`)
    pub source_value: String,
    /// Detected convention
    pub source_convention: CasingConvention,
    /// Word boundaries detected (e.g., `["By", "Item"]`)
    pub words: Vec<String>,
    /// Lazy-computed variants
    pub cached_variants: HashMap<CasingConvention, String>,
    /// Variants allowed by matrix
    pub allowed_variants: HashSet<CasingConvention>,
    /// True for `__dtoname__`, `__dtoversion__`
    pub is_special_builtin: bool,
}

impl PlaceholderMetadata {
    /// Create new placeholder metadata from a validated definition
    pub fn new(
        placeholder_name: String,
        source_value: String,
        source_convention: CasingConvention,
        words: Vec<String>,
        is_special_builtin: bool,
    ) -> Self {
        let matrix = VariantGenerationMatrix::new();
        let allowed_variants = matrix
            .allowed_variants(source_convention)
            .cloned()
            .unwrap_or_default();
        
        Self {
            placeholder_name,
            source_value,
            source_convention,
            words,
            cached_variants: HashMap::new(),
            allowed_variants,
            is_special_builtin,
        }
    }
    
    /// Compute a variant for the target casing convention
    /// Returns None if the variant is not allowed by the generation matrix
    pub fn compute_variant(&self, target: CasingConvention) -> Option<String> {
        // Check if this variant is allowed
        if !self.allowed_variants.contains(&target) {
            return None;
        }
        
        // Apply the appropriate transformation
        let result = match target {
            CasingConvention::Lowercase => to_lowercase(&self.words),
            CasingConvention::Uppercase => to_uppercase(&self.words),
            CasingConvention::PascalCase => to_pascal_case(&self.words),
            CasingConvention::CamelCase => to_camel_case(&self.words),
            CasingConvention::LowerSnakeCase => to_lower_snake_case(&self.words),
            CasingConvention::ScreamingSnakeCase => to_screaming_snake_case(&self.words),
            CasingConvention::KebabCase => to_kebab_case(&self.words),
            CasingConvention::Fallback => {
                // Fallback just returns the source value as-is
                self.source_value.clone()
            }
        };
        
        Some(result)
    }
    
    /// Get a variant with lazy caching
    /// Computes the variant if not already cached, then returns a reference
    pub fn get_variant(&mut self, target: CasingConvention) -> Option<&str> {
        // Check if already cached (without borrowing beyond this check)
        if !self.cached_variants.contains_key(&target) {
            // Compute and cache if not present
            if let Some(computed) = self.compute_variant(target) {
                self.cached_variants.insert(target, computed);
            } else {
                return None;
            }
        }
        
        // Now return the cached value
        self.cached_variants.get(&target).map(|s| s.as_str())
    }
}

/// Central registry tracking all placeholder definitions
#[derive(Debug, Default, Clone)]
pub struct PlaceholderRegistry {
    /// Key: normalized name; Value: all definitions
    pub definitions: HashMap<String, Vec<PlaceholderDefinition>>,
    /// Set of normalized names rejected due to mixed casing
    pub rejected: HashSet<String>,
    /// Validated placeholders ready for use
    pub validated: HashMap<String, PlaceholderMetadata>,
}

impl PlaceholderRegistry {
    /// Create a new empty registry
    pub fn new() -> Self {
        Self::default()
    }
    
    /// Add a placeholder definition to the registry
    pub fn add_definition(&mut self, def: PlaceholderDefinition) {
        let normalized_name = def.normalized_name.clone();
        self.definitions
            .entry(normalized_name)
            .or_default()
            .push(def);
    }
    
    /// Validate consistency: check for mixed casing conflicts
    /// Returns a list of validation errors for placeholders with multiple conventions
    pub fn validate_consistency(&self) -> Vec<ValidationError> {
        self.definitions
            .iter()
            .filter_map(|(normalized_name, defs)| {
                // Check if multiple conventions are used
                let conventions: HashSet<CasingConvention> = 
                    defs.iter().map(|d| d.convention).collect();
                
                if conventions.len() > 1 {
                    Some(ValidationError::MixedCasing {
                        logical_name: normalized_name.clone(),
                        definitions: defs.clone(),
                    })
                } else {
                    None
                }
            })
            .collect()
    }
    
    /// Finalize the registry after all definitions are added
    /// - Populates the rejected set with placeholders that have mixed casing
    /// - Moves valid definitions to the validated map
    /// - Should be called after validate_consistency() and logging warnings
    pub fn finalize(&mut self) {
        // Build rejection set from validation errors
        let errors = self.validate_consistency();
        for error in errors {
            if let ValidationError::MixedCasing { logical_name, .. } = error {
                self.rejected.insert(logical_name);
            }
        }
        
        // Move valid definitions to validated map
        // Note: PlaceholderMetadata creation will be implemented in Phase 3
        // For now, this just sets up the structure
        for (normalized_name, defs) in &self.definitions {
            if !self.rejected.contains(normalized_name) && !defs.is_empty() {
                // Will create PlaceholderMetadata here in Phase 3
                // For now, we just ensure the structure is in place
            }
        }
    }
    
    /// Retrieve validated placeholder metadata by normalized name
    pub fn get_metadata(&self, name: &str) -> Option<&PlaceholderMetadata> {
        self.validated.get(name)
    }
}

/// Validation failures during placeholder processing
#[derive(Debug)]
pub enum ValidationError {
    /// Multiple conventions used for the same logical placeholder
    MixedCasing {
        logical_name: String,
        definitions: Vec<PlaceholderDefinition>,
    },
    /// No table row matched the placeholder/value pair
    NoMatchingConvention {
        placeholder: String,
        value: String,
        file: PathBuf,
        line: usize,
    },
    /// Malformed placeholder
    InvalidPattern {
        placeholder: String,
        reason: String,
        file: PathBuf,
        line: usize,
    },
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_placeholder_metadata_new() {
        let metadata = PlaceholderMetadata::new(
            "__AliasName__".to_string(),
            "ByItem".to_string(),
            CasingConvention::PascalCase,
            vec!["By".to_string(), "Item".to_string()],
            false,
        );
        
        assert_eq!(metadata.placeholder_name, "__AliasName__");
        assert_eq!(metadata.source_value, "ByItem");
        assert_eq!(metadata.source_convention, CasingConvention::PascalCase);
        assert_eq!(metadata.words, vec!["By", "Item"]);
        assert_eq!(metadata.cached_variants.len(), 0);
        assert_eq!(metadata.allowed_variants.len(), 7); // Multi-word generates all 7
    }
    
    #[test]
    fn test_compute_variant_allowed() {
        let metadata = PlaceholderMetadata::new(
            "__AliasName__".to_string(),
            "ByItem".to_string(),
            CasingConvention::PascalCase,
            vec!["By".to_string(), "Item".to_string()],
            false,
        );
        
        assert_eq!(
            metadata.compute_variant(CasingConvention::LowerSnakeCase),
            Some("by_item".to_string())
        );
        assert_eq!(
            metadata.compute_variant(CasingConvention::KebabCase),
            Some("by-item".to_string())
        );
        assert_eq!(
            metadata.compute_variant(CasingConvention::CamelCase),
            Some("byItem".to_string())
        );
    }
    
    #[test]
    fn test_compute_variant_not_allowed() {
        // Lowercase source only allows 3 variants
        let metadata = PlaceholderMetadata::new(
            "__aliasname__".to_string(),
            "byitem".to_string(),
            CasingConvention::Lowercase,
            vec!["byitem".to_string()],
            false,
        );
        
        assert_eq!(metadata.allowed_variants.len(), 3);
        assert!(metadata.compute_variant(CasingConvention::Lowercase).is_some());
        assert!(metadata.compute_variant(CasingConvention::Uppercase).is_some());
        assert!(metadata.compute_variant(CasingConvention::PascalCase).is_some());
        
        // These should NOT be allowed
        assert!(metadata.compute_variant(CasingConvention::CamelCase).is_none());
        assert!(metadata.compute_variant(CasingConvention::LowerSnakeCase).is_none());
        assert!(metadata.compute_variant(CasingConvention::KebabCase).is_none());
    }
    
    #[test]
    fn test_get_variant_caching() {
        let mut metadata = PlaceholderMetadata::new(
            "__AliasName__".to_string(),
            "ByItem".to_string(),
            CasingConvention::PascalCase,
            vec!["By".to_string(), "Item".to_string()],
            false,
        );
        
        // First call computes and caches
        assert_eq!(metadata.cached_variants.len(), 0);
        let result1 = metadata.get_variant(CasingConvention::LowerSnakeCase);
        assert_eq!(result1, Some("by_item"));
        assert_eq!(metadata.cached_variants.len(), 1);
        
        // Second call returns cached value
        let result2 = metadata.get_variant(CasingConvention::LowerSnakeCase);
        assert_eq!(result2, Some("by_item"));
        assert_eq!(metadata.cached_variants.len(), 1);
        
        // Different variant computes new
        let result3 = metadata.get_variant(CasingConvention::KebabCase);
        assert_eq!(result3, Some("by-item"));
        assert_eq!(metadata.cached_variants.len(), 2);
    }
    
    #[test]
    fn test_fallback_convention() {
        let metadata = PlaceholderMetadata::new(
            "__aliasname__".to_string(),
            "by-item".to_string(),
            CasingConvention::Fallback,
            vec!["by-item".to_string()],
            false,
        );
        
        // Fallback only allows lowercase variant (uses source as-is)
        assert_eq!(
            metadata.compute_variant(CasingConvention::Lowercase),
            Some("by-item".to_string())
        );
    }
}
