from functools import cached_property
from plone import api
from plone.app.vocabularies.catalog import CatalogSource
from plone.app.z3cform.interfaces import IContentBrowserWidget
from plone.app.z3cform.widgets.contentbrowser import ContentBrowserWidget
from plone.restapi.serializer.utils import get_portal_type_title
from ploneintranet.core import _
from ploneintranet.layout.interfaces import IPloneintranetFormLayer
from Products.Five import BrowserView
from z3c.form.interfaces import IFieldWidget
from z3c.form.widget import FieldWidget
from zope.component import adapter
from zope.interface import implementer
from zope.interface import implementer_only
from zope.schema.interfaces import IChoice

import logging

logger = logging.getLogger(__name__)


class IPIContentBrowserWidget(IContentBrowserWidget):
    """Marker interface for a custom content browser widget"""


@implementer_only(IPIContentBrowserWidget)
class PIContentBrowserWidget(ContentBrowserWidget):
    # These are options you can set on the widget.  For example:
    # directives.widget("fieldname", PIContentBrowserFieldWidget, allow_up=True)

    # Allow going up to a higher level in the site.
    # I think for safety we do not want to allow this.
    allow_up = False

    @cached_property
    def widget_url(self):
        """Get the URL of the widget that uses this panel."""
        current_url = self.request.getURL().partition("++widget++")[0]
        return f"{current_url}/++widget++{self.name}"

    @cached_property
    def panel_url(self):
        """Get the URL of the panel that uses this view."""
        return f"{self.widget_url}/@@panel_miller_columns"

    @property
    def query(self):
        if self.field is None:
            # Not yet initialized.
            return {}
        return self.field.source.query or {}


@implementer(IFieldWidget)
@adapter(IChoice, CatalogSource, IPloneintranetFormLayer)
def PIContentBrowserFieldWidget(field, request, extra=None):
    if extra is not None:
        request = extra
    return FieldWidget(field, PIContentBrowserWidget(request))


class PanelMillerColumns(BrowserView):
    """Panel for selecting content.

    This can be for copying, for adding a related item, for whatever.

    You browse the site, select an item for previewing so you see details,
    and then you choose it for copying/adding/etc.

    This must be called with a widget as context.

    In one miller column we always show all folderish items within a context,
    plus all items that are selectable.  What is selectable is usually
    defined in the field that uses our widget, something like this:

    directives.widget("sample", PIContentBrowserFieldWidget)
    sample = schema.Choice(
        title=_("Sample"),
        source=CatalogSource(portal_type="Document"),
        required=False,
    )
    """

    title = _("Select content from another container.")
    description = _("Choose content from another container.")
    button_title = _("Select")

    @cached_property
    def actual_context(self):
        """Return the actual content item."""
        return self.context.form.getContent()

    @cached_property
    def up_path(self):
        """Return the path to go up one level."""
        if not self.context.allow_up:
            return ""
        path = (
            self.request.form.get("path")
            or self.request.form.get("preview")
            or self.start_path
        )
        if not path:
            return ""
        return path.rpartition("/")[0]

    @cached_property
    def start_path(self):
        """Return the path to start searching.

        The standard contentbrowser seems to have a reasonable default.
        """
        pattern_options = self.context.get_pattern_options()
        return pattern_options.get("basePath")

    @cached_property
    def source_object(self):
        """Get the currently selected source object.

        This can be a folder where we browse for other items.
        Or it can be an item that we have selected for previewing.
        """
        path = (
            self.request.form.get("path")
            or self.request.form.get("preview")
            or self.start_path
        )
        if path:
            return api.content.get(path=path)
        return self.actual_context

    def type_title(self, portal_type):
        """Map the portal_type to a user friendly title."""
        return get_portal_type_title(portal_type)

    @cached_property
    def preview(self):
        """Get info on the item that is selected for preview."""
        preview = self.request.form.get("preview")
        if not preview:
            return
        # Note: this may be None if nothing exists on the path.
        return api.content.get(path=preview)

    @cached_property
    def preview_uid(self):
        """Get the UID of the item that is selected for preview."""
        return self.preview.UID if self.preview else None

    def portal_type_to_icon(self, portal_type):
        """Map the portal_type to an icon.

        In the page template we then use `type-{icon}` as an html class.
        """
        return portal_type.rpartition(".")[-1].replace(" ", "-")

    @cached_property
    def items(self):
        """List of items to show in one Miller column.

        These are all browsable content items directly within the source object.
        We return a list of tuples: (brain, selectable yes/no).
        """
        # First get all folderish items.
        folders = api.content.find(
            context=self.source_object, is_folderish=True, depth=1
        )

        # Then get all selectable items.  The results may overlap with the folders.
        query = {
            "context": self.source_object,
            "depth": 1,
        }
        # Get the search parameters from the widget.
        query.update(self.context.query)
        selectable = api.content.find(**query)
        selectable_uids = {brain.UID for brain in selectable}

        # First get the folderish items in the results.
        # Include a boolean saying if they are selectable or not.
        results = []
        for folder in folders:
            folder_selectable = folder.UID in selectable_uids
            if folder_selectable:
                # Remove the uid, so we can avoid duplication when including
                # the rest of the selectable items later.
                selectable_uids.remove(folder.UID)
            results.append((folder, folder_selectable))

        # If any selectable items are left, include them.
        if selectable_uids:
            for item in selectable:
                if item.UID not in selectable_uids:
                    # This item was already included in the folders.
                    continue
                results.append((item, True))

        return results
