"""Host 侧组件注册表。

对齐旧系统 component_registry.py 的核心能力：
- 按类型注册组件（action / command / tool / event_handler / hook_handler / message_gateway）
- 命名空间 (plugin_id.component_name)
- 命令正则匹配
- 组件启用/禁用
- 多维度查询（按名称、类型、插件）
- 注册统计
"""

from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, TypedDict

import contextlib
import re

from src.common.logger import get_logger
from src.core.tooling import build_tool_detailed_description

from .hook_spec_registry import HookSpecRegistry

logger = get_logger("plugin_runtime.host.component_registry")


class ComponentRegistrationError(ValueError):
    """组件注册失败异常。"""

    def __init__(
        self,
        message: str,
        *,
        component_name: str = "",
        component_type: str = "",
        plugin_id: str = "",
    ) -> None:
        """初始化组件注册失败异常。

        Args:
            message: 原始错误信息。
            component_name: 组件名称。
            component_type: 组件类型。
            plugin_id: 插件 ID。
        """

        self.component_name = str(component_name or "").strip()
        self.component_type = str(component_type or "").strip()
        self.plugin_id = str(plugin_id or "").strip()
        super().__init__(message)


class ComponentTypes(str, Enum):
    ACTION = "ACTION"
    COMMAND = "COMMAND"
    TOOL = "TOOL"
    EVENT_HANDLER = "EVENT_HANDLER"
    HOOK_HANDLER = "HOOK_HANDLER"
    MESSAGE_GATEWAY = "MESSAGE_GATEWAY"


ComponentChatScope = Literal["all", "group", "private"]


def _normalize_chat_scope(raw_value: Any) -> ComponentChatScope:
    """规范化组件聊天类型适用范围。"""

    normalized_value = str(raw_value or "all").strip().lower()
    if normalized_value == "group":
        return "group"
    if normalized_value == "private":
        return "private"
    return "all"


class StatusDict(TypedDict):
    total: int
    action: int
    command: int
    tool: int
    event_handler: int
    hook_handler: int
    message_gateway: int
    plugins: int


class ComponentEntry:
    """组件条目"""

    __slots__ = (
        "name",
        "full_name",
        "component_type",
        "plugin_id",
        "metadata",
        "enabled",
        "compiled_pattern",
        "disabled_session",
        "chat_scope",
        "allowed_session",
    )

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        self.name: str = name
        self.full_name: str = f"{plugin_id}.{name}"
        self.component_type: ComponentTypes = ComponentTypes(component_type)
        self.plugin_id: str = plugin_id
        self.metadata: Dict[str, Any] = metadata
        self.enabled: bool = metadata.get("enabled", True)
        self.disabled_session: Set[str] = set()
        self.chat_scope: ComponentChatScope = _normalize_chat_scope(chat_scope)
        self.allowed_session: Set[str] = {
            str(session_id).strip()
            for session_id in (allowed_session or [])
            if str(session_id).strip()
        }

class ActionEntry(ComponentEntry):
    """Action 组件条目"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)


class CommandEntry(ComponentEntry):
    """Command 组件条目"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)
        self.aliases: List[str] = metadata.get("aliases", [])
        self.compiled_pattern: Optional[re.Pattern] = None
        if pattern := metadata.get("command_pattern", ""):
            try:
                self.compiled_pattern = re.compile(pattern)
            except (re.error, TypeError) as e:
                logger.warning(f"命令 {self.full_name} 正则编译失败: {e}")


