from plone import api
from plone import schema  # includes zope.schema
from plone.app.event.dx.behaviors import default_timezone
from plone.app.vocabularies.catalog import CatalogSource
from plone.app.z3cform.widgets.datetime import DateFieldWidget
from plone.app.z3cform.widgets.datetime import DatetimeFieldWidget
from plone.app.z3cform.widgets.select import AjaxSelectFieldWidget
from plone.autoform import directives
from plone.autoform.view import WidgetsView
from plone.namedfile.field import NamedBlobFile
from plone.namedfile.field import NamedBlobImage
from plone.supermodel import model
from ploneintranet import schema as pischema
from ploneintranet.core import _
from ploneintranet.layout.widgets.checkbox import PIMinimalCheckBoxFieldWidget
from ploneintranet.layout.widgets.form import PIEditForm
from ploneintranet.layout.widgets.miller_columns import PIContentBrowserFieldWidget
from ploneintranet.layout.widgets.select import PISelectFromOptgroupsFieldWidget
from ploneintranet.userprofile.vocabularies import PrincipalSource
from z3c.form import form
from z3c.form.browser.checkbox import CheckBoxFieldWidget
from z3c.form.browser.radio import RadioFieldWidget
from zope.interface import Invalid
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary


def invalid_when_triggered(value):
    """This validator is always invalid when the form is submitted with the
    "Submit and error" button.
    """
    try:
        api.portal.get()
    except api.exc.CannotGetPortalError:
        # Prevents the validator to kick in when the instance is starting
        return True

    request = api.portal.getRequest()
    if request is None:
        return True

    if request.form.get("form.buttons.submit_and_error"):
        raise Invalid(f"Forced server-side error: {value!r}")

    return True


def _optgroups_vocabulary_factory():
    """Sample vocabulary for which some terms
    are marked to belong to the same optgroup.
    """
    default_terms = [SimpleTerm("Campari"), SimpleTerm("Aperol")]
    main_terms = [SimpleTerm("Pasta"), SimpleTerm("Pizza")]

    # Disable Aperol because I prefer Campari
    default_terms[1].disabled = True

    for term in main_terms:
        term.optgroup = _("Main dishes")

    beverage_terms = [SimpleTerm("Beer"), SimpleTerm("Wine")]
    for term in beverage_terms:
        term.optgroup = _("Beverages")
    terms = default_terms + main_terms + beverage_terms

    return SimpleVocabulary(terms)


