from django.forms import Media
from typing import Any, Dict, Optional
from django.forms import Widget

from . import models


class TagsEditor(Widget):
    """
    Custom widget that provides an alternative editor for tags provided by the
    taggit library.

    The widget makes use of bootstrap v4 and its badge/pill feature and renders
    a list of tags as badges that can be clicked to remove or add a tag to
    the list of set tags. In addition, a user can dynamically add new, custom
    tags, using a text editor.
    """

    class Media:
        js = ("babybuddy/js/tags_editor.js",)

    input_type = "hidden"
    template_name = "core/widget_tag_editor.html"

    @staticmethod
    def __unpack_tag(tag: models.Tag):
        """
        Tiny utility function used to translate a tag to a serializable
        dictionary of strings.
        """
        return {"name": tag.name, "color": tag.color}

    def format_value(self, value: Any) -> Optional[str]:
        """
        Override format_value to provide a list of dictionaries rather than
        a flat, comma-separated list of tags. This allows for the more
        complex rendering of tags provided by this plugin.
        """
        if value is not None and not isinstance(value, str):
            value = [self.__unpack_tag(tag) for tag in value]
        return value

    def build_attrs(self, base_attrs, extra_attrs=None):
        """
        Bootstrap integration adds form-control to the classes of the widget.
        This works only for "plain" input-based widgets however. In addition,
        we need to add a custom class "babybuddy-tags-editor" for the javascript
        file to detect the widget and take control of its contents.
        """
        attrs = super().build_attrs(base_attrs, extra_attrs)
        class_string = attrs.get("class", "")
        class_string = class_string.replace("form-control", "")
        attrs["class"] = class_string + " babybuddy-tags-editor"
        return attrs

    def get_context(self, name: str, value: Any, attrs) -> Dict[str, Any]:
        """
        Adds extra information to the payload provided to the widget's template.

        Specifically:
        - Query a list if "recently used" tags (max 256 to not cause
          DoS issues) from the database to be used for auto-completion. ("most")
        - Query a smaller list of 5 tags to be made available from a quick
          selection widget ("quick").
        """
        most_tags = models.Tag.objects.order_by("-last_used").all()[:256]

        result = super().get_context(name, value, attrs)

        tag_names = set(
            x["name"] for x in (result.get("widget", {}).get("value", None) or [])
        )
        quick_suggestion_tags = [t for t in most_tags if t.name not in tag_names][:5]

        result["widget"]["tag_suggestions"] = {
            "quick": [
                self.__unpack_tag(t)
                for t in quick_suggestion_tags
                if t.name not in tag_names
            ],
            "most": [self.__unpack_tag(t) for t in most_tags],
        }
        return result