from __future__ import annotations

from typing import Any, Callable

from flownet.core.request import FlowAckRoute


class RunControlOutputCoordinator:
    """
    Runtime-managed control-only coordinator.

    This adapter provides an ACK target when a run has no user-visible output
    channel (for example default/void egress). It participates in control-path
    request accounting but intentionally carries no data-plane methods.
    """

    def __init__(self, *, runtime_managed: bool = True):
        self.runtime_managed = runtime_managed
        self._actor_id: str | None = None
        self._actor_ref = None
        self._runtime_start_request: Callable[[str, int], Any] | None = None
        self._runtime_ack_request_delta: Callable[[str, int], Any] | None = None
        self._runtime_next_request_meta: Callable[[str], dict[str, Any]] | None = None
        self._runtime_finish_request: Callable[[str], Any] | None = None
        self._runtime_close_ingress: Callable[[], bool] | None = None
        self._runtime_request_done: Callable[[str], bool] | None = None

    def _get_actor_id(self) -> str:
        if self._actor_id is None:
            from flownet.api.runtime import make_object_actor

            self._actor_ref = make_object_actor(self)
            self._actor_id = self._actor_ref.actor_id
        return self._actor_id

    def _get_actor_address(self) -> str | None:
        actor_ref = self._actor_ref
        if actor_ref is None:
            return None
        normalized = str(getattr(actor_ref, "address", "") or "").strip()
        return normalized or None

    def ack_route(self) -> FlowAckRoute:
        return FlowAckRoute(
            coordinator_target=self._get_actor_id(),
            coordinator_address=self._get_actor_address(),
        )

    def event_chain_done(self, request_id: str, delta: int) -> int:
        if not request_id:
            return 0
        if self._runtime_ack_request_delta is None:
            return 0
        pending = self._runtime_ack_request_delta(request_id, int(delta))
        try:
            return int(pending)
        except (TypeError, ValueError):
            return 0

    def flow_error(
        self,
        request_id: str,
        error_type: str,
        message: str,
        traceback_text: str,
        event: Any | None = None,
    ) -> None:
        del error_type, message, traceback_text, event
        if not request_id:
            return
        if self._runtime_ack_request_delta is None:
            return
        pending = self._runtime_ack_request_delta(request_id, 0)
        try:
            pending_value = int(pending)
        except (TypeError, ValueError):
            return
        if pending_value > 0:
            self._runtime_ack_request_delta(request_id, -pending_value)

    def finish(self, request_id: str) -> bool:
        if not request_id:
            return False
        if self._runtime_finish_request is None:
            return False
        return bool(self._runtime_finish_request(request_id))

    def close(self) -> bool:
        if self._runtime_close_ingress is None:
            return False
        return bool(self._runtime_close_ingress())

    def close_ingress(self) -> bool:
        return self.close()

    def bind_runtime_tracker(
        self,
        *,
        start_request: Callable[[str, int], Any] | None = None,
        ack_request_delta: Callable[[str, int], Any] | None = None,
        next_request_meta: Callable[[str], dict[str, Any]] | None = None,
        finish_request: Callable[[str], Any] | None = None,
        close_ingress: Callable[[], bool] | None = None,
        request_done: Callable[[str], bool] | None = None,
    ) -> None:
        self._runtime_start_request = start_request
        self._runtime_ack_request_delta = ack_request_delta
        self._runtime_next_request_meta = next_request_meta
        self._runtime_finish_request = finish_request
        self._runtime_close_ingress = close_ingress
        self._runtime_request_done = request_done


__all__ = ["RunControlOutputCoordinator"]
