"""
Boring-OS SDK flows client.

Provides the FlowsClient class for multi-agent flow operations:
run, stream, list, get, and register.
Accessed via `client.flows` on the main BoringOS client.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, AsyncIterator

if TYPE_CHECKING:
    from boring_os.client import BoringOS

from boring_os.flow_models import (
    FlowDefinition,
    FlowListResponse,
    FlowResult,
    FlowStreamChunk,
)


class FlowsClient:
    """
    Client for multi-agent flow operations.

    Accessed via `client.flows` on the main BoringOS client.
    """

    def __init__(self, client: "BoringOS"):
        self._client = client

    # -------------------------------------------------------------------------
    # Flow Execution
    # -------------------------------------------------------------------------

    def run(
        self,
        flow: str,
        message: str,
        timeout: int = 300,
    ) -> FlowResult:
        """
        Run a multi-agent flow synchronously.

        The server holds the connection while the flow executes
        through its node graph.

        Args:
            flow: Flow name to execute
            message: Initial user message
            timeout: Max seconds to wait (flows can be long)

        Returns:
            FlowResult with final text, nodes_executed, and cost

        Raises:
            FlowNotFoundError: If the flow doesn't exist
            FlowTimeoutError: If execution exceeds timeout

        Example:
            result = boring.flows.run(
                "billing_resolution_flow",
                message="I want a refund for overcharges"
            )
            print(result.text)
            print(f"Nodes: {result.node_count}")
        """
        response = self._client._request(
            "POST",
            "/flows/run",
            json={"flow": flow, "message": message},
            timeout=timeout + 5,
        )
        return FlowResult.model_validate(response)

    async def run_async(
        self,
        flow: str,
        message: str,
        timeout: int = 300,
    ) -> FlowResult:
        """Async version of run()."""
        response = await self._client._request_async(
            "POST",
            "/flows/run",
            json={"flow": flow, "message": message},
            timeout=timeout + 5,
        )
        return FlowResult.model_validate(response)

    async def stream(
        self,
        flow: str,
        message: str,
    ) -> AsyncIterator[FlowStreamChunk]:
        """
        Stream a flow execution, receiving chunks per agent node.

        Yields node transitions and per-node text output.

        Args:
            flow: Flow name to execute
            message: Initial user message

        Yields:
            FlowStreamChunk objects; node_changed=True on transitions

        Example:
            async for chunk in boring.flows.stream(
                "billing_resolution_flow", message="Explain charges"
            ):
                if chunk.node_changed:
                    print(f"\\n[Now: {chunk.current_node}]")
                if chunk.text:
                    print(chunk.text, end="")
        """
        async for chunk_data in self._client._stream_request(
            "POST",
            "/flows/stream",
            json={"flow": flow, "message": message},
        ):
            yield FlowStreamChunk.model_validate(chunk_data)

    # -------------------------------------------------------------------------
    # Flow CRUD
    # -------------------------------------------------------------------------

    def list(self) -> FlowListResponse:
        """
        List all registered flows.

        Returns:
            FlowListResponse with flow definitions and total count
        """
        response = self._client._request("GET", "/flows")
        return FlowListResponse.model_validate(response)

    def get(self, flow_name: str) -> FlowDefinition:
        """
        Get details for a specific flow.

        Args:
            flow_name: Name of the flow

        Returns:
            Full FlowDefinition

        Raises:
            FlowNotFoundError: If the flow doesn't exist
        """
        response = self._client._request("GET", f"/flows/{flow_name}")
        return FlowDefinition.model_validate(response)

    def register(self, flow_definition: dict[str, Any]) -> FlowDefinition:
        """
        Register a new flow.

        Args:
            flow_definition: Flow definition dict with name, nodes, edges

        Returns:
            Created FlowDefinition

        Example:
            boring.flows.register({
                "name": "support_flow",
                "nodes": [
                    {"name": "triage", "agent": "triage_agent"},
                    {"name": "billing", "agent": "billing_agent"},
                ],
                "edges": [
                    {"source": "triage", "target": "billing",
                     "condition_type": "keyword_match",
                     "condition_value": "billing"},
                ],
            })
        """
        response = self._client._request("POST", "/flows", json=flow_definition)
        return FlowDefinition.model_validate(response)
