"""Extension point catalog for all 30 spec-defined extension points.

Provides :func:`register_all_extension_points` which registers every
extension point defined in ``docs/specification.md`` into the
:class:`PluginManager`.

Each extension point has a name, description, Protocol type, and
category derived from its dotted name prefix.

Based on ``docs/specification.md`` Extension Points Summary and
issue #939.

ISSUES CLOSED: #939
"""

from __future__ import annotations

from typing import Any, Protocol

from cleveragents.infrastructure.plugins.extension_protocols import (
    A2AExtensionMethodExtension,
    A2ATransportExtension,
    ConfigSourceExtension,
    ConfigValidatorExtension,
    ContextPipelineComponentExtension,
    ContextStorageBackendExtension,
    ContextStrategyExtension,
    EventFilterExtension,
    EventHandlerExtension,
    OutputFormatExtension,
    OutputMaterializerExtension,
    OutputRendererExtension,
    ResourceResolverExtension,
    ResourceTypeHandlerExtension,
    SafetyGuardrailExtension,
    SkillProviderExtension,
    SkillTemplateExtension,
    ToolMiddlewareExtension,
    ToolProviderExtension,
    ValidationRuleProviderExtension,
    ValidationRunnerExtension,
)
from cleveragents.infrastructure.plugins.types import ExtensionPoint

# ---------------------------------------------------------------------------
# Structural protocol so the catalog is not tightly coupled to
# PluginManager — any object with ``register_extension_point`` works.
# ---------------------------------------------------------------------------


class ExtensionPointRegistrar(Protocol):
    """Structural type for :func:`register_all_extension_points`.

    Any object with a ``register_extension_point`` method satisfies
    this protocol (e.g. :class:`PluginManager`).
    """

    def register_extension_point(self, extension_point: ExtensionPoint) -> None:
        """Register a single extension point."""
        ...


# ---------------------------------------------------------------------------
# Extension point definitions
# ---------------------------------------------------------------------------


class _ExtensionPointDef:
    """Internal descriptor for building ExtensionPoint instances."""

    __slots__ = ("description", "name", "protocol_type")

    def __init__(self, name: str, protocol_type: type[Any], description: str) -> None:
        self.name = name
        self.protocol_type = protocol_type
        self.description = description


def _category_from_name(name: str) -> str:
    """Extract category prefix from a dotted extension point name.

    Examples::

        >>> _category_from_name("context.strategy")
        'context'
        >>> _category_from_name("context.pipeline_component.scorer")
        'context'
    """
    return name.split(".")[0]


