# -*- coding: utf-8 -*-
"""Shared type aliases for the agentscope app layer."""
from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING, Protocol

from pydantic import BaseModel, Field

from ..agent import ContextConfig, ReActConfig
from ..event import AgentEvent
from ..middleware import MiddlewareBase
from ..permission import PermissionContext
from ..state import TaskContext
from ..tool import ToolBase
from ..workspace import WorkspaceBase

if TYPE_CHECKING:
    from ._service._session_projection import SessionProjection
    from .storage import AgentRecord, SessionRecord


AgentMiddlewareFactory = (
    Callable[[str, str, str], Awaitable[list[MiddlewareBase]]]
    | Callable[
        [str, str, str, WorkspaceBase],
        Awaitable[list[MiddlewareBase]],
    ]
)
# Async factory: ``(user_id, agent_id, session_id, workspace)`` → awaitable
# of :class:`~agentscope.middleware.MiddlewareBase` instances. The legacy
# three-argument form stays supported — ``ChatService`` probes the signature
# and only passes ``workspace`` to factories that accept it.

AgentToolFactory = Callable[
    [str, str, str],
    Awaitable[list[ToolBase]],
]
#  Async factory signature: ``(user_id, agent_id, session_id)`` →
#  awaitable of :class:`~agentscope.tool.ToolBase` instances.


class EventProjector(Protocol):
    """Strategy that projects events from one session onto another.

    Each projector owns one cross-session UI feed (HITL, progress,
    errors, …). :class:`~agentscope.app._service.ChatService` calls
    every registered projector once per event produced by a run; a
    projector decides whether the event is relevant and, if so, mirrors
    it onto the owning session's target via the shared
    :class:`SessionProjection` primitive. Adding a feed means adding a
    projector — no new bus wrapper, dispatcher, or manager.

    Service dependencies a projector needs (e.g. storage, to resolve the
    team a worker belongs to) are its own concern, injected at
    construction. Only per-run request data is passed to
    :meth:`maybe_project`.
    """

    async def maybe_project(
        self,
        user_id: str,
        session_record: "SessionRecord",
        agent_record: "AgentRecord",
        event: AgentEvent,
        projection: "SessionProjection",
    ) -> None:
        """Project ``event`` if it is relevant to this feed.

        Implementations must be a no-op for irrelevant events and
        sessions. They should not raise for ordinary "not applicable"
        cases; ``ChatService`` guards the call with a catch-all so a
        projection failure never tears down the producing run, but
        projectors should not rely on that for control flow.

        Args:
            user_id (`str`):
                The owner of the running session.
            session_record (`SessionRecord`):
                The currently-running session's record.
            agent_record (`AgentRecord`):
                The currently-running agent's record.
            event (`AgentEvent`):
                The event just published to this session's channel.
            projection (`SessionProjection`):
                Shared primitive used to write the durable entry and the
                live notification.
        """


class SubAgentTemplate(BaseModel):
    """A reusable blueprint for sub-agent creation within a team.

    Developers register one or more templates at the ``create_app`` entry
    point. When the leader agent calls ``AgentCreate`` with a matching
    ``subagent_type``, the template's configuration is used instead of the
    built-in defaults.

    The :attr:`type` field serves as the routing key — it becomes an enum
    value of the ``subagent_type`` parameter exposed to the LLM.  This is
    distinct from the ``name`` parameter in ``AgentCreate``, which is the
    per-instance identifier the leader assigns to each worker (used for
    ``TeamSay(to=name)``).

    All fields are pure data (no callables), so the template is fully
    serializable for future config-driven startup.
    """

    type: str = Field(
        description=(
            "Template type identifier, e.g. ``'researcher'`` or "
            "``'coder'``. Used as the enum value for the "
            "``subagent_type`` parameter in ``AgentCreate``."
        ),
    )

    description: str = Field(
        description=(
            "Agent-readable description of this sub-agent type. "
            "Exposed to the LLM in the ``AgentCreate`` tool schema "
            "so it can choose the appropriate type."
        ),
    )

    system_prompt_template: str = Field(
        description=(
            "A Python format string for the worker's system prompt. "
            "Available placeholders: ``{team_name}``, "
            "``{team_description}``, ``{member_name}``, "
            "``{member_description}``, ``{leader_name}``."
        ),
    )

    context_config: ContextConfig = Field(
        default_factory=ContextConfig,
        description="Context configuration for the sub-agent.",
    )

    react_config: ReActConfig = Field(
        default_factory=ReActConfig,
        description="ReAct loop configuration for the sub-agent.",
    )

    permission_context: PermissionContext = Field(
        default_factory=PermissionContext,
        description=(
            "Permission context applied to the sub-agent at "
            "creation time. Controls what the worker is allowed "
            "to do (e.g. read-only vs. full access)."
        ),
    )

    override_leader_mode: bool = Field(
        default=False,
        description=(
            "Whether the template's :attr:`permission_context.mode` "
            "should override the leader session's mode for the worker. "
            "``True`` — the worker runs in the template's mode "
            "(typical for templates that pin a specific posture, e.g. "
            "a read-only research worker). ``False`` (default) — the "
            "worker inherits the leader's current mode."
        ),
    )

    extend_leader_permission_rules: bool = Field(
        default=True,
        description=(
            "Whether the leader session's allow/deny/ask permission "
            "rules should be merged on top of the template's. "
            "``True`` (default) — leader rules are appended after "
            "the template's rules for each tool, so the worker "
            "doesn't re-prompt for permissions the user has already "
            "confirmed; the template's rules take precedence on "
            "evaluation order. ``False`` — the template's rules are "
            "the worker's complete rule set."
        ),
    )

    extend_leader_working_directories: bool = Field(
        default=True,
        description=(
            "Whether the leader session's working directories should "
            "be merged into the template's. ``True`` (default) — "
            "leader directories are added for keys not already in the "
            "template (template wins on key collisions). ``False`` — "
            "the template's working directories are the worker's "
            "complete set."
        ),
    )

    tasks_context: TaskContext = Field(
        default_factory=TaskContext,
        description=(
            "Pre-defined task context for the sub-agent, allowing "
            "the template to seed an initial workflow."
        ),
    )
