//! Abstract Syntax Tree (AST) for formula expressions.

use crate::mappings::types::TargetField;
use crate::terminal::sec_edgar::UnitFamily;
use serde::{Deserialize, Serialize};

/// XBRL concept reference: #taxonomy.concept[unit]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConceptReference {
    /// XBRL taxonomy namespace (e.g., "us-gaap", "ifrs-full")
    pub taxonomy: String,
    /// Concept name within taxonomy
    pub concept: String,
    /// Unit family for the concept value
    pub unit_family: UnitFamily,
}

/// AST node representing any expression in the formula language.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expr {
    /// Field reference: $FieldName
    FieldRef(TargetField),

    /// XBRL concept reference: #taxonomy.concept[unit]
    ConceptRef(ConceptReference),

    /// Numeric literal
    Literal(f64),

    /// Binary operation: left op right
    BinaryOp {
        op: BinaryOperator,
        left: Box<Expr>,
        right: Box<Expr>,
    },

    /// Unary operation: op operand
    UnaryOp {
        op: UnaryOperator,
        operand: Box<Expr>,
    },

    /// Function call: NAME(args...)
    FunctionCall { name: String, args: Vec<Expr> },
}

/// Binary operators for arithmetic operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOperator {
    /// Addition: +
    Add,

    /// Subtraction: -
    Subtract,

    /// Multiplication: *
    Multiply,

    /// Division: /
    Divide,

    /// Modulo: %
    Modulo,
}

impl BinaryOperator {
    /// Get the precedence of this operator (higher = tighter binding).
    pub const fn precedence(&self) -> u8 {
        match self {
            Self::Add | Self::Subtract => 10,
            Self::Multiply | Self::Divide | Self::Modulo => 20,
        }
    }

    /// Get the symbol for this operator.
    #[allow(dead_code)]
    pub const fn symbol(&self) -> char {
        match self {
            Self::Add => '+',
            Self::Subtract => '-',
            Self::Multiply => '*',
            Self::Divide => '/',
            Self::Modulo => '%',
        }
    }
}

/// Unary operators for sign operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOperator {
    /// Unary plus: +
    Plus,

    /// Unary minus: -
    Minus,
}

impl UnaryOperator {
    /// Get the symbol for this operator.
    #[allow(dead_code)]
    pub const fn symbol(&self) -> char {
        match self {
            Self::Plus => '+',
            Self::Minus => '-',
        }
    }
}

impl Expr {
    /// Check if this expression contains a field reference.
    #[must_use]
    #[allow(dead_code)]
    pub fn has_field_ref(&self, field: TargetField) -> bool {
        match self {
            Expr::FieldRef(f) => *f == field,
            Expr::ConceptRef(_) => false,
            Expr::Literal(_) => false,
            Expr::BinaryOp { left, right, .. } => {
                left.has_field_ref(field) || right.has_field_ref(field)
            }
            Expr::UnaryOp { operand, .. } => operand.has_field_ref(field),
            Expr::FunctionCall { args, .. } => args.iter().any(|arg| arg.has_field_ref(field)),
        }
    }

    /// Collect all field references in this expression.
    #[must_use]
    pub fn collect_field_refs(&self) -> Vec<TargetField> {
        match self {
            Expr::FieldRef(f) => vec![*f],
            Expr::ConceptRef(_) => vec![],
            Expr::Literal(_) => vec![],
            Expr::BinaryOp { left, right, .. } => {
                let mut refs = left.collect_field_refs();
                refs.extend(right.collect_field_refs());
                refs
            }
            Expr::UnaryOp { operand, .. } => operand.collect_field_refs(),
            Expr::FunctionCall { args, .. } => args
                .iter()
                .flat_map(|arg| arg.collect_field_refs())
                .collect(),
        }
    }

    /// Check if this expression contains a specific concept reference.
    #[must_use]
    #[allow(dead_code)]
    pub fn has_concept_ref(&self, taxonomy: &str, concept: &str) -> bool {
        match self {
            Expr::ConceptRef(ref concept_ref) => {
                concept_ref.taxonomy == taxonomy && concept_ref.concept == concept
            }
            Expr::FieldRef(_) | Expr::Literal(_) => false,
            Expr::BinaryOp { left, right, .. } => {
                left.has_concept_ref(taxonomy, concept) || right.has_concept_ref(taxonomy, concept)
            }
            Expr::UnaryOp { operand, .. } => operand.has_concept_ref(taxonomy, concept),
            Expr::FunctionCall { args, .. } => args
                .iter()
                .any(|arg| arg.has_concept_ref(taxonomy, concept)),
        }
    }

    /// Collect all concept references in this expression.
    #[must_use]
    #[allow(dead_code)]
    pub fn collect_concept_refs(&self) -> Vec<ConceptReference> {
        match self {
            Expr::ConceptRef(ref concept_ref) => vec![concept_ref.clone()],
            Expr::FieldRef(_) | Expr::Literal(_) => vec![],
            Expr::BinaryOp { left, right, .. } => {
                let mut refs = left.collect_concept_refs();
                refs.extend(right.collect_concept_refs());
                refs
            }
            Expr::UnaryOp { operand, .. } => operand.collect_concept_refs(),
            Expr::FunctionCall { args, .. } => args
                .iter()
                .flat_map(|arg| arg.collect_concept_refs())
                .collect(),
        }
    }

