from __future__ import annotations

from typing import Any, Callable, TypeVar

from .execution import BaseExecRequest, Executor, Middleware, chain
from .middleware import ErrorMappingMiddleware, Policy

TReq = TypeVar("TReq", bound=BaseExecRequest)
TOut = TypeVar("TOut")


def build_executor(
    *,
    base: Executor[TReq, TOut],
    middlewares: list[Middleware[TReq, TOut]] | None,
    error_handlers: Any,
    ctx_factory: Callable[[TReq], Any],
) -> Executor[TReq, TOut]:
    """Build a layered execution pipeline.

    Ordering is intentional:

    - instrumentation: logging/metrics/etc. (measures what callers see)
    - core: error mapping (converts raw exceptions into domain errors)
    - policy: retry/backoff/etc. (sees raw backend errors when possible)
    """
    ms: list[Middleware[TReq, TOut]] = list(middlewares or [])

    # Distinguish behavioral policies from pure instrumentation by type.
    # Unknown middleware defaults to instrumentation to preserve historical behavior.
    policy = [m for m in ms if isinstance(m, Policy)]
    instrumentation = [m for m in ms if not isinstance(m, Policy)]

    # Compose the policy middleware chain. This is the middleware that applies the policy.
    policy_exec = chain(base, policy)
    # Map errors to domain exceptions using the error handler chain.
    mapped_exec = ErrorMappingMiddleware[TReq, TOut](error_handlers=error_handlers, ctx_factory=ctx_factory).wrap(
        policy_exec
    )
    # Compose the middleware chain. This is the final executor that executes the statement.
    # Error mapping is applied after the policy middleware to ensure we see raw backend errors when possible.
    return chain(mapped_exec, instrumentation)