# The authoritative list of all 30 spec-defined extension points.
_EXTENSION_POINT_DEFS: tuple[_ExtensionPointDef, ...] = (
    # --- Context (12) ---
    _ExtensionPointDef(
        "context.strategy",
        ContextStrategyExtension,
        "Pluggable context assembly strategy",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.strategy_selector",
        ContextPipelineComponentExtension,
        "Selects which context strategies to invoke",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.budget_allocator",
        ContextPipelineComponentExtension,
        "Distributes token budget across strategies",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.strategy_executor",
        ContextPipelineComponentExtension,
        "Executes selected strategies in parallel",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.deduplicator",
        ContextPipelineComponentExtension,
        "Removes duplicate context fragments",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.depth_resolver",
        ContextPipelineComponentExtension,
        "Resolves detail depth levels for fragments",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.scorer",
        ContextPipelineComponentExtension,
        "Scores context fragments by relevance",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.packer",
        ContextPipelineComponentExtension,
        "Packs scored fragments within token budget",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.orderer",
        ContextPipelineComponentExtension,
        "Orders packed fragments for presentation",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.preamble_generator",
        ContextPipelineComponentExtension,
        "Generates preamble text for assembled context",
    ),
    _ExtensionPointDef(
        "context.pipeline_component.skeleton_compressor",
        ContextPipelineComponentExtension,
        "Compresses code skeletons to save tokens",
    ),
    _ExtensionPointDef(
        "context.storage_backend",
        ContextStorageBackendExtension,
        "Pluggable storage backend for context data",
    ),
    # --- Output (3) ---
    _ExtensionPointDef(
        "output.renderer",
        OutputRendererExtension,
        "Renders assembled output to a specific format",
    ),
    _ExtensionPointDef(
        "output.materializer",
        OutputMaterializerExtension,
        "Writes rendered output to a destination",
    ),
    _ExtensionPointDef(
        "output.format",
        OutputFormatExtension,
        "Custom output serialization format",
    ),
    # --- Validation (2) ---
    _ExtensionPointDef(
        "validation.runner",
        ValidationRunnerExtension,
        "Pluggable validation execution engine",
    ),
    _ExtensionPointDef(
        "validation.rule_provider",
        ValidationRuleProviderExtension,
        "Provides validation rules from external sources",
    ),
    # --- Tools (2) ---
    _ExtensionPointDef(
        "tool.provider",
        ToolProviderExtension,
        "Registers external tools for agent use",
    ),
    _ExtensionPointDef(
        "tool.middleware",
        ToolMiddlewareExtension,
        "Intercepts tool invocations for cross-cutting concerns",
    ),
    # --- Skills (2) ---
    _ExtensionPointDef(
        "skill.provider",
        SkillProviderExtension,
        "Provides skill definitions from external sources",
    ),
    _ExtensionPointDef(
        "skill.template",
        SkillTemplateExtension,
        "Custom skill template renderer",
    ),
    # --- Resources (2) ---
    _ExtensionPointDef(
        "resource.resolver",
        ResourceResolverExtension,
        "Resolves resource URIs to content",
    ),
    _ExtensionPointDef(
        "resource.type_handler",
        ResourceTypeHandlerExtension,
        "Handles loading of specific resource types",
    ),
    # --- A2A (2) ---
    _ExtensionPointDef(
        "a2a.transport",
        A2ATransportExtension,
        "Agent-to-agent communication transport layer",
    ),
    _ExtensionPointDef(
        "a2a.extension_method",
        A2AExtensionMethodExtension,
        "Custom A2A protocol extension method",
    ),
    # --- Events (2) ---
    _ExtensionPointDef(
        "event.handler",
        EventHandlerExtension,
        "Handles domain events from the event bus",
    ),
    _ExtensionPointDef(
        "event.filter",
        EventFilterExtension,
        "Filters events before propagation",
    ),
    # --- Config (2) ---
    _ExtensionPointDef(
        "config.source",
        ConfigSourceExtension,
        "External configuration source provider",
    ),
    _ExtensionPointDef(
        "config.validator",
        ConfigValidatorExtension,
        "Validates configuration values against rules",
    ),
    # --- Safety (1) ---
    _ExtensionPointDef(
        "safety.guardrail",
        SafetyGuardrailExtension,
        "Safety guardrail for agent action inspection",
    ),
)

# Public constant: number of spec-defined extension points.
TOTAL_EXTENSION_POINTS: int = 30


def get_extension_point_definitions() -> tuple[ExtensionPoint, ...]:
    """Return all 30 spec-defined extension points as ExtensionPoint models.

    Each entry includes the point's name, Protocol type, description,
    and a ``registry_key`` equal to its category prefix.
    """
    return tuple(
        ExtensionPoint(
            name=defn.name,
            protocol_type=defn.protocol_type,
            description=defn.description,
            registry_key=_category_from_name(defn.name),
        )
        for defn in _EXTENSION_POINT_DEFS
    )


def get_extension_points_by_category() -> dict[str, list[ExtensionPoint]]:
    """Return extension points grouped by category.

    Categories are derived from the first dotted segment of each
    extension point name (e.g. ``"context"``, ``"output"``).
    """
    grouped: dict[str, list[ExtensionPoint]] = {}
    for ep in get_extension_point_definitions():
        category = _category_from_name(ep.name)
        grouped.setdefault(category, []).append(ep)
    return grouped


def register_all_extension_points(manager: ExtensionPointRegistrar) -> int:
    """Register all 30 spec-defined extension points on *manager*.

    Args:
        manager: Object with a ``register_extension_point`` method
            (typically :class:`PluginManager`).

    Returns:
        The number of extension points registered.
    """
    count = 0
    for ep in get_extension_point_definitions():
        manager.register_extension_point(ep)
        count += 1
    return count
