from __future__ import annotations

import asyncio
from dataclasses import dataclass, field
from typing import AsyncIterable, Generic, Protocol, TypeVar

StateT = TypeVar("StateT")
SignalT = TypeVar("SignalT")
EventT = TypeVar("EventT")


@dataclass(slots=True)
class Transition(Generic[StateT, EventT]):
    state: StateT
    events: list[EventT] = field(default_factory=list)
    terminal: bool = False


class Reducer(Protocol[StateT, SignalT, EventT]):
    def __call__(self, state: StateT, signal: SignalT) -> Transition[StateT, EventT]:
        ...


class AsyncPubSub(Generic[EventT]):
    def __init__(self) -> None:
        self._subscribers: set[asyncio.Queue[EventT]] = set()

    def subscribe(self, queue: asyncio.Queue[EventT]) -> bool:
        if queue is None:
            return False
        self._subscribers.add(queue)
        return True

    def unsubscribe(self, queue: asyncio.Queue[EventT]) -> bool:
        if queue is None:
            return False
        self._subscribers.discard(queue)
        return True

    async def publish(self, event: EventT) -> None:
        stale: list[asyncio.Queue[EventT]] = []
        for queue in list(self._subscribers):
            try:
                await queue.put(event)
            except Exception:
                stale.append(queue)
        for queue in stale:
            self._subscribers.discard(queue)


class OTurnNucleus(Generic[StateT, SignalT, EventT]):
    """
    OTURN: Orchestrated Task Unified Reactive Nucleus.

    Async state-machine kernel with reducer-driven transitions and pub/sub output.
    """

    def __init__(self, initial_state: StateT, reducer: Reducer[StateT, SignalT, EventT]) -> None:
        self._state = initial_state
        self._reducer = reducer
        self._pubsub: AsyncPubSub[EventT] = AsyncPubSub()
        self._terminal = False

    @property
    def state(self) -> StateT:
        return self._state

    @property
    def terminal(self) -> bool:
        return self._terminal

    def subscribe(self, queue: asyncio.Queue[EventT]) -> bool:
        return self._pubsub.subscribe(queue)

    def unsubscribe(self, queue: asyncio.Queue[EventT]) -> bool:
        return self._pubsub.unsubscribe(queue)

    async def step(self, signal: SignalT) -> Transition[StateT, EventT]:
        if self._terminal:
            return Transition(state=self._state, events=[], terminal=True)

        transition = self._reducer(self._state, signal)
        self._state = transition.state
        self._terminal = bool(transition.terminal)

        for event in transition.events:
            await self._pubsub.publish(event)

        return transition

    async def run(self, signals: AsyncIterable[SignalT]) -> StateT:
        async for signal in signals:
            transition = await self.step(signal)
            if transition.terminal:
                break
        return self._state
