"""
Template models for Prompt Dependency Resolution.

Defines PromptTemplate and ContentMacro models for template-based
prompt generation with dependency resolution.
"""

from dataclasses import dataclass, field
from typing import Dict, Any, List, Optional
from enum import Enum

from markitect.prompts.models import Artifact, ArtifactType, ArtifactMetadata


class MacroKind(Enum):
    """Types of content macros supported in templates."""
    REQUIRED = "required"  # Must be resolved, fail if missing
    OPTIONAL = "optional"  # Resolve if available, empty if missing
    GENERATE = "generate"  # Trigger generator template execution


@dataclass
class ContentMacro:
    """
    Content macro extracted from a template.

    Implements FR-2.3: ContentMacro kinds (Required, Optional, Generate)

    Syntax: {{<kind>:<target>[|<param1>=<value1>|<param2>=<value2>...]}}

    Examples:
        {{require:glossary}}
        {{optional:technical-constraints}}
        {{generate:code-examples|language=python|framework=fastapi}}

    Attributes:
        kind: Type of macro (required, optional, generate)
        target: Name of artifact or template to resolve
        parameters: Optional parameters for macro resolution
        raw_text: Original macro text from template
        line_number: Line number where macro appears (for error reporting)
    """
    kind: MacroKind
    target: str
    parameters: Dict[str, str] = field(default_factory=dict)
    raw_text: str = ""
    line_number: int = 0

    def __str__(self) -> str:
        """String representation of macro."""
        params = ''.join(f"|{k}={v}" for k, v in self.parameters.items())
        return f"{{{{{self.kind.value}:{self.target}{params}}}}}"

    def to_dict(self) -> Dict[str, Any]:
        """Convert macro to dictionary for serialization."""
        return {
            "kind": self.kind.value,
            "target": self.target,
            "parameters": self.parameters,
            "raw_text": self.raw_text,
            "line_number": self.line_number,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ContentMacro":
        """Create macro from dictionary."""
        return cls(
            kind=MacroKind(data["kind"]),
            target=data["target"],
            parameters=data.get("parameters", {}),
            raw_text=data.get("raw_text", ""),
            line_number=data.get("line_number", 0),
        )


@dataclass
class TemplateMetadata:
    """
    Extended metadata specific to prompt templates.

    Attributes:
        purpose: Description of what the template generates
        model_hints: Suggested model parameters
        expected_inputs: Documentation of expected required/optional inputs
        output_type: Expected type of generated content
    """
    purpose: Optional[str] = None
    model_hints: Dict[str, Any] = field(default_factory=dict)
    expected_inputs: List[str] = field(default_factory=list)
    output_type: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            "purpose": self.purpose,
            "model_hints": self.model_hints,
            "expected_inputs": self.expected_inputs,
            "output_type": self.output_type,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "TemplateMetadata":
        """Create from dictionary."""
        return cls(
            purpose=data.get("purpose"),
            model_hints=data.get("model_hints", {}),
            expected_inputs=data.get("expected_inputs", []),
            output_type=data.get("output_type"),
        )


@dataclass
class PromptTemplate:
    """
    Prompt template with content macros.

    Extends Artifact with template-specific functionality.
    Implements FR-2: PromptTemplate Definition

    A PromptTemplate is an Artifact of type TEMPLATE containing ContentMacros
    that reference other artifacts or trigger nested generation.

    Attributes:
        artifact: Underlying artifact
        template_metadata: Template-specific metadata
        macros: Extracted content macros
        analyzed: Whether template has been analyzed
    """
    artifact: Artifact
    template_metadata: TemplateMetadata = field(default_factory=TemplateMetadata)
    macros: List[ContentMacro] = field(default_factory=list)
    analyzed: bool = False

    @classmethod
    def create(
        cls,
        space_id: str,
        name: str,
        content: str,
        artifact_metadata: Optional[ArtifactMetadata] = None,
        template_metadata: Optional[TemplateMetadata] = None,
    ) -> "PromptTemplate":
        """
        Create a new template.

        Args:
            space_id: ID of containing space
            name: Template name
            content: Template content with macros
            artifact_metadata: General artifact metadata
            template_metadata: Template-specific metadata

        Returns:
            New PromptTemplate instance
        """
        artifact = Artifact.create(
            space_id=space_id,
            name=name,
            content=content,
            artifact_type=ArtifactType.TEMPLATE,
            metadata=artifact_metadata,
        )

        return cls(
            artifact=artifact,
            template_metadata=template_metadata or TemplateMetadata(),
            macros=[],
            analyzed=False,
        )

    @classmethod
    def from_artifact(cls, artifact: Artifact) -> "PromptTemplate":
        """
        Create template from existing artifact.

        Args:
            artifact: Artifact to wrap

        Returns:
            PromptTemplate instance

        Raises:
            ValueError: If artifact is not of type TEMPLATE
        """
        if artifact.artifact_type != ArtifactType.TEMPLATE:
            raise ValueError(
                f"Artifact must be of type TEMPLATE, got {artifact.artifact_type}"
            )

        return cls(
            artifact=artifact,
            template_metadata=TemplateMetadata(),
            macros=[],
            analyzed=False,
        )

    @property
    def id(self) -> str:
        """Get template ID."""
        return self.artifact.id

    @property
    def space_id(self) -> str:
        """Get space ID."""
        return self.artifact.space_id

    @property
    def name(self) -> str:
        """Get template name."""
        return self.artifact.name

    @property
    def content_digest(self) -> str:
        """Get content digest."""
        return self.artifact.content_digest

    def get_required_dependencies(self) -> List[str]:
        """
        Get list of required artifact names.

        Returns:
            List of artifact names from required macros
        """
        return [
            macro.target
            for macro in self.macros
            if macro.kind == MacroKind.REQUIRED
        ]

    def get_optional_dependencies(self) -> List[str]:
        """
        Get list of optional artifact names.

        Returns:
            List of artifact names from optional macros
        """
        return [
            macro.target
            for macro in self.macros
            if macro.kind == MacroKind.OPTIONAL
        ]

    def get_generators(self) -> List[str]:
        """
        Get list of generator template names.

        Returns:
            List of template names from generate macros
        """
        return [
            macro.target
            for macro in self.macros
            if macro.kind == MacroKind.GENERATE
        ]

    def to_dict(self) -> Dict[str, Any]:
        """Convert template to dictionary for serialization."""
        return {
            "artifact": self.artifact.to_dict(),
            "template_metadata": self.template_metadata.to_dict(),
            "macros": [macro.to_dict() for macro in self.macros],
            "analyzed": self.analyzed,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "PromptTemplate":
        """Create template from dictionary."""
        return cls(
            artifact=Artifact.from_dict(data["artifact"]),
            template_metadata=TemplateMetadata.from_dict(
                data.get("template_metadata", {})
            ),
            macros=[ContentMacro.from_dict(m) for m in data.get("macros", [])],
            analyzed=data.get("analyzed", False),
        )
