"""
Shared utilities for formatting and introspecting Python objects.

Provides common functions for checking object types, extracting attributes,
and formatting nested structures - eliminating code duplication across
query.py, card.py, and filter.py.

Usage:
    from utils.object_formatting import (
        is_nested_object,
        is_scalar_type,
        format_value,
        get_item_label,
    )
"""

from typing import Any, List, Tuple

# Type checking predicates
SCALAR_TYPES = (str, int, float, bool, list, dict, type(None))


def is_dataclass(obj: Any) -> bool:
    """Check if object is a dataclass."""
    return hasattr(obj, "__dataclass_fields__")


def is_nested_object(obj: Any) -> bool:
    """
    Check if object is a nested/complex object (not a scalar type).

    Returns True for dataclasses, dicts, and objects with __dict__ that aren't scalars.
    """
    if isinstance(obj, dict):
        return True
    if is_dataclass(obj):
        return True
    return hasattr(obj, "__dict__") and not isinstance(obj, SCALAR_TYPES)


def is_scalar(obj: Any) -> bool:
    """Check if object is a scalar type (str, int, float, bool, list, dict, None)."""
    return isinstance(obj, SCALAR_TYPES)


def get_item_label(item: Any, fallback: str = "item") -> str:
    """
    Extract a display label for an item using priority order.

    Priority:
    1. item.name (most common in Tarot data)
    2. item.transliteration (used in letters/numbers)
    3. str(item) (fallback)

    Args:
        item: The object to get a label for
        fallback: Value to use if no attributes found (rarely used)

    Returns:
        A string suitable for display as an item label
    """
    if hasattr(item, "name"):
        return str(getattr(item, "name", fallback))
    elif hasattr(item, "transliteration"):
        return str(getattr(item, "transliteration", fallback))
    return str(item) if item is not None else fallback


def get_dataclass_fields(obj: Any) -> List[str]:
    """Get list of field names from a dataclass."""
    if hasattr(obj, "__dataclass_fields__"):
        return list(obj.__dataclass_fields__.keys())
    return []


def get_object_attributes(obj: Any) -> List[Tuple[str, Any]]:
    """
    Extract all public attributes from an object.

    Returns list of (name, value) tuples, skipping private attributes (starting with '_').
    Works with dataclasses, dicts, and regular objects with __dict__.
    """
    attributes = []

    if isinstance(obj, dict):
        return list(obj.items())

    if is_dataclass(obj):
        for field_name in obj.__dataclass_fields__:
            value = getattr(obj, field_name, None)
            attributes.append((field_name, value))
    elif hasattr(obj, "__dict__"):
        for field_name, value in obj.__dict__.items():
            if not field_name.startswith("_"):
                attributes.append((field_name, value))

    return attributes


def format_value(value: Any, indent: int = 2) -> str:
    """
    Format a value for display, recursively handling nested objects.

    Handles:
    - Nested dataclasses and objects with custom __str__ methods
    - Lists and dicts
    - Scalar values
    - Proper indentation for nested structures

    Args:
        value: The value to format
        indent: Number of spaces for indentation (increases for nested objects)

    Returns:
        Formatted string representation of the value
    """
    indent_str = " " * indent

    # Check if object has a custom __str__ method (not the default object repr)
    if is_nested_object(value):
        # Classes that have custom __str__ implementations should use them
        obj_class = type(value).__name__
        has_custom_str = hasattr(value, "__str__") and type(value).__str__ is not object.__str__

        if has_custom_str and obj_class in [
            "Path",
            "Planet",
            "Perfume",
            "God",
            "Colorscale",
            "Sephera",
            "ElementType",
        ]:
            # Use the custom __str__ method and indent each line
            custom_output = str(value)
            lines = []
            for line in custom_output.split("\n"):
                if line.strip():  # Skip empty lines
                    lines.append(f"{indent_str}{line}")
            return "\n".join(lines)

        # Default behavior: iterate through attributes
        lines = []
        for attr_name, attr_value in get_object_attributes(value):
            # Recursively format deeply nested objects
            if is_nested_object(attr_value):
                lines.append(f"{indent_str}--- {attr_name.replace('_', ' ').title()} ---")
                nested = format_value(attr_value, indent + 2)
                lines.append(nested)
            else:
                lines.append(f"{indent_str}{attr_name}: {attr_value}")
        return "\n".join(lines)

    # Scalar values
    return str(value)


def format_object_attributes(obj: Any, indent: int = 2) -> List[str]:
    """
    Format all attributes of an object as a list of formatted lines.

    Handles nested objects with proper indentation and section headers.
    Used by display methods to format individual items consistently.

    Args:
        obj: The object to format
        indent: Base indentation level in spaces

    Returns:
        List of formatted lines ready to join with newlines
    """
    lines = []
    indent_str = " " * indent

    for attr_name, attr_value in get_object_attributes(obj):
        if is_nested_object(attr_value):
            # Nested object - add section header and format recursively
            lines.append(f"{indent_str}{attr_name}:")
            lines.append(f"{indent_str}  --- {attr_name.replace('_', ' ').title()} ---")
            nested = format_value(attr_value, indent=indent + 4)
            lines.append(nested)
        else:
            # Scalar value - just print
            lines.append(f"{indent_str}{attr_name}: {attr_value}")

    return lines
