"""Prompt template registry — safe substitution, no Jinja, no eval."""

from __future__ import annotations

import re
import string
from dataclasses import dataclass

_IDENT = re.compile(r"^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$")


class PromptError(ValueError):
    """Raised on prompt registration or rendering errors."""


@dataclass(frozen=True)
class PromptTemplate:
    key: str
    template: str
    description: str = ""
    variables: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        if not _IDENT.match(self.key):
            raise PromptError(f"invalid prompt key {self.key!r}")
        # Discover variables and store the deduplicated tuple.
        declared = set(self.variables)
        found = {
            name
            for _, name, _, _ in string.Formatter().parse(self.template)
            if name
        }
        if declared and not declared.issuperset(found):
            missing = found - declared
            raise PromptError(
                f"prompt {self.key!r}: template references undeclared variables {sorted(missing)}",
            )
        object.__setattr__(self, "variables", tuple(sorted(found)))

    def render(self, **kwargs: object) -> str:
        missing = [v for v in self.variables if v not in kwargs]
        if missing:
            raise PromptError(
                f"prompt {self.key!r}: missing variables {missing}",
            )
        try:
            return self.template.format(**kwargs)
        except (KeyError, IndexError) as exc:
            raise PromptError(
                f"prompt {self.key!r}: render failed ({exc})",
            ) from exc


_PROMPTS: dict[str, PromptTemplate] = {}


def register_prompt(prompt: PromptTemplate) -> PromptTemplate:
    existing = _PROMPTS.get(prompt.key)
    if existing is not None and existing != prompt:
        raise PromptError(
            f"prompt {prompt.key!r} already registered with different content",
        )
    _PROMPTS[prompt.key] = prompt
    return prompt


def get_prompt(key: str) -> PromptTemplate:
    try:
        return _PROMPTS[key]
    except KeyError as exc:
        raise PromptError(f"unknown prompt {key!r}") from exc


def list_prompts() -> list[PromptTemplate]:
    return sorted(_PROMPTS.values(), key=lambda p: p.key)


def reset_for_tests() -> None:
    _PROMPTS.clear()