class IComponentLibrarySchema(model.Schema):
    boolean = schema.Bool(
        title=_("Boolean"),
        required=False,
        description=_("Boolean"),
        constraint=invalid_when_triggered,
    )

    boolean_extra_accessible = pischema.PIBool(
        title=_("Boolean, extra accessible"),
        required=False,
        label_content=_("On"),
        description=_("Boolean with description"),
        constraint=invalid_when_triggered,
    )

    boolean_extra_long = pischema.PIBool(
        title=_("Boolean, voluminous languages"),
        required=False,
        label_content=_("Enabled"),
        default=True,
        description=_("Boolean with long description"),
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "boolean_minimal",
        PIMinimalCheckBoxFieldWidget,
    )
    boolean_minimal = pischema.PIBool(
        title=_("Boolean, minimal"),
        required=False,
        label_content=_("Hide my email address from other users"),
        description=_(
            "Minimal Boolean with inline label. "
            "Leave title empty to show only label_content."
        ),
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "checklist_radio",
        RadioFieldWidget,
    )
    checklist_radio = schema.Choice(
        title=_("Checklist, type radio"),
        description=_("Checklist radio description"),
        required=False,
        values=("English", "Français", "Nederlands", "עברית"),
        default="Nederlands",
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "checklist_checkbox",
        CheckBoxFieldWidget,
    )
    checklist_checkbox = schema.List(
        title=_("Checklist, type checkbox"),
        description=_("Checklist checkbox description"),
        required=False,
        value_type=schema.Choice(
            values=("Deutsch", "English", "Español", "Nederlands")
        ),
        default=["English", "Nederlands"],
        constraint=invalid_when_triggered,
    )

    text_input = schema.TextLine(
        title=_("Text input"),
        description=_("Help text"),
        required=True,
        constraint=invalid_when_triggered,
    )

    textarea = schema.Text(
        title=_("Text area"),
        description=_("Text area description"),
        required=False,
        constraint=invalid_when_triggered,
    )

    select = schema.Choice(
        title=_("Select"),
        description=_("Select description"),
        vocabulary=SimpleVocabulary(
            [
                SimpleTerm(value=name, token=name, title=name)
                for name in ("English", "Français", "Nederlands")
            ]
        ),
        required=False,
        default="English",
        constraint=invalid_when_triggered,
    )

    directives.widget("select_from_optgroups", PISelectFromOptgroupsFieldWidget)
    select_from_optgroups = schema.Choice(
        title=_("Select with optgroups"),
        description=_("Select with optgroups description"),
        vocabulary=_optgroups_vocabulary_factory(),
        required=False,
    )

    directives.widget(
        "auto_suggest_single",
        AjaxSelectFieldWidget,
        placeholder=_("Select a user or a group"),
    )
    directives.read_permission(auto_suggest_single="cmf.ModifyPortalContent")
    directives.write_permission(auto_suggest_single="cmf.ModifyPortalContent")
    auto_suggest_single = schema.Choice(
        title=_("Auto-suggest single"),
        description=_("Auto-suggest single description"),
        source=PrincipalSource(),
        required=False,
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "auto_suggest_multiple",
        AjaxSelectFieldWidget,
        placeholder=_("Select users or groups"),
    )
    directives.read_permission(auto_suggest_multiple="cmf.ModifyPortalContent")
    directives.write_permission(auto_suggest_multiple="cmf.ModifyPortalContent")
    auto_suggest_multiple = schema.List(
        title=_("Auto-suggest multiple"),
        description=_("Auto-suggest multiple description"),
        value_type=schema.Choice(
            source=PrincipalSource(),
        ),
        required=False,
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "auto_suggest_with_value",
        AjaxSelectFieldWidget,
    )
    directives.write_permission(auto_suggest_with_value="zope2.View")
    auto_suggest_with_value = schema.List(
        title=_("Auto-suggest with value"),
        description=_("Auto-suggest with value"),
        value_type=schema.Choice(
            vocabulary=SimpleVocabulary(
                [
                    SimpleTerm(value=name, token=name, title=name)
                    for name in ("Antwerp", "Maastricht", "Rotterdam")
                ]
            )
        ),
        default=["Antwerp"],
        required=False,
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "auto_suggest_with_adding",
        AjaxSelectFieldWidget,
        vocabulary="ploneintranet.layout.icons",
        resultsonly=True,
    )
    directives.write_permission(auto_suggest_with_adding="zope2.View")
    auto_suggest_with_adding = schema.List(
        title=_("Auto-suggest with adding"),
        description=_("Auto-suggest with adding"),
        # Note to self: with 'value_type=schema.Choice' we can't add new terms:
        # validation will fail because the term is not in the vocabulary.
        # The same is true for dynamic vocabularies that use the catalog.
        # So we need schema.TextLine.
        value_type=schema.TextLine(),
        default=["heart"],
        required=False,
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "date_and_time",
        DatetimeFieldWidget,
        default_timezone=default_timezone,
    )
    date_and_time = schema.Datetime(
        title=_("Date and time"),
        description=_("Date and time description"),
        required=False,
        constraint=invalid_when_triggered,
    )

    directives.widget(
        "date_picker",
        DateFieldWidget,
        default_timezone=default_timezone,
    )
    date_picker = schema.Date(
        title=_("Date picker"),
        description=_("Date picker description"),
        required=False,
        constraint=invalid_when_triggered,
    )

    email = pischema.PIEmail(
        title=_("Email"),
        description=_("Help text"),
        required=False,
        constraint=invalid_when_triggered,
    )

    phone = pischema.Telephone(
        title=_("Phone number"),
        description=_("Help text"),
        required=False,
        constraint=invalid_when_triggered,
    )

    website = schema.URI(
        title=_("Website URL"),
        description=_("Help text"),
        required=False,
        constraint=invalid_when_triggered,
    )

    primary_location = pischema.GooglePlace(
        title=_("Primary location"),
        vocabulary=SimpleVocabulary(
            [
                SimpleTerm(value=name, token=name, title=name)
                for name in ("Amsterdam", "Berlin", "London", "New York", "Paris")
            ]
        ),
        description=_("Help text"),
        required=False,
        default="Berlin",
        constraint=invalid_when_triggered,
    )

    file = NamedBlobFile(
        title=_("File"),
        description=_("File description"),
        required=False,
        constraint=invalid_when_triggered,
    )

    image = NamedBlobImage(
        title=_("Image"),
        description=_("Image description"),
        required=False,
        constraint=invalid_when_triggered,
    )

    images = pischema.MultipleFileInput(
        title=_("Multiple images"),
        description=_("Multiple images description"),
        required=False,
        accept=["image/png", "image/jpeg", "image/svg+xml"],
        constraint=invalid_when_triggered,
        value_type=NamedBlobImage(
            title="Image",
            required=False,
        ),
    )

    directives.widget(
        "source",
        PIContentBrowserFieldWidget,
        allow_up=True,
    )
    source = schema.Choice(
        title=_("Source"),
        description=_("Source description"),
        source=CatalogSource(is_folderish=False),
        required=False,
    )

    # Undocumented here: RelationList and RelationChoice
    # see ploneintranet news: carousel_slides


