from django.forms import Media
from django.utils.functional import cached_property

from .base import Panel


class PanelGroup(Panel):
    """
    Abstract class for panels that manage a set of sub-panels.
    Concrete subclasses must attach a 'children' property
    """

    def __init__(self, children=(), *args, **kwargs):
        permission = kwargs.pop("permission", None)
        super().__init__(*args, **kwargs)
        self.children = children
        self.permission = permission

    def clone_kwargs(self):
        kwargs = super().clone_kwargs()
        kwargs["children"] = self.children
        kwargs["permission"] = self.permission
        return kwargs

    def get_form_options(self):
        if self.model is None:
            raise AttributeError(
                "%s is not bound to a model yet. Use `.bind_to_model(model)` "
                "before using this method." % self.__class__.__name__
            )

        options = {}

        # Merge in form options from each child in turn, combining values that are types that we
        # know how to combine (i.e. lists, dicts and sets)
        for child in self.children:
            child_options = child.get_form_options()
            for key, new_val in child_options.items():
                if key not in options:
                    # if val is a known mutable container type that we're going to merge subsequent
                    # child values into, create a copy so that we don't risk that change leaking
                    # back into the child's internal state
                    if (
                        isinstance(new_val, list)
                        or isinstance(new_val, dict)
                        or isinstance(new_val, set)
                    ):
                        options[key] = new_val.copy()
                    else:
                        options[key] = new_val
                else:
                    current_val = options[key]
                    if isinstance(current_val, list) and isinstance(
                        new_val, (list, tuple)
                    ):
                        current_val.extend(new_val)
                    elif isinstance(current_val, tuple) and isinstance(
                        new_val, (list, tuple)
                    ):
                        options[key] = list(current_val).extend(new_val)
                    elif isinstance(current_val, dict) and isinstance(new_val, dict):
                        current_val.update(new_val)
                    elif isinstance(current_val, set) and isinstance(new_val, set):
                        current_val.update(new_val)
                    else:
                        raise ValueError(
                            "Don't know how to merge values %r and %r for form option %r"
                            % (current_val, new_val, key)
                        )

        return options

    def on_model_bound(self):
        self.children = [child.bind_to_model(self.model) for child in self.children]

    @cached_property
    def child_identifiers(self):
        """
        A list of identifiers corresponding to child panels in ``self.children``, formed from the clean_name property
        but validated to be unique and non-empty.
        """
        used_names = set()
        result = []
        for panel in self.children:
            base_name = panel.clean_name or "panel"
            candidate_name = base_name
            suffix = 0
            while candidate_name in used_names:
                suffix += 1
                candidate_name = "%s%d" % (base_name, suffix)

            result.append(candidate_name)
            used_names.add(candidate_name)

        return result

    class BoundPanel(Panel.BoundPanel):
        @cached_property
        def children(self):
            return [
                child.get_bound_panel(
                    instance=self.instance,
                    request=self.request,
                    form=self.form,
                    prefix=(f"{self.prefix}-child-{identifier}"),
                )
                for child, identifier in zip(
                    self.panel.children, self.panel.child_identifiers
                )
            ]

        @cached_property
        def visible_children(self):
            return [child for child in self.children if child.is_shown()]

        @cached_property
        def visible_children_with_identifiers(self):
            return [
                (child, identifier)
                for child, identifier in zip(
                    self.children, self.panel.child_identifiers
                )
                if child.is_shown()
            ]

        def show_panel_furniture(self):
            return any(child.show_panel_furniture() for child in self.children)

        def is_shown(self):
            """
            Check permissions on the panel group overall then check if any children
            are shown.
            """

            if self.panel.permission:
                if not self.request.user.has_perm(self.panel.permission):
                    return False

            return any(child.is_shown() for child in self.children)

        @property
        def media(self):
            media = Media()
            for item in self.visible_children:
                media += item.media
            return media

        def get_comparison(self):
            comparators = []

            for child in self.children:
                comparators.extend(child.get_comparison())

            return comparators


class TabbedInterface(PanelGroup):
    class BoundPanel(PanelGroup.BoundPanel):
        template_name = "wagtailadmin/panels/tabbed_interface.html"


class ObjectList(PanelGroup):
    class BoundPanel(PanelGroup.BoundPanel):
        template_name = "wagtailadmin/panels/object_list.html"


class FieldRowPanel(PanelGroup):
    class BoundPanel(PanelGroup.BoundPanel):
        template_name = "wagtailadmin/panels/field_row_panel.html"


class MultiFieldPanel(PanelGroup):
    class BoundPanel(PanelGroup.BoundPanel):
        template_name = "wagtailadmin/panels/multi_field_panel.html"
