"""
Base class to of the template renderer.

Fastlife comes with two rendering engines:
{class}`fastlife.adapters.jinjax.renderer.JinjaxEngine`,
and {class}`fastlife.adapters.xcomponent.renderer.XRendererFactory`,
they have to be installed using python packaging extra syntax for your tool,
fastlifeweb[jinjax] or fastlifeweb[xcomponent] since those engine are not
installed by default.

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

import abc
from collections.abc import Mapping
from typing import Any

from markupsafe import Markup
from pydantic.fields import FieldInfo

from fastlife import Request, Response
from fastlife.adapters.fastapi.form import FormModel
from fastlife.domain.model.template import InlineTemplate


class AbstractTemplateRenderer(abc.ABC):
    """
    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, request: Request) -> None:
        self.request = request
        self.globals: dict[str, Any] = {}

    @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(
        self,
        template: str,
        *,
        status_code: int = 200,
        content_type: str = "text/html",
        globals: Mapping[str, Any] | None = None,
        params: InlineTemplate,
    ) -> Response:
        """
        Render the template and build the HTTP Response.
        """
        request = self.request
        if globals:
            self.globals.update(globals)
        data = self.render_template(params)
        resp = Response(
            data, status_code=status_code, headers={"Content-Type": content_type}
        )
        resp.set_cookie(
            request.csrf_token.name,
            request.csrf_token.value,
            httponly=True,
            secure=request.url.scheme == "https",
            samesite="strict",
            max_age=60 * 15,
        )
        return resp

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

        :param template: the template to render.
        :return: The template rendering result.
        """

    @abc.abstractmethod
    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.
        """

    @abc.abstractmethod
    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.
        """


class AbstractTemplateRendererFactory(abc.ABC):
    """
    The template render factory.
    """

    @abc.abstractmethod
    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.
        """