class ComponentLibraryEditView(PIEditForm):
    ignoreContext = True
    label = "Form components"
    description = "Component library view"
    schema = IComponentLibrarySchema

    # The default fieldset header for the forms is an empty string
    default_fieldset_header = "Basic"

    @property
    def button_classes(self):
        """If you have an attribute or a property called button_classes,
        you will be able to add additional classes to the action buttons.

        The default_button_classes attribute is a dictionary that
        covers the 'save' and 'cancel' buttons.
        """
        classes = super().button_classes
        classes.update(
            {
                "submit_and_error": "pat-button icon-megaphone float-after",
            }
        )

    @form.button.buttonAndHandler(_("Save"), name="save")
    def handle_save(self, action):
        errors = self.extractData()[-1]
        if errors:
            self.status = self.formErrorsMessage
            return
        api.portal.show_message(_("Form saved successfully"))
        # We use status 307 to ask the browser to send the same POST data.
        self.request.response.redirect(
            f"{self.context.absolute_url()}/component-library-saved",
            status=307,
        )

    @form.button.buttonAndHandler(_("Cancel"), name="cancel")
    def handle_cancel(self, action):
        """Cancel button"""
        api.portal.show_message(_("Action canceled"))
        self.request.response.redirect(
            f"{self.context.absolute_url()}/component-library-canceled"
        )

    @form.button.buttonAndHandler(
        _("Submit and trigger an error"), name="submit_and_error"
    )
    def handle_submit_and_error(self, action):
        data, errors = self.extractData()
        self.submitted_data = data
        if errors:
            self.status = self.formErrorsMessage
            return


class ComponentLibraryPanelEditView(ComponentLibraryEditView):
    """Show component form in a panel.

    Proto reference: https://proto.quaivecloud.com/apps/absence/ > edit absence
    """

    panel_size = "large"

    data_pat_inject = ".modal-panel-contents::element && body"

    def updateActions(self):
        super().updateActions()
        for action in self.actions.values():
            action.addClass("close-panel")


class ComponentLibraryDisplayView(WidgetsView):
    """Render the component library form in display mode."""

    ignoreContext = True
    ignoreRequest = False
    schema = IComponentLibrarySchema
