"""Middleware pipeline for sync and async event processing."""

from __future__ import annotations

import asyncio
from typing import Any, Callable, List

from .types import ServerEvent


# ---------------------------------------------------------------------------
# Sync middleware runner
# ---------------------------------------------------------------------------


def run_middleware(
    event: ServerEvent,
    middleware: List[Callable[..., Any]],
) -> ServerEvent | None:
    """Run *event* through a synchronous middleware chain.

    Each middleware receives ``(event_dict, next_fn)`` and must call
    ``next_fn(event_dict)`` to pass the event along.  If a middleware
    does **not** call ``next_fn``, the event is silently dropped
    (returns ``None``).
    """
    if not middleware:
        return event

    result: ServerEvent | None = None
    event_dict: dict[str, Any] = dict(event)  # type: ignore[arg-type]

    def _build_chain(index: int) -> Callable[[dict[str, Any]], None]:
        def _next(evt: dict[str, Any]) -> None:
            nonlocal result
            if index >= len(middleware):
                result = evt  # type: ignore[assignment]
                return
            mw = middleware[index]
            mw(evt, _build_chain(index + 1))
        return _next

    _build_chain(0)(event_dict)
    return result


# ---------------------------------------------------------------------------
# Async middleware runner
# ---------------------------------------------------------------------------


async def run_middleware_async(
    event: ServerEvent,
    middleware: List[Callable[..., Any]],
) -> ServerEvent | None:
    """Run *event* through an asynchronous middleware chain.

    Each middleware receives ``(event_dict, next_fn)`` and must call
    ``await next_fn(event_dict)`` (or ``next_fn(event_dict)`` for sync
    middleware) to continue the chain.
    """
    if not middleware:
        return event

    result: ServerEvent | None = None
    event_dict: dict[str, Any] = dict(event)  # type: ignore[arg-type]

    def _build_chain(index: int) -> Callable[..., Any]:
        async def _next(evt: dict[str, Any]) -> None:
            nonlocal result
            if index >= len(middleware):
                result = evt  # type: ignore[assignment]
                return
            mw = middleware[index]
            ret = mw(evt, _build_chain(index + 1))
            if asyncio.iscoroutine(ret) or asyncio.isfuture(ret):
                await ret
        return _next

    first_next = _build_chain(0)
    await first_next(event_dict)
    return result
