from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable, Protocol


class ActionStepsBuilder(Protocol):
    """把单个 task 转换为确定性白名单动作步骤序列。"""

    def __call__(self, task: Any) -> list[Any]: ...


class ActionExecutor(Protocol):
    """执行单个 action_type 对应的副作用或查询,返回 StewardActionExecuteResponse。"""

    def __call__(self, executor: Any, request: Any, current_user: Any, trace: list[dict[str, Any]]) -> Any: ...


@dataclass(frozen=True)
class IntentDescriptor:
    """单个业务意图的声明式描述符。

    注册一个 IntentDescriptor 即可让意图识别、动作生成、执行分发、字段过滤
    全部自动适配,无需改动 if/else 链。
    """

    task_type: str
    assigned_agent: str
    label: str
    """中文标签,用于 system prompt、前端展示和日志。"""

    action_steps_builder: ActionStepsBuilder
    """把 StewardTask 转换为白名单动作步骤的可调用对象。"""

    signal_keywords: tuple[str, ...] = ()
    """规则兜底 / off_topic 门控的关键词;命中即视为业务相关。"""

    ontology_field_allowlist: tuple[str, ...] = ()
    """该意图允许的 canonical 槽位;为空表示沿用全局 BUSINESS_CANONICAL_FIELDS。"""

    side_effect_actions: tuple[str, ...] = ()
    """该意图产生副作用的 action_type 集合;会被纳入执行器白名单。"""

    noop_actions: tuple[str, ...] = ()
    """该意图的无副作用 action_type 集合(填充/预览/校验等)。"""

    executor: ActionExecutor | None = None
    """副作用或查询动作的执行函数;None 表示全部走 NOOP。"""

    flow_id: str | None = None
    """候选流程确认使用的 flow_id;查询类意图为 None。"""

    prompt_fragment: str = ""
    """注入 steward_intent_agent system prompt 的识别指引片段。"""


_REGISTRY: dict[str, IntentDescriptor] = {}
_FLOW_TO_TASK: dict[str, str] = {}


def register_intent(descriptor: IntentDescriptor) -> IntentDescriptor:
    """注册一个业务意图;重复注册以最后一次为准。"""
    _REGISTRY[descriptor.task_type] = descriptor
    if descriptor.flow_id:
        _FLOW_TO_TASK[descriptor.flow_id] = descriptor.task_type
    return descriptor


def get_intent(task_type: str) -> IntentDescriptor | None:
    return _REGISTRY.get(str(task_type or "").strip())


def all_intents() -> list[IntentDescriptor]:
    return list(_REGISTRY.values())


def all_task_types() -> list[str]:
    return [desc.task_type for desc in _REGISTRY.values()]


def all_assigned_agents() -> list[str]:
    return [desc.assigned_agent for desc in _REGISTRY.values()]


def all_flow_ids() -> list[str]:
    return [desc.flow_id for desc in _REGISTRY.values() if desc.flow_id]


def all_signal_keywords() -> set[str]:
    keywords: set[str] = set()
    for desc in _REGISTRY.values():
        keywords.update(desc.signal_keywords)
    return keywords


def all_side_effect_actions() -> set[str]:
    actions: set[str] = set()
    for desc in _REGISTRY.values():
        actions.update(desc.side_effect_actions)
    return actions


def all_noop_actions() -> set[str]:
    actions: set[str] = set()
    for desc in _REGISTRY.values():
        actions.update(desc.noop_actions)
    return actions


def resolve_task_type_for_flow(flow_id: str) -> str | None:
    return _FLOW_TO_TASK.get(str(flow_id or "").strip())


def resolve_intent_by_action(action_type: str) -> IntentDescriptor | None:
    """根据 action_type 反查它所属的意图描述符。"""
    normalized = str(action_type or "").strip()
    for desc in _REGISTRY.values():
        if normalized in desc.side_effect_actions or normalized in desc.noop_actions:
            return desc
    return None


def field_allowlist_for(task_type: str, *, fallback: frozenset[str] | None = None) -> frozenset[str]:
    """返回某意图允许的槽位集合;未声明则返回 fallback。"""
    desc = get_intent(task_type)
    if desc and desc.ontology_field_allowlist:
        return frozenset(desc.ontology_field_allowlist)
    return fallback or frozenset()
