# Copyright 2025 Softwell S.r.l.
# Licensed under the Apache License, Version 2.0

"""Request-scoped state container with attribute access.

Classes:
    State -- dict-backed container with ``state.attr`` syntax.

Middleware uses State to attach per-request data (user, request_id, etc.).
Missing attributes raise ``AttributeError``. Uses ``__slots__``.
"""

from typing import Any

__all__ = ["State"]


class State:
    """
    Request-scoped state container with attribute access.

    Provides ergonomic attribute-style access to request state, commonly used
    by middleware to attach data (e.g., authenticated user, request ID).

    Uses Python's magic attribute methods (``__getattr__``/``__setattr__``) to
    store data in an internal dictionary while providing ``state.attr`` syntax.
    This is a standard pattern used by Starlette, Flask, and other frameworks.

    Example:
        >>> state = State()
        >>> state.user_id = 123
        >>> state.is_authenticated = True
        >>> state.user_id
        123
        >>> "user_id" in state
        True
        >>> del state.user_id
        >>> "user_id" in state
        False

        # Missing attributes raise AttributeError
        >>> state.missing  # doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
            ...
        AttributeError: State has no attribute 'missing'
    """

    __slots__ = ("_state",)

    def __init__(self) -> None:
        """
        Initialize an empty State container.

        Uses ``object.__setattr__`` to initialize the internal dict without
        triggering our custom ``__setattr__`` override.
        """
        object.__setattr__(self, "_state", {})

    def __setattr__(self, name: str, value: Any) -> None:
        """
        Set a state attribute.

        Args:
            name: Attribute name.
            value: Value to store.
        """
        self._state[name] = value

    def __getattr__(self, name: str) -> Any:
        """
        Get a state attribute.

        Args:
            name: Attribute name.

        Returns:
            The stored value.

        Raises:
            AttributeError: If attribute does not exist.
        """
        try:
            return self._state[name]
        except KeyError:
            raise AttributeError(f"State has no attribute '{name}'") from None

    def __delattr__(self, name: str) -> None:
        """
        Delete a state attribute.

        Args:
            name: Attribute name to delete.

        Raises:
            AttributeError: If attribute does not exist.
        """
        try:
            del self._state[name]
        except KeyError:
            raise AttributeError(f"State has no attribute '{name}'") from None

    def __contains__(self, name: object) -> bool:
        """Check if attribute exists in state."""
        if not isinstance(name, str):
            return False
        return name in self._state

    def __repr__(self) -> str:
        """Return string representation for debugging."""
        return f"State({self._state!r})"
