"""Typed event catalog helper.

Provides compile-time *and* runtime validation of event names and
property shapes against a schema defined as a dictionary of
:class:`~typing.TypedDict` types.
"""

from __future__ import annotations

import sys
from typing import Any, Dict, Optional, Type, get_type_hints

from .errors import ValidationError

if sys.version_info >= (3, 11):
    from typing import get_type_hints as _get_type_hints
else:
    _get_type_hints = get_type_hints


def _is_optional(tp: Any) -> bool:
    """Return ``True`` if *tp* is ``Optional[X]`` (i.e. ``Union[X, None]``)."""
    origin = getattr(tp, "__origin__", None)
    if origin is not None:
        import typing
        # Union types
        if origin is getattr(typing, "Union", None):
            args = getattr(tp, "__args__", ())
            return type(None) in args
    return False


def _get_required_keys(schema: Type[Any]) -> set[str]:
    """Return the set of required keys for a TypedDict class."""
    # Python 3.9+ TypedDicts have __required_keys__ / __optional_keys__
    required = getattr(schema, "__required_keys__", None)
    if required is not None:
        return set(required)
    # Fallback: inspect annotations and treat non-Optional as required
    try:
        hints = _get_type_hints(schema)
    except Exception:
        return set()
    total = getattr(schema, "__total__", True)
    if total:
        return {k for k, v in hints.items() if not _is_optional(v)}
    return set()


# ---------------------------------------------------------------------------
# EventCatalog
# ---------------------------------------------------------------------------


class EventCatalog:
    """Runtime-validated event catalog.

    Enforces that only known event names are tracked and that their
    properties satisfy the schema's required keys.
    """

    def __init__(self, schemas: Dict[str, Type[Any]]) -> None:
        self._schemas = schemas

    def track(
        self,
        event: str,
        properties: Dict[str, Any],
        *,
        user_id: str | None = None,
        anonymous_id: str | None = None,
        group_id: str | None = None,
        await_response: bool = False,
    ) -> None:
        """Validate and track an event via the module-level singleton.

        Raises:
            ValidationError: If *event* is unknown or required
                properties are missing.
        """
        if event not in self._schemas:
            raise ValidationError(
                f"Unknown event '{event}'. "
                f"Valid events: {list(self._schemas.keys())}"
            )

        schema = self._schemas[event]
        required = _get_required_keys(schema)
        missing = required - set(properties.keys())
        if missing:
            raise ValidationError(
                f"Event '{event}' missing required properties: {missing}"
            )

        # Import here to avoid circular imports at module level
        from . import analytics

        analytics.track(
            event,
            properties=properties,
            user_id=user_id,
            anonymous_id=anonymous_id,
            group_id=group_id,
            await_response=await_response,
        )


def create_catalog(event_schemas: Dict[str, Type[Any]]) -> EventCatalog:
    """Create a typed event catalog.

    Usage::

        from typing import TypedDict
        from kanban_analytics import create_catalog

        class PaymentCompleted(TypedDict):
            amount: float
            currency: str
            plan_id: str

        catalog = create_catalog({
            'payment_completed': PaymentCompleted,
        })

        catalog.track(
            'payment_completed',
            user_id='u1',
            properties={'amount': 99.0, 'currency': 'USD', 'plan_id': 'pro'},
        )
    """
    return EventCatalog(event_schemas)
