from __future__ import annotations

import asyncio
import inspect
import threading
from dataclasses import dataclass
from typing import Any, Callable, ContextManager


@dataclass(frozen=True)
class RunnerStartResult:
    thread: threading.Thread | None = None
    future: Any | None = None
    close_fn: Callable[[], None] | None = None


def start_runner_lifecycle(
    *,
    name: str,
    result: Any,
    stop_event: threading.Event,
    submit_user_coro: Callable[[Any], Any],
    set_context: Callable[[], Any],
    reset_context: Callable[[Any], None],
    execution_scope_factory: Callable[[], ContextManager[Any]],
    emit_item: Callable[[Any], None],
    on_stopped: Callable[[str], None],
    on_error: Callable[[Exception], None],
    on_cancelled: Callable[[], None] | None = None,
    thread_name: str,
) -> RunnerStartResult | None:
    if inspect.isasyncgen(result):
        async def _run_async_gen():
            token = set_context()
            try:
                with execution_scope_factory():
                    async for item in result:
                        if stop_event.is_set():
                            break
                        emit_item(item)
                on_stopped("asyncgen")
            except asyncio.CancelledError:
                if on_cancelled is not None:
                    on_cancelled()
                raise
            except Exception as exc:
                on_error(exc)
            finally:
                reset_context(token)

        future = submit_user_coro(_run_async_gen())
        return RunnerStartResult(future=future, close_fn=None)

    if inspect.isgenerator(result):
        def _run_sync_gen():
            token = set_context()
            try:
                with execution_scope_factory():
                    for item in result:
                        if stop_event.is_set():
                            break
                        emit_item(item)
                on_stopped("generator")
            except Exception as exc:
                on_error(exc)
            finally:
                reset_context(token)

        thread = threading.Thread(target=_run_sync_gen, daemon=True, name=thread_name)
        thread.start()
        return RunnerStartResult(
            thread=thread,
            close_fn=getattr(result, "close", None),
        )

    if inspect.isawaitable(result):
        async def _run_async_once():
            token = set_context()
            try:
                with execution_scope_factory():
                    output = await result
                    emit_item(output)
                on_stopped("awaitable")
            except asyncio.CancelledError:
                if on_cancelled is not None:
                    on_cancelled()
                raise
            except Exception as exc:
                on_error(exc)
            finally:
                reset_context(token)

        future = submit_user_coro(_run_async_once())
        return RunnerStartResult(future=future, close_fn=None)

    if result is None:
        return None

    emit_item(result)
    on_stopped("value")
    return None


__all__ = ["RunnerStartResult", "start_runner_lifecycle"]
