//! Concept resolver for XBRL concept references in formulas.
//!
//! This module provides the trait and implementation for resolving XBRL concept
//! references to numeric values, enabling formulas to directly access XBRL data.

use crate::mappings::formula::FormulaError;
use crate::terminal::sec_edgar::UnitFamily;
use crate::terminal::types::AvailableXbrlConcept;
use std::collections::HashMap;

/// Trait for resolving XBRL concept references to numeric values.
pub trait ConceptResolver {
    /// Resolve a concept reference to its numeric value.
    fn resolve_concept(
        &self,
        taxonomy: &str,
        concept: &str,
        unit_family: UnitFamily,
    ) -> Result<f64, FormulaError>;
}

/// Concept resolver that looks up concepts from available XBRL data.
pub struct XbrlConceptResolver {
    concept_values: HashMap<String, HashMap<String, HashMap<String, f64>>>,
}

impl XbrlConceptResolver {
    pub fn new(available_concepts: Vec<AvailableXbrlConcept>) -> Self {
        let mut concept_values: HashMap<String, HashMap<String, HashMap<String, f64>>> =
            HashMap::new();

        for concept in available_concepts {
            if let Some(value) = concept.value {
                concept_values
                    .entry(concept.taxonomy)
                    .or_default()
                    .entry(concept.concept)
                    .or_default()
                    .insert(concept.unit_family, value);
            }
        }

        Self { concept_values }
    }

    #[allow(dead_code)]
    pub fn empty() -> Self {
        Self {
            concept_values: HashMap::new(),
        }
    }

    #[allow(dead_code)]
    pub fn add_concept(
        &mut self,
        taxonomy: String,
        concept: String,
        unit_family: UnitFamily,
        value: f64,
    ) {
        self.concept_values
            .entry(taxonomy)
            .or_default()
            .entry(concept)
            .or_default()
            .insert(unit_family.as_str().to_string(), value);
    }
}

impl ConceptResolver for XbrlConceptResolver {
    fn resolve_concept(
        &self,
        taxonomy: &str,
        concept: &str,
        unit_family: UnitFamily,
    ) -> Result<f64, FormulaError> {
        self.concept_values
            .get(taxonomy)
            .and_then(|by_concept| by_concept.get(concept))
            .and_then(|by_unit| by_unit.get(unit_family.as_str()))
            .copied()
            .ok_or_else(|| FormulaError::ConceptNotFound {
                taxonomy: taxonomy.to_string(),
                concept: concept.to_string(),
            })
    }
}

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

    #[test]
    fn test_resolve_existing_concept() {
        let mut resolver = XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "Revenue".to_string(),
            UnitFamily::Currency,
            1000.0,
        );

        let result = resolver
            .resolve_concept("us-gaap", "Revenue", UnitFamily::Currency)
            .unwrap();
        assert_eq!(result, 1000.0);
    }

    #[test]
    fn test_resolve_nonexistent_concept() {
        let resolver = XbrlConceptResolver::empty();

        let result = resolver.resolve_concept("us-gaap", "Revenue", UnitFamily::Currency);
        assert!(matches!(result, Err(FormulaError::ConceptNotFound { .. })));
    }

    #[test]
    fn test_resolve_concept_wrong_unit_family() {
        let mut resolver = XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "Revenue".to_string(),
            UnitFamily::Currency,
            1000.0,
        );

        // Try to resolve with wrong unit family
        let result = resolver.resolve_concept("us-gaap", "Revenue", UnitFamily::Shares);
        assert!(matches!(result, Err(FormulaError::ConceptNotFound { .. })));
    }

    #[test]
    fn test_resolve_multiple_taxonomies() {
        let mut resolver = XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "Revenue".to_string(),
            UnitFamily::Currency,
            1000.0,
        );
        resolver.add_concept(
            "ifrs-full".to_string(),
            "Revenue".to_string(),
            UnitFamily::Currency,
            2000.0,
        );

        let us_gaap_result = resolver
            .resolve_concept("us-gaap", "Revenue", UnitFamily::Currency)
            .unwrap();
        assert_eq!(us_gaap_result, 1000.0);

        let ifrs_result = resolver
            .resolve_concept("ifrs-full", "Revenue", UnitFamily::Currency)
            .unwrap();
        assert_eq!(ifrs_result, 2000.0);
    }
}
