from collections.abc import Awaitable, Callable

import tractor
import trio


async def breakpoint_forever() -> None:
    '''
    Indefinitely re-enter debugger in child actor.

    '''
    while True:
        await trio.sleep(0.1)
        await tractor.pause()


async def name_error() -> None:
    '''
    Raise a ``NameError``.

    '''
    getattr(doggypants)  # noqa


async def spawn_error() -> None:
    '''
    A nested nursery that triggers another ``NameError``.

    '''
    async with tractor.open_nursery() as an:
        return await tractor.to_actor.run(
            name_error,
            an=an,
            name='name_error_1',
        )


async def main() -> None:
    '''
    The main ``tractor`` routine.

    The process tree should look as approximately as follows:

    -python examples/debugging/multi_subactors.py
    |-python -m tractor._child --uid ('name_error', 'a7caf490 ...)
    |-python -m tractor._child --uid ('bp_forever', '1f787a7e ...)
    `-python -m tractor._child --uid ('spawn_error', '52ee14a5 ...)
       `-python -m tractor._child --uid ('name_error', '3391222c ...)

    '''
    errors: list[BaseException] = []

    async with tractor.open_nursery(
        debug_mode=True,
        # loglevel='runtime',
    ) as an:

        async def run_and_collect(
            fn: Callable[[], Awaitable[object]],
        ) -> None:
            '''
            One-shot whose (boxed) error is stashed instead of
            raised so a sibling's crash never cancels the others
            before they've had their own debugger sessions (the
            'collect all errors' the legacy `run_in_actor()` API
            did implicitly at nursery teardown).

            '''
            try:
                await tractor.to_actor.run(fn, an=an)
            except tractor.RemoteActorError as rae:
                errors.append(rae)

        # Spawn all one-shot task actors, collecting (vs.
        # raising) their errors.
        async with trio.open_nursery() as tn:
            tn.start_soon(run_and_collect, breakpoint_forever)
            tn.start_soon(run_and_collect, name_error)
            tn.start_soon(run_and_collect, spawn_error)

        if errors:
            raise BaseExceptionGroup(
                'multi_subactors errored!',
                errors,
            )


if __name__ == '__main__':
    trio.run(main)
