from django.urls import reverse
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy, ngettext

from wagtail import hooks
from wagtail.admin.ui.components import Component
from wagtail.admin.userbar import AccessibilityItem
from wagtail.models import DraftStateMixin, LockableMixin, Page, ReferenceIndex


class BaseSidePanel(Component):
    class SidePanelToggle(Component):
        template_name = "wagtailadmin/shared/side_panel_toggle.html"
        aria_label = ""
        icon_name = ""
        has_counter = True
        counter_classname = ""
        keyboard_shortcut = None

        def __init__(self, panel):
            self.panel = panel

        def get_context_data(self, parent_context):
            # Inherit classes from fragments defined in slim_header.html
            inherit = {
                "nav_icon_button_classes",
                "nav_icon_classes",
                "nav_icon_counter_classes",
            }
            context = {key: parent_context.get(key) for key in inherit}
            context["toggle"] = self
            context["panel"] = self.panel
            context["count"] = 0
            return context

    def __init__(self, object, request):
        self.object = object
        self.request = request
        self.model = type(self.object)
        self.toggle = self.SidePanelToggle(panel=self)

    def get_context_data(self, parent_context):
        context = {"panel": self, "object": self.object, "request": self.request}
        if issubclass(self.model, Page):
            context["page"] = self.object
        return context


class StatusSidePanel(BaseSidePanel):
    class SidePanelToggle(BaseSidePanel.SidePanelToggle):
        aria_label = gettext_lazy("Toggle status")
        icon_name = "info-circle"
        counter_classname = "w-bg-critical-200"

        def get_context_data(self, parent_context):
            context = super().get_context_data(parent_context)
            form = parent_context.get("form")
            context["count"] = form and len(
                form.errors.keys() & {"go_live_at", "expire_at"}
            )
            return context

    name = "status"
    title = gettext_lazy("Status")
    template_name = "wagtailadmin/shared/side_panels/status.html"
    order = 100

    def __init__(
        self,
        *args,
        show_schedule_publishing_toggle=None,
        live_object=None,
        scheduled_object=None,
        locale=None,
        translations=None,
        usage_url=None,
        history_url=None,
        last_updated_info=None,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)
        self.show_schedule_publishing_toggle = show_schedule_publishing_toggle
        self.live_object = live_object
        self.scheduled_object = scheduled_object
        self.locale = locale
        self.translations = translations
        self.usage_url = usage_url
        self.history_url = history_url
        self.last_updated_info = last_updated_info
        self.locking_enabled = isinstance(self.object, LockableMixin)

    def get_status_templates(self, context):
        templates = ["wagtailadmin/shared/side_panels/includes/status/workflow.html"]

        if self.locale:
            templates.append(
                "wagtailadmin/shared/side_panels/includes/status/locale.html"
            )

        if self.object.pk:
            if self.locking_enabled:
                templates.append(
                    "wagtailadmin/shared/side_panels/includes/status/locked.html"
                )

            if self.usage_url:
                templates.append(
                    "wagtailadmin/shared/side_panels/includes/status/usage.html"
                )

        return templates

    def get_scheduled_publishing_context(self, parent_context):
        if not isinstance(self.object, DraftStateMixin):
            return {"draftstate_enabled": False}

        context = {
            # Used for hiding the info completely if the model doesn't extend DraftStateMixin
            "draftstate_enabled": True,
            # Show error message if any of the scheduled publishing fields has errors
            "schedule_has_errors": False,
            # The dialog toggle can be hidden (e.g. if PublishingPanel is not present)
            # but the scheduled publishing info should still be shown
            "show_schedule_publishing_toggle": self.show_schedule_publishing_toggle,
            # These are the dates that show up with the unticked calendar icon,
            # aka "draft schedule"
            "draft_go_live_at": None,
            "draft_expire_at": None,
            # These are the dates that show up with the ticked calendar icon,
            # aka "active schedule"
            "scheduled_go_live_at": None,
            "scheduled_expire_at": None,
            # This is for an edge case where the live object already has an
            # expire_at, which can still take effect if the active schedule's
            # go_live_at is later than that
            "live_expire_at": None,
        }

        # Reuse logic from the toggle to get the count of errors
        if self.toggle.get_context_data(parent_context)["count"]:
            context["schedule_has_errors"] = True

        # Only consider draft schedule if the object hasn't been created
        # or if there are unpublished changes
        if not self.object.pk or self.object.has_unpublished_changes:
            context["draft_go_live_at"] = self.object.go_live_at
            context["draft_expire_at"] = self.object.expire_at

        # Get active schedule from the scheduled revision's object (if any)
        if self.scheduled_object:
            context["scheduled_go_live_at"] = self.scheduled_object.go_live_at
            context["scheduled_expire_at"] = self.scheduled_object.expire_at

            # Ignore draft schedule if it's the same as the active schedule
            if context["draft_go_live_at"] == context["scheduled_go_live_at"]:
                context["draft_go_live_at"] = None

            if context["draft_expire_at"] == context["scheduled_expire_at"]:
                context["draft_expire_at"] = None

        # The live object can still have its own active expiry date
        # that's separate from the active schedule
        if (
            self.live_object
            and self.live_object.expire_at
            and not self.live_object.expired
        ):
            context["live_expire_at"] = self.live_object.expire_at

            # Ignore the live object's expiry date if the active schedule has
            # an earlier go_live_at, as the active schedule's expiry date will
            # override the live object's expiry date when the draft is published
            if (
                context["scheduled_go_live_at"]
                and context["scheduled_go_live_at"] < context["live_expire_at"]
            ):
                context["live_expire_at"] = None

        # Only show the box for the live object expire_at edge case
        # if it passes the checks above
        context["has_live_publishing_schedule"] = bool(context["live_expire_at"])

        # Only show the main scheduled publishing box if it has at least one of
        # the draft/active schedule dates after passing the checks above
        context["has_draft_publishing_schedule"] = any(
            (
                context["scheduled_go_live_at"],
                context["scheduled_expire_at"],
                context["draft_go_live_at"],
                context["draft_expire_at"],
            )
        )

        return context

    def get_lock_context(self, parent_context):
        self.lock = None
        lock_context = {}
        if self.locking_enabled:
            self.lock = self.object.get_lock()
            if self.lock:
                lock_context = self.lock.get_context_for_user(
                    self.request.user, parent_context
                )
        return {
            "lock": self.lock,
            "user_can_lock": parent_context.get("user_can_lock"),
            "user_can_unlock": parent_context.get("user_can_unlock"),
            "lock_context": lock_context,
        }

    def get_usage_context(self):
        return {
            "usage_count": ReferenceIndex.get_grouped_references_to(
                self.object
            ).count(),
            "usage_url": self.usage_url,
        }

    def get_context_data(self, parent_context):
        context = super().get_context_data(parent_context)
        context["locale"] = self.locale
        context["translations"] = self.translations
        if self.translations:
            context["translations_total"] = len(self.translations) + 1
        context["model_name"] = capfirst(self.model._meta.verbose_name)
        context["base_model_name"] = context["model_name"]
        context["history_url"] = self.history_url
        context["status_templates"] = self.get_status_templates(context)
        context["last_updated_info"] = self.last_updated_info
        context.update(self.get_scheduled_publishing_context(parent_context))
        context.update(self.get_lock_context(parent_context))
        if self.object.pk and self.usage_url:
            context.update(self.get_usage_context())
        return context


