# ruff: noqa: UP006 UP007 UP045
# @omlish-lite
import dataclasses as dc
import typing as ta

from ....lite.abstract import Abstract
from ....lite.namespaces import NamespaceClass
from ..core import IoPipelineHandler
from ..core import IoPipelineHandlerContext
from ..core import IoPipelineHandlerFn


F = ta.TypeVar('F')
T = ta.TypeVar('T')


##


class IoPipelineHandlerFns(NamespaceClass):
    @dc.dataclass(frozen=True)
    class NoContext(ta.Generic[F, T]):
        fn: ta.Callable[[F], T]

        def __repr__(self) -> str:
            return f'{type(self).__name__}({self.fn!r})'

        def __call__(self, ctx: IoPipelineHandlerContext, obj: F) -> T:
            return self.fn(obj)

    @classmethod
    def no_context(cls, fn: ta.Callable[[F], T]) -> IoPipelineHandlerFn[F, T]:
        return cls.NoContext(fn)

    #

    @dc.dataclass(frozen=True)
    class And:
        fns: ta.Sequence[IoPipelineHandlerFn[ta.Any, bool]]

        def __repr__(self) -> str:
            return f'{type(self).__name__}([{", ".join(map(repr, self.fns))}])'

        def __call__(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> bool:
            return all(fn(ctx, msg) for fn in self.fns)

    @classmethod
    def and_(cls, *fns: IoPipelineHandlerFn[ta.Any, bool]) -> IoPipelineHandlerFn[ta.Any, bool]:
        if len(fns) == 1:
            return fns[0]
        return cls.And(fns)

    #

    @dc.dataclass(frozen=True)
    class Or:
        fns: ta.Sequence[IoPipelineHandlerFn[ta.Any, bool]]

        def __repr__(self) -> str:
            return f'{type(self).__name__}([{", ".join(map(repr, self.fns))}])'

        def __call__(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> bool:
            return any(fn(ctx, msg) for fn in self.fns)

    @classmethod
    def or_(cls, *fns: IoPipelineHandlerFn[ta.Any, bool]) -> IoPipelineHandlerFn[ta.Any, bool]:
        if len(fns) == 1:
            return fns[0]
        return cls.Or(fns)

    #

    @dc.dataclass(frozen=True)
    class Not:
        fn: IoPipelineHandlerFn[ta.Any, bool]

        def __repr__(self) -> str:
            return f'{type(self).__name__}({self.fn!r})'

        def __call__(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> bool:
            return not self.fn(ctx, msg)

    @classmethod
    def not_(cls, fn: IoPipelineHandlerFn[ta.Any, bool]) -> IoPipelineHandlerFn[ta.Any, bool]:
        if isinstance(fn, cls.Not):
            return fn.fn
        return cls.Not(fn)

    #

    @dc.dataclass(frozen=True)
    class IsInstance:
        ty: ta.Union[type, ta.Tuple[type, ...]]

        def __repr__(self) -> str:
            return f'{type(self).__name__}({self.ty!r})'

        def __call__(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> bool:
            return isinstance(msg, self.ty)

    @classmethod
    def isinstance(cls, ty: ta.Union[type, ta.Tuple[type, ...]]) -> IoPipelineHandlerFn[ta.Any, bool]:
        return cls.IsInstance(ty)

    @classmethod
    def not_isinstance(cls, ty: ta.Union[type, ta.Tuple[type, ...]]) -> IoPipelineHandlerFn[ta.Any, bool]:
        return cls.Not(cls.IsInstance(ty))


##


class FnIoPipelineHandler(IoPipelineHandler, Abstract):
    @classmethod
    def of(
            cls,
            *,
            inbound: ta.Optional[IoPipelineHandlerFn[ta.Any, None]] = None,
            outbound: ta.Optional[IoPipelineHandlerFn[ta.Any, None]] = None,
    ) -> IoPipelineHandler:
        if inbound is not None and outbound is not None:
            return DuplexFnIoPipelineHandler(inbound=inbound, outbound=outbound)
        elif inbound is not None:
            return InboundFnIoPipelineHandler(inbound)
        elif outbound is not None:
            return OutboundFnIoPipelineHandler(outbound)
        else:
            raise ValueError('At least one of inbound or outbound must be specified')


class InboundFnIoPipelineHandler(FnIoPipelineHandler):
    def __init__(self, inbound: IoPipelineHandlerFn[ta.Any, None]) -> None:
        super().__init__()

        self._inbound = inbound

    def __repr__(self) -> str:
        return f'{type(self).__name__}@{id(self):x}({self._inbound!r})'

    def inbound(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> None:
        self._inbound(ctx, msg)


class OutboundFnIoPipelineHandler(FnIoPipelineHandler):
    def __init__(self, outbound: IoPipelineHandlerFn[ta.Any, None]) -> None:
        super().__init__()

        self._outbound = outbound

    def __repr__(self) -> str:
        return f'{type(self).__name__}@{id(self):x}({self._outbound!r})'

    def outbound(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> None:
        self._outbound(ctx, msg)


class DuplexFnIoPipelineHandler(FnIoPipelineHandler):
    def __init__(
            self,
            *,
            inbound: IoPipelineHandlerFn[ta.Any, None],
            outbound: IoPipelineHandlerFn[ta.Any, None],
    ) -> None:
        super().__init__()

        self._inbound = inbound
        self._outbound = outbound

    def __repr__(self) -> str:
        return f'{type(self).__name__}@{id(self):x}(inbound={self._inbound!r}, outbound={self._outbound!r})'

    def inbound(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> None:
        self._inbound(ctx, msg)

    def outbound(self, ctx: IoPipelineHandlerContext, msg: ta.Any) -> None:
        self._outbound(ctx, msg)
