"""CrewMixin — crew-style task orchestration: run_tasks, resume, execute, route."""
from __future__ import annotations

import asyncio
import inspect
import time
from typing import Any
from uuid import uuid4

from logos_adk.crew import (
    CrewProcessMode,
    CrewResult,
    CrewRoutingDecisionSchema,
    CrewTask,
    CrewTaskResult,
)
from logos_adk.ui_events import make_ui_event

from logos_adk.team._types import AgentLike


class CrewMixin:
    """Crew-style task orchestration over teams."""

    # -- public API ----------------------------------------------------------

    async def run_tasks(
        self,
        tasks: list[CrewTask],
        *,
        kickoff: str = "",
        process: CrewProcessMode = "sequential",
        manager_role: str | None = None,
        max_parallel_tasks: int | None = None,
        approval_responses: dict[str, bool] | None = None,
        approval_resolver: Any | None = None,
        session_id: str | None = None,
        **kwargs: Any,
    ) -> CrewResult:
        """Execute explicit crew tasks over the current team."""
        if not tasks:
            raise ValueError("run_tasks() requires at least one CrewTask")
        kwargs = self._apply_budget_kwargs(kwargs)
        self._validate_crew_tasks(tasks)
        run_id = uuid4().hex
        effective_process = process or self._config.process or "sequential"
        effective_manager_role = manager_role or self._config.manager_role
        if effective_process not in ("sequential", "hierarchical"):
            raise ValueError(f"Unsupported crew process '{effective_process}'")
        return await self._run_crew_tasks(
            tasks,
            process=effective_process,
            run_id=run_id,
            kickoff=kickoff,
            manager_role=effective_manager_role,
            max_parallel_tasks=max_parallel_tasks,
            approval_responses=approval_responses,
            approval_resolver=approval_resolver,
            session_id=session_id,
            **kwargs,
        )

    async def resume_approval(
        self,
        run_id: str,
        *,
        task_name: str,
        approved: bool,
        session_id: str | None = None,
        **kwargs: Any,
    ) -> CrewResult:
        paused_run = self._load_paused_crew_run(run_id)
        if paused_run is None:
            raise ValueError(f"No paused crew run found for run_id '{run_id}'")
        pending_task_name = paused_run.pending_approval.get("task_name")
        if pending_task_name != task_name:
            raise ValueError(
                f"Paused crew run '{run_id}' is waiting for approval on '{pending_task_name}', not '{task_name}'"
            )
        self._paused_crew_runs.pop(run_id, None)
        effective_kwargs = dict(paused_run.kwargs or {})
        effective_kwargs.update(kwargs)
        approval_responses = dict(effective_kwargs.pop("approval_responses", {}) or {})
        approval_responses[task_name] = bool(approved)
        approval_resolver = effective_kwargs.pop("approval_resolver", None)
        effective_session_id = session_id if session_id is not None else paused_run.session_id
        return await self._run_crew_tasks(
            paused_run.tasks,
            process=paused_run.process,
            run_id=run_id,
            kickoff=paused_run.kickoff,
            manager_role=paused_run.manager_role,
            max_parallel_tasks=paused_run.max_parallel_tasks,
            approval_responses=approval_responses,
            approval_resolver=approval_resolver,
            session_id=effective_session_id,
            context=paused_run.context,
            task_results_by_name=paused_run.task_results_by_name,
            task_results=paused_run.task_results,
            handoffs=paused_run.handoffs,
            pending=paused_run.pending,
            completed=paused_run.completed,
            carried_role=paused_run.carried_role,
            carried_agent=paused_run.carried_agent,
            last_role=paused_run.last_role,
            last_agent=paused_run.last_agent,
            **effective_kwargs,
        )

    # -- unified crew task runner --------------------------------------------

    async def _run_crew_tasks(
        self,
        tasks: list[CrewTask],
        *,
        process: CrewProcessMode,
        run_id: str,
        kickoff: str,
        max_parallel_tasks: int | None,
        approval_responses: dict[str, bool] | None,
        approval_resolver: Any | None,
        session_id: str | None,
        manager_role: str | None = None,
        context: dict[str, str] | None = None,
        task_results_by_name: dict[str, CrewTaskResult] | None = None,
        task_results: list[CrewTaskResult] | None = None,
        handoffs: list[dict[str, Any]] | None = None,
        pending: dict[str, CrewTask] | None = None,
        completed: set[str] | None = None,
        carried_role: str | None = None,
        carried_agent: str | None = None,
        last_role: str | None = None,
        last_agent: str | None = None,
        **kwargs: Any,
    ) -> CrewResult:
        is_hierarchical = process == "hierarchical"
        context = dict(context or {"kickoff": kickoff})
        task_results_by_name = dict(task_results_by_name or {})
        task_results = list(task_results or [])
        handoffs = [dict(item) for item in (handoffs or [])]
        event_callback = kwargs.get("_workflow_event_callback")
        trace_id = kwargs.get("_trace_id")
        correlation_id = kwargs.get("correlation_id") or session_id or run_id
        sequence = int(kwargs.get("_workflow_event_sequence", 0))
        order = {task.name: index for index, task in enumerate(tasks)}
        pending = dict(pending or {task.name: task for task in tasks})
        completed = set(completed or set())
        status = "completed"
        pending_approval: dict[str, Any] | None = None

        # Hierarchical-only: resolve manager agent for routing
        manager_agent: AgentLike | None = None
        manager_agent_name: str | None = None
        if is_hierarchical:
            manager_agent = self._resolve_manager_agent(manager_role)
            manager_agent_name = manager_agent.name if manager_agent is not None else self._orchestrator_name

        while pending:
            ready = [
                task
                for task in tasks
                if task.name in pending and all(dep in completed for dep in task.depends_on)
            ]
            if not ready:
                unresolved = ", ".join(sorted(pending))
                raise ValueError(f"Crew tasks contain unresolved dependencies for: {unresolved}")
            batch = self._select_ready_batch(ready, max_parallel_tasks=max_parallel_tasks)

            # Emit node_started events
            for task in batch:
                started_output: dict[str, Any] = {"assigned_role": task.role, "assigned_agent": task.agent_name}
                if is_hierarchical:
                    started_output["manager_agent"] = manager_agent_name
                await self._emit_workflow_event(
                    event_callback,
                    make_ui_event(
                        event="node_started",
                        event_type="crew_task",
                        graph_name=self.name,
                        graph_run_id=run_id,
                        sequence=sequence,
                        session_id=session_id,
                        correlation_id=correlation_id,
                        trace_id=trace_id,
                        node_name=task.name,
                        status="running",
                        output=started_output,
                    ),
                )
                sequence += 1

            # Execute batch
            shared_context = dict(context)
            if is_hierarchical:
                results = await asyncio.gather(
                    *[
                        self._execute_hierarchical_task(
                            task,
                            kickoff=kickoff,
                            context=shared_context,
                            manager_role=manager_role,
                            manager_agent=manager_agent,
                            task_results_by_name=task_results_by_name,
                            approval_responses=approval_responses,
                            approval_resolver=approval_resolver,
                            session_id=session_id,
                            **kwargs,
                        )
                        for task in batch
                    ]
                )
            else:
                results = await asyncio.gather(
                    *[
                        self._execute_sequential_task(
                            task,
                            kickoff=kickoff,
                            context=shared_context,
                            carried_role=carried_role,
                            carried_agent=carried_agent,
                            task_results_by_name=task_results_by_name,
                            approval_responses=approval_responses,
                            approval_resolver=approval_resolver,
                            session_id=session_id,
                            **kwargs,
                        )
                        for task in batch
                    ]
                )

            # Process results in task order
            for task, result, approval_info in sorted(results, key=lambda item: order[item[0].name]):
                completed_output: dict[str, Any] = {
                    "task_name": task.name,
                    "status": result.status,
                    "output": result.output,
                    "assigned_role": result.assigned_role,
                    "assigned_agent": result.assigned_agent,
                }
                if is_hierarchical:
                    completed_output["manager_agent"] = manager_agent_name
                else:
                    completed_output["handoff_to_role"] = result.handoff_to_role
                    completed_output["handoff_to_agent"] = result.handoff_to_agent
                await self._emit_workflow_event(
                    event_callback,
                    make_ui_event(
                        event="node_completed",
                        event_type="crew_task",
                        graph_name=self.name,
                        graph_run_id=run_id,
                        sequence=sequence,
                        session_id=session_id,
                        correlation_id=correlation_id,
                        trace_id=trace_id,
                        node_name=task.name,
                        status=result.status,
                        output=completed_output,
                    ),
                )
                sequence += 1

                # Build handoff record (hierarchical always, sequential only on explicit handoff)
                current_handoff: dict[str, Any] | None = None
                if is_hierarchical:
                    current_handoff = {
                        "task": task.name,
                        "from_role": last_role,
                        "from_agent": last_agent,
                        "to_role": result.assigned_role,
                        "to_agent": result.assigned_agent,
                        "manager_agent": manager_agent_name,
                    }

                # Handle paused (approval) tasks
                if result.status == "paused":
                    pending_approval = {"run_id": run_id, **(approval_info or {})}
                    self._store_paused_crew_run(
                        run_id=run_id,
                        process=process,
                        tasks=tasks,
                        kickoff=kickoff,
                        context=context,
                        task_results=task_results,
                        task_results_by_name=task_results_by_name,
                        handoffs=handoffs,
                        pending=pending,
                        completed=completed,
                        pending_approval=pending_approval,
                        max_parallel_tasks=max_parallel_tasks,
                        session_id=session_id,
                        kwargs=kwargs,
                        manager_role=manager_role if is_hierarchical else None,
                        carried_role=carried_role if not is_hierarchical else None,
                        carried_agent=carried_agent if not is_hierarchical else None,
                        last_role=last_role if is_hierarchical else None,
                        last_agent=last_agent if is_hierarchical else None,
                    )
                    paused_handoffs = [*handoffs, current_handoff] if current_handoff else handoffs
                    return self._build_crew_result(
                        run_id=run_id,
                        process=process,
                        kickoff=kickoff,
                        status=result.status,
                        context=context,
                        task_results=[*task_results, result],
                        handoffs=paused_handoffs,
                        manager_role=manager_role if is_hierarchical else None,
                        manager_agent=manager_agent_name if is_hierarchical else None,
                        pending_approval=pending_approval,
                    )

                task_results.append(result)
                task_results_by_name[task.name] = result
                pending.pop(task.name, None)

                # Track handoffs
                if current_handoff:
                    handoffs.append(current_handoff)

                if result.status == "completed":
                    completed.add(task.name)
                    context[task.name] = result.output
                    if task.output_key:
                        context[task.output_key] = result.output
                    if is_hierarchical:
                        last_role = result.assigned_role
                        last_agent = result.assigned_agent
                    else:
                        next_role = task.handoff_to_role or result.handoff_to_role
                        next_agent = task.handoff_to_agent or result.handoff_to_agent
                        if next_role or next_agent:
                            handoffs.append(
                                {
                                    "task": task.name,
                                    "from_role": result.assigned_role,
                                    "from_agent": result.assigned_agent,
                                    "to_role": next_role,
                                    "to_agent": next_agent,
                                }
                            )
                        carried_role = next_role or carried_role
                        carried_agent = next_agent or carried_agent
                    continue

                # Non-completed, non-paused -> early exit (failed/etc.)
                status = result.status
                self._clear_paused_crew_run(run_id)
                return self._build_crew_result(
                    run_id=run_id,
                    process=process,
                    kickoff=kickoff,
                    status=status,
                    context=context,
                    task_results=task_results,
                    handoffs=handoffs,
                    manager_role=manager_role if is_hierarchical else None,
                    manager_agent=manager_agent_name if is_hierarchical else None,
                    pending_approval=pending_approval,
                )

        self._clear_paused_crew_run(run_id)
        return self._build_crew_result(
            run_id=run_id,
            process=process,
            kickoff=kickoff,
            status=status,
            context=context,
            task_results=task_results,
            handoffs=handoffs,
            manager_role=manager_role if is_hierarchical else None,
            manager_agent=manager_agent_name if is_hierarchical else None,
            pending_approval=None,
        )

    # -- task execution helpers ----------------------------------------------

    async def _execute_sequential_task(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        carried_role: str | None,
        carried_agent: str | None,
        task_results_by_name: dict[str, CrewTaskResult],
        approval_responses: dict[str, bool] | None,
        approval_resolver: Any | None,
        session_id: str | None,
        **kwargs: Any,
    ) -> tuple[CrewTask, CrewTaskResult, dict[str, Any] | None]:
        assigned_role, assigned_agent = self._resolve_inherited_assignment(
            task,
            task_results_by_name=task_results_by_name,
            carried_role=carried_role,
            carried_agent=carried_agent,
        )
        if task.task_type == "approval":
            result, approval_info = await self._execute_approval_task(
                task,
                context=context,
                approval_responses=approval_responses,
                approval_resolver=approval_resolver,
            )
            return task, result, approval_info
        result = await self._execute_crew_task(
            task,
            kickoff=kickoff,
            context=context,
            assigned_role=assigned_role,
            assigned_agent=assigned_agent,
            session_id=session_id,
            manager_instruction=None,
            **kwargs,
        )
        return task, result, None

    async def _execute_hierarchical_task(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        manager_role: str | None,
        manager_agent: AgentLike,
        task_results_by_name: dict[str, CrewTaskResult],
        approval_responses: dict[str, bool] | None,
        approval_resolver: Any | None,
        session_id: str | None,
        **kwargs: Any,
    ) -> tuple[CrewTask, CrewTaskResult, dict[str, Any] | None]:
        if task.task_type == "approval":
            result, approval_info = await self._execute_approval_task(
                task,
                context=context,
                approval_responses=approval_responses,
                approval_resolver=approval_resolver,
            )
            return task, result, approval_info
        decision = await self._route_hierarchical_task(
            task,
            kickoff=kickoff,
            context=context,
            manager_role=manager_role,
            manager_agent=manager_agent,
            session_id=session_id,
            **kwargs,
        )
        result = await self._execute_crew_task(
            task,
            kickoff=kickoff,
            context=context,
            assigned_role=decision.get("role"),
            assigned_agent=decision.get("agent_name"),
            session_id=session_id,
            manager_instruction=decision.get("instruction"),
            **kwargs,
        )
        return task, result, None

    def _resolve_inherited_assignment(
        self,
        task: CrewTask,
        *,
        task_results_by_name: dict[str, CrewTaskResult],
        carried_role: str | None,
        carried_agent: str | None,
    ) -> tuple[str | None, str | None]:
        if task.role or task.agent_name:
            return task.role, task.agent_name
        for dep_name in reversed(task.depends_on):
            dep_result = task_results_by_name.get(dep_name)
            if dep_result is None:
                continue
            inherited_agent = dep_result.handoff_to_agent or dep_result.assigned_agent
            inherited_role = dep_result.handoff_to_role or dep_result.assigned_role
            if inherited_role or inherited_agent:
                return inherited_role, inherited_agent
        return carried_role, carried_agent

    async def _execute_approval_task(
        self,
        task: CrewTask,
        *,
        context: dict[str, str],
        approval_responses: dict[str, bool] | None,
        approval_resolver: Any | None,
    ) -> tuple[CrewTaskResult, dict[str, Any] | None]:
        started = time.monotonic()
        decision, resolver_metadata = await self._resolve_approval_decision(
            task,
            context=context,
            approval_responses=approval_responses,
            approval_resolver=approval_resolver,
        )
        duration_ms = (time.monotonic() - started) * 1000
        base_metadata = {
            "approval_message": task.approval_message or task.description,
            "resolver_metadata": resolver_metadata,
            "process_task_metadata": task.metadata,
        }
        if decision is None:
            pending_approval = {
                "task_name": task.name,
                "message": task.approval_message or task.description,
                "depends_on": list(task.depends_on),
                "context": {
                    key: context[key]
                    for key in list(dict.fromkeys([*task.depends_on, *task.context]))
                    if key in context
                },
            }
            return (
                CrewTaskResult(
                    task_name=task.name,
                    task_type="approval",
                    assigned_agent="human",
                    assigned_role="human",
                    prompt=task.approval_message or task.description,
                    output="",
                    status="paused",
                    duration_ms=duration_ms,
                    depends_on=list(task.depends_on),
                    async_execution=task.async_execution,
                    handoff_to_role=task.handoff_to_role,
                    handoff_to_agent=task.handoff_to_agent,
                    metadata={**base_metadata, "pending_approval": pending_approval},
                ),
                pending_approval,
            )
        approved = bool(decision)
        return (
            CrewTaskResult(
                task_name=task.name,
                task_type="approval",
                assigned_agent="human",
                assigned_role="human",
                prompt=task.approval_message or task.description,
                output="approved" if approved else "rejected",
                status="completed" if approved else "failed",
                duration_ms=duration_ms,
                depends_on=list(task.depends_on),
                async_execution=task.async_execution,
                handoff_to_role=task.handoff_to_role,
                handoff_to_agent=task.handoff_to_agent,
                metadata={**base_metadata, "approved": approved},
            ),
            None,
        )

    async def _resolve_approval_decision(
        self,
        task: CrewTask,
        *,
        context: dict[str, str],
        approval_responses: dict[str, bool] | None,
        approval_resolver: Any | None,
    ) -> tuple[bool | None, dict[str, Any]]:
        if approval_responses is not None and task.name in approval_responses:
            return bool(approval_responses[task.name]), {"source": "approval_responses"}
        if approval_resolver is None:
            return None, {}
        result = approval_resolver(task, context)
        if inspect.isawaitable(result):
            result = await result
        if isinstance(result, dict):
            if "approved" not in result:
                raise ValueError(f"Approval resolver for task '{task.name}' must return 'approved'")
            metadata = {key: value for key, value in result.items() if key != "approved"}
            return bool(result["approved"]), metadata
        if isinstance(result, bool):
            return result, {"source": "approval_resolver"}
        raise ValueError(
            f"Approval resolver for task '{task.name}' must return bool or dict with 'approved'"
        )

    async def _route_hierarchical_task(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        manager_role: str | None,
        manager_agent: AgentLike,
        session_id: str | None,
        **kwargs: Any,
    ) -> dict[str, str | None]:
        if task.role or task.agent_name:
            return {
                "role": task.role,
                "agent_name": task.agent_name,
                "instruction": None,
            }
        manager_prompt = self._build_manager_prompt(
            task,
            kickoff=kickoff,
            context=context,
            manager_role=manager_role,
        )
        response = await manager_agent.run(
            manager_prompt,
            session_id=session_id,
            output_schema=CrewRoutingDecisionSchema,
            **kwargs,
        )
        decision = getattr(response, "parsed", None)
        if isinstance(decision, CrewRoutingDecisionSchema):
            resolved = decision.model_dump()
        else:
            resolved = CrewRoutingDecisionSchema.model_validate_json(response.content).model_dump()
        if not resolved.get("role") and not resolved.get("agent_name"):
            raise ValueError(
                f"Hierarchical routing for task '{task.name}' must select either role or agent_name"
            )
        return {
            "role": resolved.get("role"),
            "agent_name": resolved.get("agent_name"),
            "instruction": resolved.get("instruction"),
        }

    async def _execute_crew_task(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        assigned_role: str | None,
        assigned_agent: str | None,
        session_id: str | None,
        manager_instruction: str | None,
        **kwargs: Any,
    ) -> CrewTaskResult:
        target = self._resolve_task_target(assigned_role=assigned_role, assigned_agent=assigned_agent)
        prompt = self._build_task_prompt(
            task,
            kickoff=kickoff,
            context=context,
            assigned_role=assigned_role,
            manager_instruction=manager_instruction,
        )
        started = time.monotonic()
        response = await target.run(prompt, session_id=session_id, **kwargs)
        duration_ms = (time.monotonic() - started) * 1000
        return CrewTaskResult(
            task_name=task.name,
            task_type=task.task_type,
            assigned_agent=target.name,
            assigned_role=getattr(getattr(target, "_config", None), "role", None),
            prompt=prompt,
            output=response.content,
            status="completed",
            duration_ms=duration_ms,
            depends_on=list(task.depends_on),
            async_execution=task.async_execution,
            handoff_to_role=task.handoff_to_role,
            handoff_to_agent=task.handoff_to_agent,
            metadata={
                "expected_output": task.expected_output,
                "output_key": task.output_key,
                "process_task_metadata": task.metadata,
                "raw": response.raw,
            },
        )

    # -- helpers -------------------------------------------------------------

    def _resolve_manager_agent(self, manager_role: str | None) -> AgentLike:
        if manager_role is not None:
            manager = self.get_agent_by_role(manager_role)
            if manager is None:
                raise ValueError(
                    f"Manager role '{manager_role}' is not assigned to any agent in team '{self.name}'"
                )
            return manager
        if self._orchestrator_name is None:
            raise ValueError(f"Team '{self.name}' has no orchestrator to run hierarchical process")
        return self._agents[self._orchestrator_name]

    def _validate_crew_tasks(self, tasks: list[CrewTask]) -> None:
        task_names = [task.name for task in tasks]
        if len(set(task_names)) != len(task_names):
            raise ValueError("Crew task names must be unique")
        known = set(task_names)
        for task in tasks:
            missing = [dep for dep in task.depends_on if dep not in known]
            if missing:
                raise ValueError(
                    f"Crew task '{task.name}' depends on unknown tasks: {', '.join(missing)}"
                )

    def _select_ready_batch(
        self,
        ready: list[CrewTask],
        *,
        max_parallel_tasks: int | None,
    ) -> list[CrewTask]:
        first = ready[0]
        if first.task_type == "approval" or not first.async_execution:
            return [first]
        candidates = [task for task in ready if task.task_type == "work" and task.async_execution]
        if max_parallel_tasks is None:
            return candidates
        return candidates[:max(1, max_parallel_tasks)]

    def _build_crew_result(
        self,
        *,
        run_id: str,
        process: CrewProcessMode,
        kickoff: str,
        status: str,
        context: dict[str, str],
        task_results: list[CrewTaskResult],
        handoffs: list[dict[str, Any]],
        manager_role: str | None = None,
        manager_agent: str | None = None,
        pending_approval: dict[str, Any] | None = None,
    ) -> CrewResult:
        final_output = kickoff
        for result in task_results:
            if result.output:
                final_output = result.output
        return CrewResult(
            run_id=run_id,
            crew_name=self.name,
            process=process,
            status=status,
            final_output=final_output,
            task_results=task_results,
            context=context,
            handoffs=handoffs,
            manager_role=manager_role,
            manager_agent=manager_agent,
            pending_approval=pending_approval,
        )

    def _resolve_task_target(self, *, assigned_role: str | None, assigned_agent: str | None) -> AgentLike:
        if assigned_agent is not None:
            agent = self.get_agent(assigned_agent)
            if agent is None:
                raise ValueError(f"Agent '{assigned_agent}' is not registered in team '{self.name}'")
            return agent
        if assigned_role is not None:
            agent = self.get_agent_by_role(assigned_role)
            if agent is None:
                raise ValueError(f"Role '{assigned_role}' is not assigned to any agent in team '{self.name}'")
            return agent
        if self._orchestrator_name is None:
            raise ValueError(f"Task target could not be resolved in team '{self.name}'")
        return self._agents[self._orchestrator_name]

    def _build_task_prompt(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        assigned_role: str | None,
        manager_instruction: str | None,
    ) -> str:
        sections = [
            f"Crew task: {task.name}",
            f"Kickoff:\n{kickoff}",
            f"Task description:\n{task.description}",
        ]
        if assigned_role:
            sections.append(f"Assigned role:\n{assigned_role}")
        if task.expected_output:
            sections.append(f"Expected output:\n{task.expected_output}")
        if task.depends_on:
            sections.append("Depends on tasks:\n" + ", ".join(task.depends_on))
        if manager_instruction:
            sections.append(f"Manager instruction:\n{manager_instruction}")
        effective_context_keys = list(dict.fromkeys([*task.depends_on, *task.context]))
        if effective_context_keys:
            context_lines = []
            for key in effective_context_keys:
                if key not in context:
                    continue
                value = context[key]
                # Truncate long context values to avoid token waste
                if len(value) > 2000:
                    value = value[:2000] + "\n... [truncated]"
                context_lines.append(f"- {key}: {value}")
            if context_lines:
                sections.append("Context from prior tasks:\n" + "\n".join(context_lines))
        if task.metadata:
            sections.append(f"Task metadata:\n{task.metadata}")
        sections.append("Return only the task result.")
        return "\n\n".join(sections)

    def _build_manager_prompt(
        self,
        task: CrewTask,
        *,
        kickoff: str,
        context: dict[str, str],
        manager_role: str | None,
    ) -> str:
        roster_lines = []
        for entry in self.roster():
            line = f"- {entry['name']}: role={entry.get('role', '')}"
            if entry.get("goal"):
                line += f"; goal={entry['goal']}"
            roster_lines.append(line)
        context_lines = [
            f"- {key}: {value}"
            for key, value in context.items()
            if key != "kickoff"
        ]
        manager_header = "You are a crew manager assigning work."
        if manager_role:
            manager_header += f" Your role is {manager_role}."
        return (
            manager_header
            + "\nReturn JSON only with shape "
            + "{\"role\": \"...\", \"agent_name\": null, \"instruction\": \"...\"}.\n\n"
            + "Team roster:\n"
            + ("\n".join(roster_lines) if roster_lines else "- No explicit roles assigned")
            + "\n\nKickoff:\n"
            + kickoff
            + "\n\nTask:\n"
            + task.description
            + ("\n\nDepends on:\n" + ", ".join(task.depends_on) if task.depends_on else "")
            + ("\n\nExpected output:\n" + task.expected_output if task.expected_output else "")
            + ("\n\nAvailable context:\n" + "\n".join(context_lines) if context_lines else "")
        )

    async def _emit_workflow_event(self, callback: Any, event: dict[str, Any]) -> None:
        if callback is None:
            return
        result = callback(event)
        if asyncio.iscoroutine(result):
            await result
