"""AI provider interface for code generation.

This module defines the protocol that all AI providers must implement.
Following the Dependency Inversion Principle (ADR-001), this interface
is defined in the domain layer and implemented in the infrastructure layer.
"""

from collections.abc import Callable, Iterator
from typing import Any, Protocol

from pydantic import BaseModel, ConfigDict, Field

from cleveragents.domain.models.core import Change, Context, Plan, Project


class ProviderResponse(BaseModel):
    """Response from an AI provider.

    Attributes:
        changes: List of generated changes
        model_used: Name of the model that generated the response
        token_count: Number of tokens used in the request
        error_message: Error message if generation failed
    """

    changes: list[Change]
    model_used: str
    token_count: int
    error_message: str | None = None

    model_config = ConfigDict(validate_assignment=True)


class ActorInvocationContext(BaseModel):
    """Context about the selected actor passed to provider executions."""

    name: str
    provider: str
    model: str
    options: dict[str, Any] = Field(default_factory=dict)
    graph_descriptor: dict[str, Any] | None = None
    unsafe: bool = False
    config_hash: str | None = None
    config_blob: dict[str, Any] = Field(default_factory=dict)
    initial_context: dict[str, Any] = Field(default_factory=dict)

    model_config = ConfigDict(validate_assignment=True)


class AIProviderInterface(Protocol):
    """Protocol for AI providers that generate code changes.

    This protocol defines the contract that all AI providers must implement.
    Implementations can be OpenAI, Anthropic, local models, or mock providers
    for testing.
    """

    def generate_changes(
        self,
        project: Project,
        plan: Plan,
        contexts: list[Context],
        actor_context: ActorInvocationContext | None = None,
        progress_callback: Callable[[int], None] | None = None,
    ) -> ProviderResponse:
        """Generate code changes based on the plan and context.

        Args:
            project: The project being modified
            plan: The plan containing instructions
            contexts: List of context files to consider
            actor_context: Metadata and options for the selected actor
            progress_callback: Optional callback for progress updates (0-100)

        Returns:
            ProviderResponse containing generated changes and metadata

        Raises:
            ProviderError: If generation fails
        """
        ...

    def stream_changes(
        self,
        project: Project,
        plan: Plan,
        contexts: list[Context],
        actor_context: ActorInvocationContext | None = None,
        progress_callback: Callable[[int], None] | None = None,
    ) -> Iterator[dict[str, object]]:
        """Stream workflow events while generating changes.

        Implementations should yield dictionaries keyed by workflow node name
        (e.g., ``{"load_context": {...}}``) and finish with a ``"__end__"``
        event containing a ``ProviderResponse`` payload under the ``response``
        key. Errors should raise exceptions so callers can surface them.

        Args:
            project: The project being modified
            plan: The plan containing instructions
            contexts: List of context files to consider
            actor_context: Metadata and options for the selected actor
            progress_callback: Optional callback for progress updates (0-100)
        """
        ...

    @property
    def name(self) -> str:
        """Get the provider name."""
        ...

    @property
    def model_id(self) -> str:
        """Get the model identifier."""
        ...