class PageStatusSidePanel(StatusSidePanel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.object.pk:
            self.usage_url = reverse("wagtailadmin_pages:usage", args=(self.object.pk,))

            permissions = self.object.permissions_for_user(self.request.user)
            if permissions.can_view_revisions():
                self.history_url = reverse(
                    "wagtailadmin_pages:history",
                    args=(self.object.pk,),
                )

    def get_status_templates(self, context):
        templates = super().get_status_templates(context)
        templates.insert(
            -1, "wagtailadmin/shared/side_panels/includes/status/privacy.html"
        )
        return templates

    def get_usage_context(self):
        context = super().get_usage_context()
        context["usage_url_text"] = ngettext(
            "Referenced %(count)s time",
            "Referenced %(count)s times",
            context["usage_count"],
        ) % {"count": context["usage_count"]}
        return context

    def get_context_data(self, parent_context):
        context = super().get_context_data(parent_context)
        page = self.object

        if page.id:
            context.update(
                {
                    "workflow_history_url": reverse(
                        "wagtailadmin_pages:workflow_history", args=(page.id,)
                    ),
                    "revisions_compare_url_name": "wagtailadmin_pages:revisions_compare",
                    "lock_url": reverse("wagtailadmin_pages:lock", args=(page.id,)),
                    "unlock_url": reverse("wagtailadmin_pages:unlock", args=(page.id,)),
                }
            )

        context.update(
            {
                "model_name": self.model.get_verbose_name(),
                "base_model_name": Page._meta.verbose_name,
                "model_description": self.model.get_page_description(),
                "status_templates": self.get_status_templates(context),
            }
        )

        return context


class CommentsSidePanel(BaseSidePanel):
    class SidePanelToggle(BaseSidePanel.SidePanelToggle):
        aria_label = gettext_lazy("Toggle comments")
        icon_name = "comment"

    name = "comments"
    title = gettext_lazy("Comments")
    template_name = "wagtailadmin/shared/side_panels/comments.html"
    order = 300

    def get_context_data(self, parent_context):
        context = super().get_context_data(parent_context)
        context["form"] = parent_context.get("form")
        return context


class ChecksSidePanel(BaseSidePanel):
    class SidePanelToggle(BaseSidePanel.SidePanelToggle):
        aria_label = gettext_lazy("Toggle checks")
        icon_name = "glasses"

    name = "checks"
    title = gettext_lazy("Checks")
    template_name = "wagtailadmin/shared/side_panels/checks.html"
    order = 350

    def get_axe_configuration(self):
        # Retrieve the Axe configuration from the userbar.
        userbar_items = [AccessibilityItem()]
        for fn in hooks.get_hooks("construct_wagtail_userbar"):
            fn(self.request, userbar_items)

        for item in userbar_items:
            if isinstance(item, AccessibilityItem):
                return item.get_axe_configuration(self.request)

    def get_context_data(self, parent_context):
        context = super().get_context_data(parent_context)
        context["axe_configuration"] = self.get_axe_configuration()
        return context


class PreviewSidePanel(BaseSidePanel):
    class SidePanelToggle(BaseSidePanel.SidePanelToggle):
        aria_label = gettext_lazy("Toggle preview")
        icon_name = "mobile-alt"
        has_counter = False
        keyboard_shortcut = "mod+p"

    name = "preview"
    title = gettext_lazy("Preview")
    template_name = "wagtailadmin/shared/side_panels/preview.html"
    order = 400

    def __init__(self, object, request, *, preview_url):
        super().__init__(object, request)
        self.preview_url = preview_url

    def get_context_data(self, parent_context):
        context = super().get_context_data(parent_context)
        context["preview_url"] = self.preview_url
        context["has_multiple_modes"] = len(self.object.preview_modes) > 1
        return context