class ToolEntry(ComponentEntry):
    """Tool 组件条目"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        self.description: str = str(metadata.get("description", "") or "").strip()
        self.brief_description: str = str(
            metadata.get("brief_description", self.description) or self.description or f"工具 {name}"
        ).strip()
        self.parameters: List[Dict[str, Any]] = metadata.get("parameters", [])
        self.parameters_raw: Dict[str, Any] | List[Dict[str, Any]] = metadata.get("parameters_raw", {})
        detailed_description = str(metadata.get("detailed_description", "") or "").strip()
        self.detailed_description: str = detailed_description
        self.invoke_method: str = str(metadata.get("invoke_method", "plugin.invoke_tool") or "plugin.invoke_tool").strip()
        self.legacy_component_type: str = str(metadata.get("legacy_component_type", "") or "").strip()
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)

        if not self.detailed_description:
            parameters_schema = self._get_parameters_schema()
            self.detailed_description = build_tool_detailed_description(parameters_schema)

    def _get_parameters_schema(self) -> Dict[str, Any] | None:
        """获取当前工具条目的对象级参数 Schema。

        Returns:
            Dict[str, Any] | None: 归一化后的参数 Schema。
        """

        if isinstance(self.parameters_raw, dict) and self.parameters_raw:
            if self.parameters_raw.get("type") == "object" or "properties" in self.parameters_raw:
                return dict(self.parameters_raw)

            required_names: List[str] = []
            normalized_properties: Dict[str, Any] = {}
            for property_name, property_schema in self.parameters_raw.items():
                if not isinstance(property_schema, dict):
                    continue
                property_schema_copy = dict(property_schema)
                if bool(property_schema_copy.pop("required", False)):
                    required_names.append(str(property_name))
                normalized_properties[str(property_name)] = property_schema_copy

            schema: Dict[str, Any] = {
                "type": "object",
                "properties": normalized_properties,
            }
            if required_names:
                schema["required"] = required_names
            return schema

        if isinstance(self.parameters, list) and self.parameters:
            properties: Dict[str, Any] = {}
            required_names: List[str] = []
            for parameter in self.parameters:
                if not isinstance(parameter, dict):
                    continue
                parameter_name = str(parameter.get("name", "") or "").strip()
                if not parameter_name:
                    continue
                if bool(parameter.get("required", False)):
                    required_names.append(parameter_name)
                properties[parameter_name] = {
                    key: value
                    for key, value in parameter.items()
                    if key not in {"name", "required", "param_type"}
                }
                properties[parameter_name]["type"] = str(
                    parameter.get("type", parameter.get("param_type", "string")) or "string"
                )

            schema = {
                "type": "object",
                "properties": properties,
            }
            if required_names:
                schema["required"] = required_names
            return schema

        return None


class EventHandlerEntry(ComponentEntry):
    """EventHandler 组件条目"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        self.event_type: str = metadata.get("event_type", "")
        self.weight: int = metadata.get("weight", 0)
        self.intercept_message: bool = metadata.get("intercept_message", False)
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)


