//! Field Mapper - Bidirectional lookup structure for Nickel field names
//!
//! Provides efficient O(1) lookups across three representations of a field:
//! - By alias (semantic short name): "database_driver"
//! - By flat_name (full path with separators): "tracker-core-database-driver"
//! - By path (vector of nested keys): ["tracker", "core", "database", "driver"]

use super::schema_ir::{NickelFieldIR, NickelSchemaIR};
use std::collections::HashMap;

/// Bidirectional field lookup structure
#[derive(Debug, Clone)]
pub struct FieldMapper {
    alias_to_field: HashMap<String, NickelFieldIR>,
    flat_to_field: HashMap<String, NickelFieldIR>,
    path_to_field: HashMap<Vec<String>, NickelFieldIR>,
}

impl FieldMapper {
    /// Create a FieldMapper from a Nickel schema
    ///
    /// # Errors
    ///
    /// Returns error if alias collision is detected (same alias for different paths)
    pub fn from_schema(schema: &NickelSchemaIR) -> crate::Result<Self> {
        let mut alias_to_field: HashMap<String, NickelFieldIR> = HashMap::new();
        let mut flat_to_field: HashMap<String, NickelFieldIR> = HashMap::new();
        let mut path_to_field: HashMap<Vec<String>, NickelFieldIR> = HashMap::new();

        for field in &schema.fields {
            // flat_name is guaranteed unique by separating paths with '-' instead of '_'
            // This makes flat_name collision impossible (mathematically provable)
            flat_to_field.insert(field.flat_name.clone(), field.clone());

            // path is unique by definition
            path_to_field.insert(field.path.clone(), field.clone());

            // alias must be unique, if present
            if let Some(ref alias) = field.alias {
                if let Some(existing) = alias_to_field.get(alias) {
                    return Err(crate::ErrorWrapper::validation_failed(format!(
                        "Alias collision detected for '{}': used by multiple fields:\n  - {:?}\n  - {:?}\n\n\
                         Solution: Use flat_names (with '-' separator) instead:\n  - {}\n  - {}",
                        alias,
                        existing.path,
                        field.path,
                        existing.flat_name,
                        field.flat_name
                    )));
                }
                alias_to_field.insert(alias.clone(), field.clone());
            }
        }

        Ok(Self {
            alias_to_field,
            flat_to_field,
            path_to_field,
        })
    }

    /// Get field by any key type (alias, flat_name, or path string)
    ///
    /// # Arguments
    ///
    /// * `key` - Field identifier (can be alias, flat_name, or path joined by '-')
    pub fn get(&self, key: &str) -> Option<&NickelFieldIR> {
        // Try alias lookup first (aliases are preferred)
        if let Some(field) = self.alias_to_field.get(key) {
            return Some(field);
        }

        // Try flat_name lookup
        if let Some(field) = self.flat_to_field.get(key) {
            return Some(field);
        }

        None
    }

    /// Get field by path vector
    pub fn get_by_path(&self, path: &[String]) -> Option<&NickelFieldIR> {
        self.path_to_field.get(path)
    }

    /// Get the preferred name for a field (alias if present, otherwise flat_name)
    pub fn preferred_name<'a>(&self, field: &'a NickelFieldIR) -> &'a str {
        field.alias.as_deref().unwrap_or(&field.flat_name)
    }

    /// Get all fields
    pub fn fields(&self) -> impl Iterator<Item = &NickelFieldIR> {
        self.flat_to_field.values()
    }
}

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

    fn create_test_schema() -> NickelSchemaIR {
        NickelSchemaIR {
            name: "test".to_string(),
            description: None,
            fields: vec![
                NickelFieldIR {
                    path: vec![
                        "tracker".to_string(),
                        "core".to_string(),
                        "database".to_string(),
                        "driver".to_string(),
                    ],
                    flat_name: "tracker-core-database-driver".to_string(),
                    alias: Some("database_driver".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["ssh_credentials".to_string(), "username".to_string()],
                    flat_name: "ssh_credentials-username".to_string(),
                    alias: Some("ssh_username".to_string()),
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
                NickelFieldIR {
                    path: vec!["simple".to_string()],
                    flat_name: "simple".to_string(),
                    alias: None,
                    nickel_type: NickelType::String,
                    doc: None,
                    default: None,
                    optional: false,
                    contract: None,
                    contract_call: None,
                    group: None,
                    fragment_marker: None,
                    is_array_of_records: false,
                    array_element_fields: None,
                    encryption_metadata: None,
                },
            ],
        }
    }

    #[test]
    fn test_mapper_creation() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");
        assert_eq!(mapper.flat_to_field.len(), 3);
        assert_eq!(mapper.alias_to_field.len(), 2);
        assert_eq!(mapper.path_to_field.len(), 3);
    }

    #[test]
    fn test_lookup_by_alias() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let field = mapper.get("database_driver").expect("Field not found");
        assert_eq!(field.flat_name, "tracker-core-database-driver");
        assert_eq!(field.alias, Some("database_driver".to_string()));
    }

    #[test]
    fn test_lookup_by_flat_name() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let field = mapper
            .get("ssh_credentials-username")
            .expect("Field not found");
        assert_eq!(
            field.path,
            vec!["ssh_credentials".to_string(), "username".to_string()]
        );
    }

    #[test]
    fn test_lookup_by_path() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");

        let path = vec![
            "tracker".to_string(),
            "core".to_string(),
            "database".to_string(),
            "driver".to_string(),
        ];
        let field = mapper.get_by_path(&path).expect("Field not found");
        assert_eq!(field.alias, Some("database_driver".to_string()));
    }

    #[test]
    fn test_preferred_name_with_alias() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");
        let field = mapper.get("database_driver").expect("Field not found");

        assert_eq!(mapper.preferred_name(field), "database_driver");
    }

    #[test]
    fn test_preferred_name_without_alias() {
        let schema = create_test_schema();
        let mapper = FieldMapper::from_schema(&schema).expect("FieldMapper creation failed");
        let field = mapper.get("simple").expect("Field not found");

        assert_eq!(mapper.preferred_name(field), "simple");
    }

    #[test]
    fn test_no_collision_with_hyphen_separator() {
        // This test verifies the core benefit: hyphen separator prevents collisions
        let path1 = ["ssh_credentials".to_string(), "username".to_string()];
        let path2 = ["ssh".to_string(), "credentials_username".to_string()];

        let flat1 = path1.join("-"); // ssh_credentials-username
        let flat2 = path2.join("-"); // ssh-credentials_username

        assert_ne!(flat1, flat2, "Hyphen separator should prevent collisions");
    }
}
