"""
The ``extra_views.formsets`` provides a simple way to handle formsets.
The ``extra_views.advanced`` provides a method to combine that with a create/update form.

This package provides classes that support both options for polymorphic formsets.
"""

from __future__ import annotations

from typing import Any, cast

import extra_views  # type: ignore[import-untyped]
from django.core.exceptions import ImproperlyConfigured
from django.forms import BaseFormSet

from polymorphic.formsets import (
    BasePolymorphicInlineFormSet,
    BasePolymorphicModelFormSet,
    PolymorphicFormSetChild,
    polymorphic_child_forms_factory,
)

__all__ = (
    "PolymorphicFormSetView",
    "PolymorphicInlineFormSetView",
    "PolymorphicInlineFormSet",
)


class PolymorphicFormSetMixin:
    """
    Internal Mixin, that provides polymorphic integration with the ``extra_views`` package.
    """

    formset_class: type[BaseFormSet] = BasePolymorphicModelFormSet

    #: Default 0 extra forms
    factory_kwargs: dict[str, Any] = {"extra": 0}

    #: Define the children
    # :type: list[PolymorphicFormSetChild]
    formset_children: list[PolymorphicFormSetChild] | None = None

    def get_formset_children(self) -> list[PolymorphicFormSetChild]:
        """
        :rtype: list[PolymorphicFormSetChild]
        """
        if not self.formset_children:
            raise ImproperlyConfigured(
                "Define 'formset_children' as list of `PolymorphicFormSetChild`"
            )
        return self.formset_children

    def get_formset_child_kwargs(self) -> dict[str, Any]:
        return {}

    def get_formset(self) -> type[BaseFormSet]:
        """
        Returns the formset class from the inline formset factory
        """
        # Implementation detail:
        # Since `polymorphic_modelformset_factory` and `polymorphic_inlineformset_factory` mainly
        # reuse the standard factories, and then add `child_forms`, the same can be done here.
        # This makes sure the base class construction is completely honored.
        FormSet = super().get_formset()  # type: ignore[misc]
        FormSet.child_forms = polymorphic_child_forms_factory(
            self.get_formset_children(), **self.get_formset_child_kwargs()
        )
        return cast(type[BaseFormSet], FormSet)


class PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetView):
    """
    A view that displays a single polymorphic formset.

    .. code-block:: python

        from polymorphic.formsets import PolymorphicFormSetChild


        class ItemsView(PolymorphicFormSetView):
            model = Item
            formset_children = [
                PolymorphicFormSetChild(ItemSubclass1),
                PolymorphicFormSetChild(ItemSubclass2),
            ]

    """

    formset_class: type[BasePolymorphicModelFormSet] = BasePolymorphicModelFormSet


class PolymorphicInlineFormSetView(PolymorphicFormSetMixin, extra_views.InlineFormSetView):
    """
    A view that displays a single polymorphic formset - with one parent object.
    This is a variation of the :mod:`extra_views` package classes for django-polymorphic.

    .. code-block:: python

        from polymorphic.formsets import PolymorphicFormSetChild


        class OrderItemsView(PolymorphicInlineFormSetView):
            model = Order
            inline_model = Item
            formset_children = [
                PolymorphicFormSetChild(ItemSubclass1),
                PolymorphicFormSetChild(ItemSubclass2),
            ]
    """

    formset_class: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet


class PolymorphicInlineFormSet(PolymorphicFormSetMixin, extra_views.InlineFormSetFactory):
    """
    An inline to add to the ``inlines`` of
    the :class:`~extra_views.advanced.CreateWithInlinesView`
    and :class:`~extra_views.advanced.UpdateWithInlinesView` class.

    .. code-block:: python

        from polymorphic.formsets import PolymorphicFormSetChild


        class ItemsInline(PolymorphicInlineFormSet):
            model = Item
            formset_children = [
                PolymorphicFormSetChild(ItemSubclass1),
                PolymorphicFormSetChild(ItemSubclass2),
            ]


        class OrderCreateView(CreateWithInlinesView):
            model = Order
            inlines = [ItemsInline]

            def get_success_url(self):
                return self.object.get_absolute_url()

    """

    formset_class: type[BasePolymorphicInlineFormSet] = BasePolymorphicInlineFormSet