class HookHandlerEntry(ComponentEntry):
    """HookHandler 组件条目。"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        self.hook: str = self._normalize_hook_name(metadata.get("hook", ""))
        self.mode: str = self._normalize_mode(metadata.get("mode", "blocking"))
        self.order: str = self._normalize_order(metadata.get("order", "normal"))
        self.timeout_ms: int = self._normalize_timeout_ms(metadata.get("timeout_ms", 0))
        self.error_policy: str = self._normalize_error_policy(metadata.get("error_policy", "skip"))
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)

    @staticmethod
    def _normalize_error_policy(raw_value: Any) -> str:
        """规范化 Hook 异常处理策略。

        Args:
            raw_value: 原始异常处理策略值。

        Returns:
            str: 规范化后的异常处理策略。

        Raises:
            ValueError: 当异常处理策略不受支持时抛出。
        """

        normalized_source = getattr(raw_value, "value", raw_value)
        normalized_value = str(normalized_source or "").strip().lower() or "skip"
        if normalized_value not in {"abort", "skip", "log"}:
            raise ValueError(f"HookHandler 异常处理策略不合法: {raw_value}")
        return normalized_value

    @staticmethod
    def _normalize_hook_name(raw_value: Any) -> str:
        """规范化命名 Hook 名称。

        Args:
            raw_value: 原始 Hook 名称。

        Returns:
            str: 去空白后的 Hook 名称。

        Raises:
            ValueError: 当 Hook 名称为空时抛出。
        """

        normalized_source = getattr(raw_value, "value", raw_value)
        if not (normalized_value := str(normalized_source or "").strip()):
            raise ValueError("HookHandler 的 hook 名称不能为空")
        return normalized_value

    @staticmethod
    def _normalize_mode(raw_value: Any) -> str:
        """规范化 Hook 处理模式。

        Args:
            raw_value: 原始模式值。

        Returns:
            str: 规范化后的模式。

        Raises:
            ValueError: 当模式不受支持时抛出。
        """

        normalized_source = getattr(raw_value, "value", raw_value)
        normalized_value = str(normalized_source or "").strip().lower() or "blocking"
        if normalized_value not in {"blocking", "observe"}:
            raise ValueError(f"HookHandler 模式不合法: {raw_value}")
        return normalized_value

    @staticmethod
    def _normalize_order(raw_value: Any) -> str:
        """规范化 Hook 顺序槽位。

        Args:
            raw_value: 原始顺序值。

        Returns:
            str: 规范化后的顺序槽位。

        Raises:
            ValueError: 当顺序值不受支持时抛出。
        """

        normalized_source = getattr(raw_value, "value", raw_value)
        normalized_value = str(normalized_source or "").strip().lower() or "normal"
        if normalized_value not in {"early", "normal", "late"}:
            raise ValueError(f"HookHandler 顺序槽位不合法: {raw_value}")
        return normalized_value

    @staticmethod
    def _normalize_timeout_ms(raw_value: Any) -> int:
        """规范化 Hook 超时配置。

        Args:
            raw_value: 原始超时值。

        Returns:
            int: 规范化后的超时毫秒数。

        Raises:
            ValueError: 当超时值为负数或无法转换为整数时抛出。
        """

        try:
            timeout_ms = int(raw_value or 0)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"HookHandler 超时配置不合法: {raw_value}") from exc
        if timeout_ms < 0:
            raise ValueError(f"HookHandler 超时配置不能为负数: {raw_value}")
        return timeout_ms

    @property
    def is_blocking(self) -> bool:
        """返回当前 Hook 是否为阻塞模式。"""

        return self.mode == "blocking"

    @property
    def is_observe(self) -> bool:
        """返回当前 Hook 是否为观察模式。"""

        return self.mode == "observe"


class MessageGatewayEntry(ComponentEntry):
    """MessageGateway 组件条目"""

    def __init__(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> None:
        self.route_type: str = self._normalize_route_type(metadata.get("route_type", ""))
        self.platform: str = str(metadata.get("platform", "") or "").strip()
        self.protocol: str = str(metadata.get("protocol", "") or "").strip()
        self.account_id: str = str(metadata.get("account_id", "") or "").strip()
        self.scope: str = str(metadata.get("scope", "") or "").strip()
        super().__init__(name, component_type, plugin_id, metadata, chat_scope, allowed_session)

    @staticmethod
    def _normalize_route_type(raw_value: Any) -> str:
        """规范化消息网关路由类型。

        Args:
            raw_value: 原始路由类型值。

        Returns:
            str: 规范化后的路由类型。

        Raises:
            ValueError: 当路由类型不受支持时抛出。
        """

        normalized_value = str(raw_value or "").strip().lower()
        route_type_aliases = {
            "send": "send",
            "receive": "receive",
            "recv": "receive",
            "recive": "receive",
            "duplex": "duplex",
        }
        route_type = route_type_aliases.get(normalized_value)
        if route_type is None:
            raise ValueError(f"MessageGateway 路由类型不合法: {raw_value}")
        return route_type

    @property
    def supports_send(self) -> bool:
        """返回当前网关是否支持出站。"""

        return self.route_type in {"send", "duplex"}

    @property
    def supports_receive(self) -> bool:
        """返回当前网关是否支持入站。"""

        return self.route_type in {"receive", "duplex"}


class ComponentRegistry:
    """Host 侧组件注册表。

    由 Supervisor 在收到 plugin.register_components 时调用。
    供业务层查询可用组件、匹配命令、调度 action/event 等。
    """

    def __init__(self, hook_spec_registry: Optional[HookSpecRegistry] = None) -> None:
        """初始化组件注册表。

        Args:
            hook_spec_registry: 可选的 Hook 规格注册中心；提供后会在注册
                HookHandler 时执行规格校验。
        """

        # 全量索引
        self._components: Dict[str, ComponentEntry] = {}  # full_name -> comp

        # 按类型索引
        self._by_type: Dict[ComponentTypes, Dict[str, ComponentEntry]] = {
            comp_type: {} for comp_type in ComponentTypes
        }  # component_type -> (full_name -> comp)

        # 按插件索引
        self._by_plugin: Dict[str, List[ComponentEntry]] = {}
        self._hook_spec_registry = hook_spec_registry

    @staticmethod
    def _convert_action_metadata_to_tool_metadata(
        name: str,
        metadata: Dict[str, Any],
    ) -> Dict[str, Any]:
        """将旧 Action 元数据转换为统一 Tool 元数据。

        Args:
            name: 组件名称。
            metadata: Action 原始元数据。

        Returns:
            Dict[str, Any]: 转换后的 Tool 元数据。
        """

        action_parameters = metadata.get("action_parameters")
        parameters_schema: Dict[str, Any] | None = None
        if isinstance(action_parameters, dict) and action_parameters:
            properties: Dict[str, Any] = {}
            for parameter_name, parameter_description in action_parameters.items():
                normalized_name = str(parameter_name or "").strip()
                if not normalized_name:
                    continue
                properties[normalized_name] = {
                    "type": "string",
                    "description": str(parameter_description or "").strip() or "兼容旧 Action 参数",
                }
            if properties:
                parameters_schema = {
                    "type": "object",
                    "properties": properties,
                }

        detailed_parts: List[str] = []
        if parameters_schema is not None:
            parameter_description = build_tool_detailed_description(parameters_schema)
            if parameter_description:
                detailed_parts.append(parameter_description)

        action_require = [
            str(item).strip()
            for item in (metadata.get("action_require") or [])
            if str(item).strip()
        ]
        if action_require:
            detailed_parts.append("使用建议：\n" + "\n".join(f"- {item}" for item in action_require))

        associated_types = [
            str(item).strip()
            for item in (metadata.get("associated_types") or [])
            if str(item).strip()
        ]
        if associated_types:
            detailed_parts.append(f"适用消息类型：{'、'.join(associated_types)}。")

        activation_type = str(metadata.get("activation_type", "always") or "always").strip()
        activation_keywords = [
            str(item).strip()
            for item in (metadata.get("activation_keywords") or [])
            if str(item).strip()
        ]
        activation_lines = [f"兼容旧 Action 激活方式：{activation_type}。"]
        if activation_keywords:
            activation_lines.append(f"激活关键词：{'、'.join(activation_keywords)}。")
        if str(metadata.get("action_prompt", "") or "").strip():
            activation_lines.append(f"原始 Action 提示语：{str(metadata['action_prompt']).strip()}。")
        detailed_parts.append("\n".join(activation_lines))

        brief_description = str(metadata.get("brief_description", metadata.get("description", "") or f"工具 {name}")).strip()
        return {
            **metadata,
            "description": brief_description,
            "brief_description": brief_description,
            "detailed_description": "\n\n".join(part for part in detailed_parts if part).strip(),
            "parameters_raw": parameters_schema or {},
            "invoke_method": "plugin.invoke_action",
            "legacy_action": True,
            "legacy_component_type": "ACTION",
        }

    @staticmethod
    def _normalize_component_type(component_type: str) -> ComponentTypes:
        """规范化组件类型输入。

        Args:
            component_type: 原始组件类型字符串。

        Returns:
            ComponentTypes: 规范化后的组件类型枚举。

        Raises:
            ValueError: 当组件类型不受支持时抛出。
        """

        normalized_value = str(component_type or "").strip().upper()
        return ComponentTypes(normalized_value)

    def clear(self) -> None:
        """清空全部组件注册状态。"""
        self._components.clear()
        for type_dict in self._by_type.values():
            type_dict.clear()
        self._by_plugin.clear()

    @staticmethod
    def _is_legacy_action_component(component: ComponentEntry) -> bool:
        """判断组件是否为兼容旧 Action 的 Tool 条目。

        Args:
            component: 待判断的组件条目。

        Returns:
            bool: 是否为兼容旧 Action 组件。
        """

        if not isinstance(component, ToolEntry):
            return False
        return str(component.metadata.get("legacy_component_type", "") or "").strip().upper() == "ACTION"

    def _validate_hook_handler_entry(self, component: HookHandlerEntry) -> None:
        """校验 HookHandler 是否满足已注册的 Hook 规格。

        Args:
            component: 待校验的 HookHandler 条目。

        Raises:
            ComponentRegistrationError: HookHandler 声明不合法时抛出。
        """

        if self._hook_spec_registry is None:
            return

        hook_spec = self._hook_spec_registry.get_hook_spec(component.hook)
        if hook_spec is None:
            raise ComponentRegistrationError(
                f"HookHandler {component.full_name} 声明了未注册的 Hook: {component.hook}",
                component_name=component.name,
                component_type=component.component_type.value,
                plugin_id=component.plugin_id,
            )

        if component.is_blocking and not hook_spec.allow_blocking:
            raise ComponentRegistrationError(
                f"HookHandler {component.full_name} 不能注册为 blocking：Hook {component.hook} 不允许 blocking 处理器",
                component_name=component.name,
                component_type=component.component_type.value,
                plugin_id=component.plugin_id,
            )

        if component.is_observe and not hook_spec.allow_observe:
            raise ComponentRegistrationError(
                f"HookHandler {component.full_name} 不能注册为 observe：Hook {component.hook} 不允许 observe 处理器",
                component_name=component.name,
                component_type=component.component_type.value,
                plugin_id=component.plugin_id,
            )

        if component.error_policy == "abort" and not hook_spec.allow_abort:
            raise ComponentRegistrationError(
                f"HookHandler {component.full_name} 不能使用 error_policy=abort：Hook {component.hook} 不允许 abort",
                component_name=component.name,
                component_type=component.component_type.value,
                plugin_id=component.plugin_id,
            )

    def _build_component_entry(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> ComponentEntry:
        """根据声明构造组件条目。

        Args:
            name: 组件名称。
            component_type: 组件类型。
            plugin_id: 插件 ID。
            metadata: 组件元数据。

        Returns:
            ComponentEntry: 已构造并完成校验的组件条目。

        Raises:
            ComponentRegistrationError: 组件声明不合法时抛出。
        """

        try:
            normalized_type = self._normalize_component_type(component_type)
            normalized_metadata = dict(metadata)
            if normalized_type == ComponentTypes.ACTION:
                normalized_metadata = self._convert_action_metadata_to_tool_metadata(name, normalized_metadata)
                component = ToolEntry(
                    name,
                    ComponentTypes.TOOL.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
            elif normalized_type == ComponentTypes.COMMAND:
                component = CommandEntry(
                    name,
                    normalized_type.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
            elif normalized_type == ComponentTypes.TOOL:
                component = ToolEntry(
                    name,
                    normalized_type.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
            elif normalized_type == ComponentTypes.EVENT_HANDLER:
                component = EventHandlerEntry(
                    name,
                    normalized_type.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
            elif normalized_type == ComponentTypes.HOOK_HANDLER:
                component = HookHandlerEntry(
                    name,
                    normalized_type.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
                self._validate_hook_handler_entry(component)
            elif normalized_type == ComponentTypes.MESSAGE_GATEWAY:
                component = MessageGatewayEntry(
                    name,
                    normalized_type.value,
                    plugin_id,
                    normalized_metadata,
                    chat_scope,
                    allowed_session,
                )
            else:
                raise ComponentRegistrationError(
                    f"组件类型 {component_type} 不存在",
                    component_name=name,
                    component_type=component_type,
                    plugin_id=plugin_id,
                )
        except ComponentRegistrationError:
            raise
        except Exception as exc:
            raise ComponentRegistrationError(
                str(exc),
                component_name=name,
                component_type=component_type,
                plugin_id=plugin_id,
            ) from exc

        return component

    def _remove_existing_component_entry(self, component: ComponentEntry) -> None:
        """移除同名旧组件条目。

        Args:
            component: 即将写入的新组件条目。
        """

        if component.full_name not in self._components:
            return

        logger.warning(f"组件 {component.full_name} 已存在，覆盖")
        old_component = self._components[component.full_name]
        old_list = self._by_plugin.get(old_component.plugin_id)
        if old_list is not None:
            with contextlib.suppress(ValueError):
                old_list.remove(old_component)
        if old_type_dict := self._by_type.get(old_component.component_type):
            old_type_dict.pop(component.full_name, None)

    def _add_component_entry(self, component: ComponentEntry) -> None:
        """写入单个组件条目到全部索引。

        Args:
            component: 待写入的组件条目。
        """

        self._remove_existing_component_entry(component)
        self._components[component.full_name] = component
        self._by_type[component.component_type][component.full_name] = component
        self._by_plugin.setdefault(component.plugin_id, []).append(component)

    # ====== 注册 / 注销 ======
    def register_component(
        self,
        name: str,
        component_type: str,
        plugin_id: str,
        metadata: Dict[str, Any],
        chat_scope: str = "all",
        allowed_session: Optional[List[str]] = None,
    ) -> bool:
        """注册单个组件。

        Args:
            name: 组件名称（不含插件 ID 前缀）。
            component_type: 组件类型（如 ``ACTION``、``COMMAND`` 等）。
            plugin_id: 插件 ID。
            metadata: 组件元数据。

        Returns:
            bool: 注册成功时恒为 ``True``。

        Raises:
            ComponentRegistrationError: 组件声明不合法时抛出。
        """

        component = self._build_component_entry(
            name,
            component_type,
            plugin_id,
            metadata,
            chat_scope,
            allowed_session,
        )
        self._add_component_entry(component)
        return True

    def register_plugin_components(self, plugin_id: str, components: List[Dict[str, Any]]) -> int:
        """批量替换一个插件的组件集合。

        该方法会先完整校验所有组件声明，只有全部通过后才会替换旧组件，
        从而避免插件进入半注册状态。

        Args:
            plugin_id: 插件 ID。
            components: 组件声明字典列表。

        Returns:
            int: 实际注册的组件数量。

        Raises:
            ComponentRegistrationError: 任一组件声明不合法时抛出。
        """

        prepared_components: List[ComponentEntry] = []
        for component_data in components:
            raw_metadata = (
                dict(component_data.get("metadata", {}))
                if isinstance(component_data.get("metadata"), dict)
                else {}
            )
            chat_scope = str(component_data.get("chat_scope", raw_metadata.pop("chat_scope", "all")) or "all")
            raw_allowed_session = component_data.get("allowed_session", raw_metadata.pop("allowed_session", []))
            allowed_session = (
                [str(item).strip() for item in raw_allowed_session if str(item).strip()]
                if isinstance(raw_allowed_session, list)
                else []
            )
            prepared_components.append(
                self._build_component_entry(
                    name=str(component_data.get("name", "") or ""),
                    component_type=str(component_data.get("component_type", "") or ""),
                    plugin_id=plugin_id,
                    metadata=raw_metadata,
                    chat_scope=chat_scope,
                    allowed_session=allowed_session,
                )
            )

        self.remove_components_by_plugin(plugin_id)
        for component in prepared_components:
            self._add_component_entry(component)
        return len(prepared_components)

    def remove_components_by_plugin(self, plugin_id: str) -> int:
        """移除某个插件的所有组件，返回移除数量。

        Args:
            plugin_id (str): 插件id
        Returns:
            count (int): 移除的组件数量
        """
        comps = self._by_plugin.pop(plugin_id, [])
        for comp in comps:
            self._components.pop(comp.full_name, None)
            if type_dict := self._by_type.get(comp.component_type):
                type_dict.pop(comp.full_name, None)
        return len(comps)

    # ====== 启用 / 禁用 ======
    def check_component_enabled(
        self,
        component: ComponentEntry,
        session_id: Optional[str] = None,
        is_group_chat: Optional[bool] = None,
        group_id: Optional[str] = None,
        platform: Optional[str] = None,
    ):
        if session_id and session_id in component.disabled_session:
            return False
        if is_group_chat is not None:
            if component.chat_scope == "group" and is_group_chat is not True:
                return False
            if component.chat_scope == "private" and is_group_chat is not False:
                return False
        if component.allowed_session:
            allowed_candidates = {str(session_id or "").strip(), str(group_id or "").strip()}
            if platform and group_id:
                allowed_candidates.add(f"{platform}:{group_id}")
            allowed_candidates.discard("")
            if component.allowed_session.isdisjoint(allowed_candidates):
                return False
        return component.enabled

    def toggle_component_status(self, full_name: str, enabled: bool, session_id: Optional[str] = None) -> bool:
        """启用或禁用指定组件。

        Args:
            full_name (str): 组件全名
            enabled (bool): 使能情况
            session_id (Optional[str]): 可选的会话ID，仅对该会话禁用（如果提供）
        Returns:
            success (bool): 是否成功设置（失败原因通常是组件不存在）
        """
        comp = self._components.get(full_name)
        if comp is None:
            return False
        if session_id:
            if enabled:
                comp.disabled_session.discard(session_id)
            else:
                comp.disabled_session.add(session_id)
        else:
            comp.enabled = enabled
        return True

    def set_component_enabled(self, full_name: str, enabled: bool, session_id: Optional[str] = None) -> bool:
        """设置指定组件的启用状态。

        Args:
            full_name: 组件全名。
            enabled: 目标启用状态。
            session_id: 可选的会话 ID，仅对该会话生效。

        Returns:
            bool: 是否设置成功。
        """

        return self.toggle_component_status(full_name, enabled, session_id=session_id)

    def toggle_plugin_status(self, plugin_id: str, enabled: bool, session_id: Optional[str] = None) -> int:
        """批量启用或禁用某插件的所有组件。

        Args:
            plugin_id (str): 插件id
            enabled (bool): 使能情况
            session_id (Optional[str]): 可选的会话ID，仅对该会话禁用（如果提供）
        Returns:
            count (int): 成功设置的组件数量（失败原因通常是插件不存在）
        """
        comps = self._by_plugin.get(plugin_id, [])
        for comp in comps:
            if session_id:
                if enabled:
                    comp.disabled_session.discard(session_id)
                else:
                    comp.disabled_session.add(session_id)
            else:
                comp.enabled = enabled
        return len(comps)

    def get_component(self, full_name: str) -> Optional[ComponentEntry]:
        """按全名查询。

        Args:
            full_name (str): 组件全名
        Returns:
            component (Optional[ComponentEntry]): 组件条目，未找到时为 None
        """
        return self._components.get(full_name)

    def get_components_by_type(
        self,
        component_type: str,
        *,
        enabled_only: bool = True,
        session_id: Optional[str] = None,
        is_group_chat: Optional[bool] = None,
        group_id: Optional[str] = None,
        platform: Optional[str] = None,
    ) -> List[ComponentEntry]:
        """按类型查询组件

        Args:
            component_type (str): 组件类型（如 `ACTION`、`COMMAND` 等）
            enabled_only (bool): 是否仅返回启用的组件
            session_id (Optional[str]): 可选的会话ID，若提供则考虑会话禁用状态
        Returns:
            components (List[ComponentEntry]): 组件条目列表
        """
        try:
            comp_type = self._normalize_component_type(component_type)
        except ValueError:
            logger.error(f"组件类型 {component_type} 不存在")
            raise

        if comp_type == ComponentTypes.ACTION:
            action_components = [
                component
                for component in self._by_type.get(ComponentTypes.TOOL, {}).values()
                if self._is_legacy_action_component(component)
            ]
            if enabled_only:
                return [
                    component
                    for component in action_components
                    if self.check_component_enabled(component, session_id, is_group_chat, group_id, platform)
                ]
            return action_components

        type_dict = self._by_type.get(comp_type, {})
        if enabled_only:
            return [
                c
                for c in type_dict.values()
                if self.check_component_enabled(c, session_id, is_group_chat, group_id, platform)
            ]
        return list(type_dict.values())

    def get_components_by_plugin(
        self, plugin_id: str, *, enabled_only: bool = True, session_id: Optional[str] = None
    ) -> List[ComponentEntry]:
        """按插件查询组件。

        Args:
            plugin_id (str): 插件ID
            enabled_only (bool): 是否仅返回启用的组件
            session_id (Optional[str]): 可选的会话ID，若提供则考虑会话禁用状态
        Returns:
            components (List[ComponentEntry]): 组件条目列表
        """
        comps = self._by_plugin.get(plugin_id, [])
        return [c for c in comps if self.check_component_enabled(c, session_id)] if enabled_only else list(comps)

    def find_command_by_text(
        self, text: str, session_id: Optional[str] = None
    ) -> Optional[Tuple[ComponentEntry, Dict[str, Any]]]:
        """通过文本匹配命令正则，返回 (组件, matched_groups) 元组。

        matched_groups 为正则命名捕获组 dict，别名匹配时为空 dict。
        Args:
            text (str): 待匹配文本
            session_id (Optional[str]): 可选的会话ID，若提供则考虑会话禁用状态
        Returns:
            result (Optional[tuple[ComponentEntry, Dict[str, Any]]]): 匹配到的组件及正则捕获组，未找到时为 None
        """
        for comp in self._by_type.get(ComponentTypes.COMMAND, {}).values():
            if not self.check_component_enabled(comp, session_id):
                continue
            if not isinstance(comp, CommandEntry):
                continue
            if comp.compiled_pattern:
                if m := comp.compiled_pattern.search(text):
                    return comp, m.groupdict()
            # 别名匹配
            for alias in comp.aliases:
                if text.startswith(alias):
                    return comp, {}
        return None

    def get_event_handlers(
        self, event_type: str, *, enabled_only: bool = True, session_id: Optional[str] = None
    ) -> List[EventHandlerEntry]:
        """查询指定事件类型的事件处理器组件。

        Args:
            event_type (str): 事件类型
            enabled_only (bool): 是否仅返回启用的组件
            session_id (Optional[str]): 可选的会话ID，若提供则考虑会话禁用状态
        Returns:
            handlers (List[EventHandlerEntry]): 符合条件的 EventHandler 组件列表，按 weight 降序排序
        """
        handlers: List[EventHandlerEntry] = []
        for comp in self._by_type.get(ComponentTypes.EVENT_HANDLER, {}).values():
            if enabled_only and not self.check_component_enabled(comp, session_id):
                continue
            if not isinstance(comp, EventHandlerEntry):
                continue
            if comp.event_type == event_type:
                handlers.append(comp)
        handlers.sort(key=lambda c: c.weight, reverse=True)
        return handlers

    def get_hook_handlers(
        self, hook_name: str, *, enabled_only: bool = True, session_id: Optional[str] = None
    ) -> List[HookHandlerEntry]:
        """获取订阅指定命名 Hook 的全部处理器。

        Args:
            hook_name: 目标 Hook 名称。
            enabled_only: 是否仅返回启用的组件。
            session_id: 可选的会话 ID，若提供则考虑会话禁用状态。

        Returns:
            List[HookHandlerEntry]: 符合条件的 HookHandler 组件列表。
        """
        handlers: List[HookHandlerEntry] = []
        for comp in self._by_type.get(ComponentTypes.HOOK_HANDLER, {}).values():
            if enabled_only and not self.check_component_enabled(comp, session_id):
                continue
            if not isinstance(comp, HookHandlerEntry):
                continue
            if comp.hook == hook_name:
                handlers.append(comp)
        handlers.sort(key=lambda comp: (self._get_hook_mode_rank(comp.mode), self._get_hook_order_rank(comp.order), comp.plugin_id, comp.name))
        return handlers

    @staticmethod
    def _get_hook_mode_rank(mode: str) -> int:
        """返回 Hook 模式的排序权重。

        Args:
            mode: Hook 模式字符串。

        Returns:
            int: 越小表示越靠前。
        """

        return {"blocking": 0, "observe": 1}.get(mode, 99)

    @staticmethod
    def _get_hook_order_rank(order: str) -> int:
        """返回 Hook 顺序槽位的排序权重。

        Args:
            order: Hook 顺序槽位字符串。

        Returns:
            int: 越小表示越靠前。
        """

        return {"early": 0, "normal": 1, "late": 2}.get(order, 99)

    def get_message_gateway(
        self,
        plugin_id: str,
        name: str,
        *,
        enabled_only: bool = True,
        session_id: Optional[str] = None,
    ) -> Optional[MessageGatewayEntry]:
        """按插件和组件名获取单个消息网关。

        Args:
            plugin_id: 插件 ID。
            name: 网关组件名称。
            enabled_only: 是否仅返回启用的组件。
            session_id: 可选的会话 ID。

        Returns:
            Optional[MessageGatewayEntry]: 若存在则返回消息网关条目。
        """

        component = self._components.get(f"{plugin_id}.{name}")
        if not isinstance(component, MessageGatewayEntry):
            return None
        if enabled_only and not self.check_component_enabled(component, session_id):
            return None
        return component

    def get_message_gateways(
        self,
        *,
        plugin_id: Optional[str] = None,
        platform: str = "",
        route_type: str = "",
        enabled_only: bool = True,
        session_id: Optional[str] = None,
    ) -> List[MessageGatewayEntry]:
        """查询消息网关组件列表。

        Args:
            plugin_id: 可选的插件 ID 过滤条件。
            platform: 可选的平台过滤条件。
            route_type: 可选的路由类型过滤条件。
            enabled_only: 是否仅返回启用的组件。
            session_id: 可选的会话 ID。

        Returns:
            List[MessageGatewayEntry]: 符合条件的消息网关组件列表。
        """

        normalized_platform = str(platform or "").strip()
        normalized_route_type = str(route_type or "").strip().lower()
        gateways: List[MessageGatewayEntry] = []
        for comp in self._by_type.get(ComponentTypes.MESSAGE_GATEWAY, {}).values():
            if not isinstance(comp, MessageGatewayEntry):
                continue
            if plugin_id and comp.plugin_id != plugin_id:
                continue
            if enabled_only and not self.check_component_enabled(comp, session_id):
                continue
            if normalized_platform and comp.platform != normalized_platform:
                continue
            if normalized_route_type and comp.route_type != normalized_route_type:
                continue
            gateways.append(comp)
        return gateways

    def get_tools(self, *, enabled_only: bool = True, session_id: Optional[str] = None) -> List[ToolEntry]:
        """查询所有工具组件。

        Args:
            enabled_only (bool): 是否仅返回启用的组件
            session_id (Optional[str]): 可选的会话ID，若提供则考虑会话禁用状态
        Returns:
            tools (List[ToolEntry]): 符合条件的 Tool 组件列表
        """
        tools: List[ToolEntry] = []
        for comp in self._by_type.get(ComponentTypes.TOOL, {}).values():
            if enabled_only and not self.check_component_enabled(comp, session_id):
                continue
            if isinstance(comp, ToolEntry):
                tools.append(comp)
        return tools

    def get_tools_for_llm(self, *, enabled_only: bool = True, session_id: Optional[str] = None) -> List[Dict[str, Any]]:
        """兼容旧接口，返回可供 LLM 使用的工具条目列表。

        Args:
            enabled_only: 是否仅返回启用的组件。
            session_id: 可选的会话 ID，若提供则考虑会话禁用状态。

        Returns:
            List[Dict[str, Any]]: 兼容旧结构的工具组件字典列表。
        """

        return [
            {
                "name": tool.full_name,
                "description": tool.description,
                "parameters": (
                    dict(tool.parameters_raw)
                    if isinstance(tool.parameters_raw, dict) and tool.parameters_raw
                    else tool._get_parameters_schema() or {}
                ),
                "parameters_raw": tool.parameters_raw,
                "enabled": tool.enabled,
                "plugin_id": tool.plugin_id,
            }
            for tool in self.get_tools(enabled_only=enabled_only, session_id=session_id)
            if not self._is_legacy_action_component(tool)
        ]

    # ====== 统计信息 ======
    def get_stats(self) -> StatusDict:
        """获取注册统计。

        Returns:
            stats (StatusDict): 组件统计信息，包括总数、各类型数量、插件数量等
        """
        return StatusDict(
            total=len(self._components),
            action=len(
                [
                    component
                    for component in self._by_type.get(ComponentTypes.TOOL, {}).values()
                    if self._is_legacy_action_component(component)
                ]
            ),
            command=len(self._by_type[ComponentTypes.COMMAND]),
            tool=len(
                [
                    component
                    for component in self._by_type.get(ComponentTypes.TOOL, {}).values()
                    if not self._is_legacy_action_component(component)
                ]
            ),
            event_handler=len(self._by_type[ComponentTypes.EVENT_HANDLER]),
            hook_handler=len(self._by_type[ComponentTypes.HOOK_HANDLER]),
            message_gateway=len(self._by_type[ComponentTypes.MESSAGE_GATEWAY]),
            plugins=len(self._by_plugin),
        )
