"""
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 collections.abc import Mapping
from typing import Any

from markupsafe import Markup
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from xcomponent import Catalog

from fastlife import Request, XTemplate
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.service.templates import (
    AbstractTemplateRenderer,
    AbstractTemplateRendererFactory,
)
from fastlife.settings import Settings
from fastlife.shared_utils.resolver import resolve


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

    request: Request
    """Associated request that needs a response."""
    ns_catalog: dict[str, Catalog]

    def __init__(
        self, globals: dict[str, Any], request: Request, ns_catalog: dict[str, Catalog]
    ) -> None:
        self.request = request
        self.ns_catalog = ns_catalog
        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.
        """
        prefix = name or self.request.registry.settings.form_data_model_prefix
        form_data: Mapping[str, Any] = {}
        if issubclass(model, BaseModel):
            form_data = FormModel[Any].default(prefix, model).form_data
        return (
            WidgetFactory(self, token)
            .get_widget(
                model,
                form_data=form_data,
                form_errors={},
                prefix=prefix,
                removable=removable,
                field=field,
            )
            .to_html(self)
        )

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

        :param template: the template to render.
        :return: The template rendering result.
        """
        params = {
            k: getattr(template, k) for k in template.__class__.model_fields.keys()
        }
        return self.ns_catalog[template.namespace].render(
            template.template,
            globals=self.globals,
            **params,
        )


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

    def __init__(self, settings: "Settings", ns_catalog: dict[str, Catalog]) -> None:
        self.globals = resolve(settings.xcomponent_global_catalog_class)().model_dump()
        self.ns_catalog = ns_catalog

    def __call__(self, request: Request) -> AbstractTemplateRenderer[XTemplate]:
        """
        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,
            ns_catalog=self.ns_catalog,
        )


@configure
def includeme(conf: Configurator) -> None:
    conf.include(".functions")
    conf.include(".html")
    conf.include(".icons")
    conf.include(".pydantic_form")
    conf.add_renderer(
        XRendererFactory(
            conf.registry.settings,
            conf.build_catalogs(),
        ),
    )
