"""
Template renderer based on XComponent.

More template engine can be registered using the configurator method
{meth}`add_renderer <fastlife.config.configurator.GenericConfigurator.add_renderer>`
"""

from typing import Any

from markupsafe import Markup
from pydantic.fields import FieldInfo

from fastlife import Request
from fastlife.adapters.xcomponent.pydantic_form.widget_factory.factory import (
    WidgetFactory,
)
from fastlife.config import Configurator, configure
from fastlife.domain.model.form import FormModel
from fastlife.domain.model.template import InlineTemplate
from fastlife.service.templates import (
    AbstractTemplateRenderer,
    AbstractTemplateRendererFactory,
)
from fastlife.settings import Settings
from fastlife.shared_utils.resolver import resolve

from .catalog import catalog


class XTemplateRenderer(AbstractTemplateRenderer):
    """
    An object that will be initialized by an AbstractTemplateRendererFactory,
    passing the request to process.
    """

    request: Request
    """Associated request that needs a response."""

    def __init__(self, globals: dict[str, Any], request: Request) -> None:
        self.request = request
        self.globals: dict[str, Any] = {**globals}
        self.globals["pydantic_form"] = self.pydantic_form

    def pydantic_form(
        self, model: FormModel[Any], *, token: str | None = None
    ) -> Markup:
        """
        Generate HTML markup to build a form from the given form model.

        :param model: the form model that will be transformed to markup.
        :param token: a token used to ensure that unique identifier are unique.
        :return: HTML Markup generated by composign fields widgets.
        """
        return WidgetFactory(self, token).get_markup(model)

    def pydantic_form_field(
        self,
        model: type[Any],
        *,
        name: str | None,
        token: str | None,
        removable: bool,
        field: FieldInfo | None,
    ) -> Markup:
        """
        Generate HTML for a particular field in a form.

        This function is used to generate union subtypes in Ajax requests.
        :param model: a pydantic or python builtin type that is requests to be rendered
        :param name: name for the field
        :param token: the token of the form to render unique identifiers
        :param removable: add a way let the user remove the widget
        :param field: only render this particular field for the model.
        :return: HTML Markup.
        """
        return (
            WidgetFactory(self, token)
            .get_widget(
                model,
                form_data={},
                form_errors={},
                prefix=(name or self.request.registry.settings.form_data_model_prefix),
                removable=removable,
                field=field,
            )
            .to_html(self)
        )

    @property
    def route_prefix(self) -> str:
        """Used to buid pydantic form widget that do ajax requests."""
        return self.request.registry.settings.fastlife_route_prefix

    def render_template(self, template: InlineTemplate) -> str:
        """
        Render an inline template.

        :param template: the template to render.
        :return: The template rendering result.
        """
        params = template.model_dump()
        return catalog.render(
            template.template,
            globals=self.globals,
            **params,
        )


class XRendererFactory(AbstractTemplateRendererFactory):
    """
    The template render factory.
    """

    def __init__(self, settings: "Settings") -> None:
        self.globals = resolve(settings.jinjax_global_catalog_class)().model_dump()

    def __call__(self, request: Request) -> AbstractTemplateRenderer:
        """
        While processing an HTTP Request, a renderer object is created giving
        isolated context per request.

        :param Request: the HTTP Request to process.
        :return: The renderer object that will process that request.
        """
        return XTemplateRenderer(globals=self.globals, request=request)


@configure
def includeme(conf: Configurator) -> None:
    conf.add_renderer("xcomponent", XRendererFactory(conf.registry.settings))
