"""Numbers loader - access to numerology and number correspondences."""

from typing import TYPE_CHECKING, Dict, Optional, Union, overload

from utils.filter import universal_filter

if TYPE_CHECKING:
    from utils.attributes import Color, Number


def calculate_digital_root(value: int) -> int:
    """
    Calculate the digital root of a number by repeatedly summing its digits.

    Digital root reduces any number to a single digit (1-9) by repeatedly
    summing its digits until a single digit remains.

    Args:
        value: The number to reduce to digital root

    Returns:
        The digital root (1-9)

    Examples:
        >>> calculate_digital_root(14)  # 1+4 = 5
        5
        >>> calculate_digital_root(99)  # 9+9 = 18, 1+8 = 9
        9
        >>> calculate_digital_root(5)
        5
    """
    if value < 1:
        raise ValueError(f"Value must be positive, got {value}")

    while value >= 10:
        value = sum(int(digit) for digit in str(value))

    return value


class Numbers:
    """
    Unified accessor for numerology, numbers, and color correspondences.

    All methods are class methods, so Numbers is accessed as a static namespace:

        num = Numbers.number(5)
        root = Numbers.digital_root(256)
        color = Numbers.color_by_number(root)
    """

    # These are populated on first access from CardDataLoader
    _numbers: Dict[int, "Number"] = {}  # type: ignore
    _colors: Dict[int, "Color"] = {}  # type: ignore
    _initialized: bool = False

    @classmethod
    def _ensure_initialized(cls) -> None:
        """Lazy-load data from CardDataLoader on first access."""
        if cls._initialized:
            return

        from tarot.card.data import CardDataLoader

        loader = CardDataLoader()
        cls._numbers = loader.number()
        cls._colors = loader.color()
        cls._initialized = True

    @classmethod
    @overload
    def number(cls, value: int) -> Optional["Number"]: ...

    @classmethod
    @overload
    def number(cls, value: None = ...) -> Dict[int, "Number"]: ...

    @classmethod
    def number(cls, value: Optional[int] = None) -> Union[Optional["Number"], Dict[int, "Number"]]:
        """Return an individual Number or the full numerology table."""
        cls._ensure_initialized()
        if value is None:
            return cls._numbers.copy()
        return cls._numbers.get(value)

    @classmethod
    @overload
    def color(cls, sephera_number: int) -> Optional["Color"]: ...

    @classmethod
    @overload
    def color(cls, sephera_number: None = ...) -> Dict[int, "Color"]: ...

    @classmethod
    def color(
        cls, sephera_number: Optional[int] = None
    ) -> Union[Optional["Color"], Dict[int, "Color"]]:
        """Return a single color correspondence or the entire map."""
        cls._ensure_initialized()
        if sephera_number is None:
            return cls._colors.copy()
        return cls._colors.get(sephera_number)

    @classmethod
    def color_by_number(cls, number: int) -> Optional["Color"]:
        """Get a Color by mapping a number through digital root."""
        root = calculate_digital_root(number)
        return cls.color(root)

    @classmethod
    def number_by_digital_root(cls, value: int) -> Optional["Number"]:
        """Get a Number object using digital root calculation."""
        root = calculate_digital_root(value)
        return cls.number(root)

    @classmethod
    def digital_root(cls, value: int) -> int:
        """Get the digital root of a value."""
        return calculate_digital_root(value)

    @classmethod
    def filter_numbers(cls, **kwargs) -> list:
        """
        Filter numbers by any Number attribute.

        Uses the universal filter from utils.filter for consistency
        across the entire project.

        Args:
            **kwargs: Any Number attribute with its value

        Usage:
            Numbers.filter_numbers(element="Fire")
            Numbers.filter_numbers(sephera_number=5)

        Returns:
            List of Number objects matching all filters
        """
        cls._ensure_initialized()
        return universal_filter(list(cls._numbers.values()), **kwargs)

    @classmethod
    def display_filter_numbers(cls, **kwargs) -> str:
        """
        Filter numbers and display results nicely formatted.

        Combines filtering and formatting in one call.

        Args:
            **kwargs: Any Number attribute with its value

        Returns:
            Formatted string with filtered numbers

        Example:
            print(Numbers.display_filter_numbers(element="Fire"))
        """
        from utils.filter import format_results

        results = cls.filter_numbers(**kwargs)
        return format_results(results)

    @classmethod
    def filter_colors(cls, **kwargs) -> list:
        """
        Filter colors by any Color attribute.

        Uses the universal filter from utils.filter for consistency
        across the entire project.

        Args:
            **kwargs: Any Color attribute with its value

        Usage:
            Numbers.filter_colors(element="Water")
            Numbers.filter_colors(sephera_number=3)

        Returns:
            List of Color objects matching all filters
        """
        cls._ensure_initialized()
        return universal_filter(list(cls._colors.values()), **kwargs)

    @classmethod
    def display_filter_colors(cls, **kwargs) -> str:
        """
        Filter colors and display results nicely formatted.

        Combines filtering and formatting in one call.

        Args:
            **kwargs: Any Color attribute with its value

        Returns:
            Formatted string with filtered colors

        Example:
            print(Numbers.display_filter_colors(element="Water"))
        """
        from utils.filter import format_results

        results = cls.filter_colors(**kwargs)
        return format_results(results)
