"""
FreeExile Character Stat Aggregator Domain Types & AST Models.
Defines Modifier Types, Stat Modifiers, Evaluation Contexts,
AST Hierarchy for Calculation Auditing, and Aggregated Character Stats.
Strict typing with @dataclass(slots=True) following Elite Standards 2026.
"""

from __future__ import annotations
from enum import Enum
from typing import Dict, List, Optional, FrozenSet, Any, Collection
from dataclasses import dataclass, field


class ModifierType(str, Enum):
    """Canonical Path of Exile modifier types for character stat calculations."""
    FLAT = "FLAT"
    INCREASED = "INCREASED"
    REDUCED = "REDUCED"
    MORE = "MORE"
    LESS = "LESS"


@dataclass(slots=True, frozen=True)
class StatModifier:
    """An individual modifier contributing to a character stat."""
    stat_key: str
    mod_type: ModifierType
    value: float
    tags: FrozenSet[str] = frozenset()
    condition: Optional[str] = None
    source: str = ""


class ASTNode:
    """Base class for all stat formula AST nodes."""
    def to_dict(self) -> Dict[str, Any]:
        raise NotImplementedError


@dataclass(slots=True, frozen=True)
class ConstantNode(ASTNode):
    """Constant base value node in calculation AST."""
    name: str
    value: float
    source: str = ""

    def to_dict(self) -> Dict[str, Any]:
        return {
            "ast_type": "Constant",
            "name": self.name,
            "value": round(self.value, 4),
            "source": self.source,
        }


@dataclass(slots=True, frozen=True)
class ModifierContributionNode(ASTNode):
    """Records an individual modifier's contribution to the calculation."""
    source: str
    modifier_type: str
    value: float
    tags: List[str] = field(default_factory=list)
    condition: Optional[str] = None
    condition_met: bool = True

    def to_dict(self) -> Dict[str, Any]:
        return {
            "ast_type": "ModifierContribution",
            "source": self.source,
            "modifier_type": self.modifier_type,
            "value": round(self.value, 4),
            "tags": list(self.tags),
            "condition": self.condition,
            "condition_met": self.condition_met,
        }


@dataclass(slots=True, frozen=True)
class SumNode(ASTNode):
    """Sum of flat modifiers added to the base value."""
    name: str
    total: float
    children: List[Dict[str, Any]] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "ast_type": "Sum",
            "name": self.name,
            "total": round(self.total, 4),
            "children": self.children,
        }


@dataclass(slots=True, frozen=True)
class ScaleFactorNode(ASTNode):
    """Additive percentage scale factor: 1.0 + sum(increased - reduced) / 100.0."""
    name: str
    percentage_sum: float
    multiplier: float
    children: List[Dict[str, Any]] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "ast_type": "ScaleFactor",
            "name": self.name,
            "percentage_sum": round(self.percentage_sum, 4),
            "multiplier": round(self.multiplier, 4),
            "children": self.children,
        }


@dataclass(slots=True, frozen=True)
class ProductNode(ASTNode):
    """Multiplicative compound product: prod(1.0 + more / 100.0) * prod(1.0 - less / 100.0)."""
    name: str
    multiplier: float
    children: List[Dict[str, Any]] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "ast_type": "Product",
            "name": self.name,
            "multiplier": round(self.multiplier, 4),
            "children": self.children,
        }


@dataclass(slots=True, frozen=True)
class EvaluationContext:
    """Context holding active skill tags, conditions, and vital ratios."""
    active_tags: FrozenSet[str] = frozenset()
    conditions: FrozenSet[str] = frozenset()
    current_hp_ratio: float = 1.0

    def matches_tags(self, required_tags: Collection[str]) -> bool:
        """Returns True if modifier tags are a subset of active tags (case-insensitive)."""
        if not required_tags:
            return True
        active_lower = {t.lower() for t in self.active_tags}
        return all(t.lower() in active_lower for t in required_tags)

    def is_condition_met(self, condition: Optional[str]) -> bool:
        """Evaluates whether a named condition is met under the current context."""
        if condition is None or not condition.strip():
            return True
        c_lower = condition.strip().lower()
        if c_lower in {c.lower() for c in self.conditions}:
            return True
        if c_lower in ("on_low_health", "on_low_life"):
            return self.current_hp_ratio <= 0.35
        if c_lower in ("on_full_health", "on_full_life"):
            return self.current_hp_ratio >= 1.0
        return False


@dataclass(slots=True, frozen=True)
class AggregatedCharacterStats:
    """Final calculated character combat vitals and secondary attributes."""
    attack_damage: float
    max_hp: float
    crit_chance: float
    crit_multiplier: float
    move_speed: float
    resistances: Dict[str, float] = field(default_factory=dict)
    all_stats: Dict[str, float] = field(default_factory=dict)

    @property
    def base_attack(self) -> float:
        """Alias for attack_damage for CombatActor compatibility."""
        return self.attack_damage

    @property
    def movement_speed(self) -> float:
        """Alias for move_speed for PlayerCharacter compatibility."""
        return self.move_speed

    def to_dict(self) -> Dict[str, Any]:
        """Serializes final stats to a standard dictionary."""
        return {
            "attack_damage": self.attack_damage,
            "max_hp": self.max_hp,
            "crit_chance": self.crit_chance,
            "crit_multiplier": self.crit_multiplier,
            "move_speed": self.move_speed,
            "resistances": dict(self.resistances),
            "all_stats": dict(self.all_stats),
        }
