import logging
from typing import Any, Protocol, runtime_checkable

from jinja2 import Environment, FileSystemLoader, select_autoescape

logger = logging.getLogger(__name__)


@runtime_checkable
class TemplateRenderer(Protocol):
    """Protocol for template rendering.

    Any object with a compatible ``render`` method satisfies this protocol,
    enabling static type checkers to validate callers without coupling them
    to the concrete :class:`TemplateEngine` implementation.
    """

    def render(self, template_str: str, context: dict[str, Any]) -> str: ...


class TemplateEngine:
    """
    Engine for rendering Jinja2 templates in workflows.
    """

    def __init__(self, template_dirs: list[str] | None = None):
        if template_dirs:
            loader = FileSystemLoader(template_dirs)
        else:
            loader = None

        self.env = Environment(
            loader=loader,
            # Disable autoescape for inline templates (default_for_string=False)
            # We generate markdown, not HTML - escaping breaks apostrophes etc.
            autoescape=select_autoescape(["html", "xml"], default_for_string=False),
            trim_blocks=True,
            lstrip_blocks=True,
        )

    def render(self, template_str: str, context: dict[str, Any]) -> str:
        """
        Render a template string with the given context.
        """
        try:
            template = self.env.from_string(template_str)
            return str(template.render(**context))
        except Exception as e:
            logger.error(f"Error rendering template: {e}", exc_info=True)
            # Fallback to original string or raise?
            # For workflows, it might be better to fail typically, but let's return error message in string for visibility if strict validation isn't on.
            # actually, better to raise so the action fails and handles it.
            raise

    def render_file(self, template_name: str, context: dict[str, Any]) -> str:
        """
        Render a template file with the given context.
        """
        try:
            template = self.env.get_template(template_name)
            return str(template.render(**context))
        except Exception as e:
            logger.error(f"Error rendering template file '{template_name}': {e}", exc_info=True)
            raise
