"""Actor execution helpers.

This module centralizes local actor execution logic so dispatcher, mailbox, and
policy can evolve independently.
"""

from __future__ import annotations

import asyncio
from typing import Any, Callable

from flownet.api.actor_method_ref import ActorMethodRef
from flownet.runtime.contexts.actor import _build_actor_context, _user_execution_scope
from flownet.utils.async_tools import await_in_loop
from flownet.utils.context_vars import run_in_executor_with_context


class ActorCallExecutor:
    """Executes local actor calls with the correct user/system context."""

    def __init__(
        self,
        resolve_actor_fn: Callable[[str], Any],
        user_loop: asyncio.AbstractEventLoop | None,
        container_handler: Any,
    ) -> None:
        self._resolve_actor_fn = resolve_actor_fn
        self._user_loop = user_loop
        self._container_handler = container_handler

    async def execute(self, rmf: ActorMethodRef, *args, **kwargs) -> Any:
        if rmf.actor_id == "__container__":
            method = getattr(self._container_handler, rmf.method, None)
            if method is None:
                raise ValueError(
                    f"[FlownetRuntime] Method {rmf.method} not found on {rmf.actor_id}"
                )
            if asyncio.iscoroutinefunction(method):
                return await method(*args, **kwargs)
            return method(*args, **kwargs)

        actor = self._resolve_actor_fn(rmf.actor_id)
        # Resolve through Actor wrapper so lock/lockfree policy is preserved.
        method = getattr(actor, rmf.method, None)
        if method is None:
            raise ValueError(
                f"[FlownetRuntime] Method {rmf.method} not found on {rmf.actor_id}"
            )
        if rmf.actor_id.startswith("__"):
            if asyncio.iscoroutinefunction(method):
                return await method(*args, **kwargs)
            return method(*args, **kwargs)

        actor_ctx = _build_actor_context()
        if asyncio.iscoroutinefunction(method):
            with _user_execution_scope(actor_ctx):
                current_loop = asyncio.get_running_loop()
                if (
                    self._user_loop is not None
                    and self._user_loop.is_running()
                    and self._user_loop != current_loop
                ):
                    return await await_in_loop(self._user_loop, method(*args, **kwargs))
                return await method(*args, **kwargs)

        current_loop = asyncio.get_running_loop()
        with _user_execution_scope(actor_ctx):
            return await run_in_executor_with_context(current_loop, method, *args, **kwargs)