    /// Collect all function calls in this expression.
    #[must_use]
    pub fn collect_functions(&self) -> Vec<String> {
        match self {
            Expr::FieldRef(_) | Expr::ConceptRef(_) | Expr::Literal(_) => vec![],
            Expr::BinaryOp { left, right, .. } => {
                let mut funcs = left.collect_functions();
                funcs.extend(right.collect_functions());
                funcs
            }
            Expr::UnaryOp { operand, .. } => operand.collect_functions(),
            Expr::FunctionCall { name, args } => {
                let mut funcs = vec![name.clone()];
                funcs.extend(args.iter().flat_map(|arg| arg.collect_functions()));
                funcs
            }
        }
    }
}

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

    #[test]
    fn test_binary_operator_precedence() {
        assert_eq!(BinaryOperator::Add.precedence(), 10);
        assert_eq!(BinaryOperator::Subtract.precedence(), 10);
        assert_eq!(BinaryOperator::Multiply.precedence(), 20);
        assert_eq!(BinaryOperator::Divide.precedence(), 20);
        assert!(BinaryOperator::Multiply.precedence() > BinaryOperator::Add.precedence());
    }

    #[test]
    fn test_collect_field_refs() {
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Add,
            left: Box::new(Expr::FieldRef(TargetField::TotalRevenues)),
            right: Box::new(Expr::FieldRef(TargetField::NetIncome)),
        };

        let refs = expr.collect_field_refs();
        assert_eq!(refs.len(), 2);
        assert!(refs.contains(&TargetField::TotalRevenues));
        assert!(refs.contains(&TargetField::NetIncome));
    }

    #[test]
    fn test_collect_functions() {
        let expr = Expr::FunctionCall {
            name: "SUM".to_string(),
            args: vec![
                Expr::FieldRef(TargetField::TotalRevenues),
                Expr::FieldRef(TargetField::NetIncome),
            ],
        };

        let funcs = expr.collect_functions();
        assert_eq!(funcs, vec!["SUM"]);
    }

    #[test]
    fn test_has_field_ref() {
        let expr = Expr::FieldRef(TargetField::TotalRevenues);
        assert!(expr.has_field_ref(TargetField::TotalRevenues));
        assert!(!expr.has_field_ref(TargetField::NetIncome));
    }

    #[test]
    fn test_collect_concept_refs() {
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Add,
            left: Box::new(Expr::ConceptRef(ConceptReference {
                taxonomy: "us-gaap".to_string(),
                concept: "RevenueFromContractWithCustomerExcludingAssessedTax".to_string(),
                unit_family: UnitFamily::Currency,
            })),
            right: Box::new(Expr::ConceptRef(ConceptReference {
                taxonomy: "us-gaap".to_string(),
                concept: "CostOfGoodsSold".to_string(),
                unit_family: UnitFamily::Currency,
            })),
        };

        let refs = expr.collect_concept_refs();
        assert_eq!(refs.len(), 2);
        assert_eq!(refs[0].taxonomy, "us-gaap");
        assert_eq!(
            refs[0].concept,
            "RevenueFromContractWithCustomerExcludingAssessedTax"
        );
        assert_eq!(refs[1].concept, "CostOfGoodsSold");
    }

    #[test]
    fn test_has_concept_ref() {
        let expr = Expr::ConceptRef(ConceptReference {
            taxonomy: "us-gaap".to_string(),
            concept: "RevenueFromContractWithCustomerExcludingAssessedTax".to_string(),
            unit_family: UnitFamily::Currency,
        });

        assert!(expr.has_concept_ref(
            "us-gaap",
            "RevenueFromContractWithCustomerExcludingAssessedTax"
        ));
        assert!(!expr.has_concept_ref("us-gaap", "NetIncomeLoss"));
        assert!(!expr.has_concept_ref(
            "ifrs-full",
            "RevenueFromContractWithCustomerExcludingAssessedTax"
        ));
    }

    #[test]
    fn test_collect_concept_refs_mixed_expression() {
        // Test expression: $gross_profit + #us-gaap.OtherExpenses[currency]
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Add,
            left: Box::new(Expr::FieldRef(TargetField::GrossProfit)),
            right: Box::new(Expr::ConceptRef(ConceptReference {
                taxonomy: "us-gaap".to_string(),
                concept: "OtherExpenses".to_string(),
                unit_family: UnitFamily::Currency,
            })),
        };

        let field_refs = expr.collect_field_refs();
        let concept_refs = expr.collect_concept_refs();

        assert_eq!(field_refs.len(), 1);
        assert!(field_refs.contains(&TargetField::GrossProfit));

        assert_eq!(concept_refs.len(), 1);
        assert_eq!(concept_refs[0].concept, "OtherExpenses");
    }
}
