//! Alias Generator - Heuristic-based semantic alias generation
//!
//! Generates human-readable short names (aliases) for Nickel schema field paths
//! using configurable heuristic rules.
//!
//! # Examples
//!
//! ```
//! use typedialog_core::nickel::AliasGenerator;
//!
//! let path = vec!["tracker".to_string(), "core".to_string(), "database".to_string(), "driver".to_string()];
//! assert_eq!(AliasGenerator::generate(&path), Some("database_driver".to_string()));
//! ```

/// Generates semantic aliases for Nickel field paths
pub struct AliasGenerator;

impl AliasGenerator {
    /// Generate an alias for a field path using heuristic rules
    ///
    /// Returns `None` if the path should use flat_name instead of an alias.
    ///
    /// # Rules
    ///
    /// 1. Very short paths (1 element) return None
    /// 2. Semantic patterns are checked first (features.X.enabled → enable_X)
    /// 3. Common prefixes (tracker, core, config) are dropped
    /// 4. Array indicators (plural forms) are singularized
    /// 5. Path elements are joined with underscore
    ///
    /// # Examples
    ///
    /// - `["tracker", "core", "database", "driver"]` → `"database_driver"`
    /// - `["features", "prometheus", "enabled"]` → `"enable_prometheus"`
    /// - `["ssh_credentials", "username"]` → `"ssh_username"`
    pub fn generate(path: &[String]) -> Option<String> {
        // Rule 1: Single element paths don't need aliases
        if path.is_empty() || path.len() == 1 {
            return None;
        }

        // Rule 2: Check semantic patterns first (on original path)
        if let Some(alias) = Self::apply_semantic_patterns(path) {
            return Some(alias);
        }

        // Rule 3: Drop common prefixes
        let trimmed = Self::drop_common_prefixes(path);

        // If nothing left after trimming, use last element
        if trimmed.is_empty() {
            return path.last().cloned();
        }

        // Rule 4: Singularize array indicators
        let singularized = Self::singularize_arrays(&trimmed);

        // Rule 5: Join path elements with underscore
        Some(singularized.join("_"))
    }

    /// Drop common prefixes from the beginning of a path
    fn drop_common_prefixes(path: &[String]) -> Vec<String> {
        const COMMON_PREFIXES: &[&str] = &["tracker", "core", "config"];

        let mut start_idx = 0;
        for elem in path.iter() {
            if COMMON_PREFIXES.contains(&elem.as_str()) {
                start_idx += 1;
            } else {
                break;
            }
        }

        if start_idx >= path.len() {
            return path.to_vec();
        }
        path[start_idx..].to_vec()
    }

    /// Singularize plural forms commonly used for arrays
    fn singularize_arrays(path: &[String]) -> Vec<String> {
        let singular_map = [
            ("trackers", "tracker"),
            ("servers", "server"),
            ("hosts", "host"),
            ("ports", "port"),
            ("addresses", "address"),
            ("credentials", "credential"),
            ("settings", "setting"),
            ("features", "feature"),
            ("options", "option"),
            ("items", "item"),
            ("tags", "tag"),
        ];

        path.iter()
            .map(|elem| {
                for (plural, singular) in &singular_map {
                    if elem == *plural {
                        return singular.to_string();
                    }
                }
                elem.clone()
            })
            .collect()
    }

    /// Apply semantic patterns based on field path structure
    ///
    /// Returns `Some(alias)` if a semantic pattern matches, `None` otherwise.
    fn apply_semantic_patterns(path: &[String]) -> Option<String> {
        if path.is_empty() {
            return None;
        }

        // Pattern: features.X.enabled → enable_X
        if path.len() >= 2 && path[0] == "features" && path.last() == Some(&"enabled".to_string()) {
            let feature_name = &path[1];
            return Some(format!("enable_{}", feature_name));
        }

        // Pattern: api.X → X (for simple API configs)
        if path.len() == 2 && path[0] == "api" {
            return Some(path[1].clone());
        }

        None
    }
}

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

    #[test]
    fn test_single_element_returns_none() {
        assert_eq!(AliasGenerator::generate(&["name".to_string()]), None);
    }

    #[test]
    fn test_two_element_simple_path() {
        // Two-element paths without semantic patterns join with underscore
        let path = vec!["user".to_string(), "name".to_string()];
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("user_name".to_string())
        );
    }

    #[test]
    fn test_database_driver_pattern() {
        let path = vec![
            "tracker".to_string(),
            "core".to_string(),
            "database".to_string(),
            "driver".to_string(),
        ];
        // Drops tracker, core prefixes and joins
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("database_driver".to_string())
        );
    }

    #[test]
    fn test_features_enabled_pattern() {
        let path = vec![
            "features".to_string(),
            "prometheus".to_string(),
            "enabled".to_string(),
        ];
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("enable_prometheus".to_string())
        );
    }

    #[test]
    fn test_features_grafana_enabled() {
        let path = vec![
            "features".to_string(),
            "grafana".to_string(),
            "enabled".to_string(),
        ];
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("enable_grafana".to_string())
        );
    }

    #[test]
    fn test_http_tracker_bind_address() {
        let path = vec![
            "tracker".to_string(),
            "core".to_string(),
            "http".to_string(),
            "bind_address".to_string(),
        ];
        // Drops tracker, core and joins
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("http_bind_address".to_string())
        );
    }

    #[test]
    fn test_ssh_credentials_username() {
        let path = vec!["ssh_credentials".to_string(), "username".to_string()];
        // Two-element path joins with underscore
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("ssh_credentials_username".to_string())
        );
    }

    #[test]
    fn test_singularize_arrays() {
        // Longer path with plural array indicator
        let long_path = vec![
            "tracker".to_string(),
            "core".to_string(),
            "http_trackers".to_string(),
            "bind_address".to_string(),
        ];
        // After dropping tracker, core: ["http_trackers", "bind_address"]
        // http_trackers is not singularized (pattern doesn't match exactly)
        // So result joins to: http_trackers_bind_address
        assert_eq!(
            AliasGenerator::generate(&long_path),
            Some("http_trackers_bind_address".to_string())
        );
    }

    #[test]
    fn test_drop_common_prefixes() {
        let path = vec![
            "tracker".to_string(),
            "core".to_string(),
            "config".to_string(),
            "database".to_string(),
            "driver".to_string(),
        ];
        // Should drop tracker, core, config prefixes
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("database_driver".to_string())
        );
    }

    #[test]
    fn test_api_pattern() {
        let path = vec!["api".to_string(), "bind_address".to_string()];
        // Pattern: api.X → X
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("bind_address".to_string())
        );
    }

    #[test]
    fn test_three_element_path() {
        let path = vec![
            "tracker".to_string(),
            "http".to_string(),
            "bind_address".to_string(),
        ];
        // Drops tracker prefix and joins
        assert_eq!(
            AliasGenerator::generate(&path),
            Some("http_bind_address".to_string())
        );
    }
}
