"""Introspection mixin for REPL/notebook discoverability.

This module provides a reusable mixin that adds `__dir__` and `__getattr__`
for better interactive discovery in REPLs and notebooks.
"""

from __future__ import annotations

from typing import Any


class InteractiveIntrospectionMixin:
    """Mixin providing interactive discoverability for namespace classes.

    Implements `__dir__()` and `__getattr__()` to improve the developer
    experience in REPLs, Jupyter notebooks, and LLM interactions.

    Example:
        >>> class MyNamespace(InteractiveIntrospectionMixin):
        ...     def my_method(self):
        ...         pass
        >>>
        >>> ns = MyNamespace()
        >>> dir(ns)
        ['my_method']
        >>> ns.typo
        AttributeError: MyNamespace has no attribute 'typo'.
        Available methods: my_method.
        Use dir(ns) or help(ns) to explore the API.
    """

    def __dir__(self) -> list[str]:
        """Return public API, filtering out internal attributes.

        Uses dynamic introspection so the list never goes stale as the API evolves.
        Aligns with the interactive discovery convention in docs/conventions/interactive-discovery.md.

        Returns:
            Sorted list of public attribute names (no leading underscore)
        """
        attrs = set(super().__dir__())
        public = {name for name in attrs if not name.startswith("_")}
        return sorted(public)

    def __getattr__(self, name: str) -> Any:
        """Provide helpful error message for non-existent attributes.

        Dynamically discovers available methods to ensure error messages
        stay current as the API evolves.

        Args:
            name: Attribute name that was accessed

        Raises:
            AttributeError: With helpful message listing available methods
        """
        available = [m for m in dir(self) if not m.startswith("_")]
        class_name = self.__class__.__name__
        raise AttributeError(
            f"{class_name} has no attribute '{name}'. "
            f"Available methods: {', '.join(available)}. "
            f"Use dir({class_name.lower()}) or help({class_name.lower()}) to explore the API."
        )
