/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.causeway.viewer.wicket.ui.components.collection.selector;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.DownloadLink;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.core.metamodel.tabular.DataTableInteractive;
import org.apache.causeway.viewer.wicket.model.hints.CausewaySelectorEvent;
import org.apache.causeway.viewer.wicket.model.links.Menuable;
import org.apache.causeway.viewer.wicket.model.models.coll.CollectionModel;
import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
import org.apache.causeway.viewer.wicket.model.util.PageUtils;
import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
import org.apache.causeway.viewer.wicket.ui.components.widgets.links.AjaxLinkNoPropagate;
import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
import org.apache.causeway.viewer.wicket.ui.util.Wkt;
import org.apache.causeway.viewer.wicket.ui.util.WktComponents;
import org.apache.causeway.viewer.wicket.ui.util.WktLinks;

import org.jspecify.annotations.NonNull;

/**
 * Provides a list of links for selecting other views that support
 * {@link org.apache.causeway.viewer.commons.model.components.UiComponentType#COLLECTION_CONTENTS} with a backing
 * {@link org.apache.causeway.viewer.wicket.model.models.coll.CollectionModel}.
 */
public class CollectionPresentationSelectorPanel
extends PanelAbstract<DataTableInteractive, CollectionModel> {

    private static final long serialVersionUID = 1L;

    private static final String ID_VIEWS = "views";
    private static final String ID_VIEW_LIST = "viewList";
    private static final String ID_VIEW_LINK = "viewLink";
    private static final String ID_VIEW_ITEM = "viewItem";
    private static final String ID_VIEW_ITEM_TITLE = "viewItemTitle";
    private static final String ID_VIEW_ITEM_ICON = "viewItemIcon";
    private static final String ID_VIEW_ITEM_CHECKMARK = "viewItemCheckmark"; // indicator for the selected item
    private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon";
    private static final String ID_SECTION_SEPARATOR = "sectionSeparator";
    private static final String ID_SECTION_LABEL = "sectionLabel";

    private CollectionPresentationSelectorHelper selectorHelper;
    private final Can<CollectionPresentationChoice> presentationChoices;
    private final ComponentHintKey componentHintKey;

    private CollectionPresentationChoice selectedCollectionPresentationChoice;

    public CollectionPresentationSelectorPanel(
            final String id,
            final CollectionModel model) {
        this(id, model, ComponentHintKey.noop());
    }

    public CollectionPresentationSelectorPanel(
            final String id,
            final CollectionModel model,
            final ComponentHintKey componentHintKey) {
        super(id, model);
        this.componentHintKey = componentHintKey;

        this.selectorHelper = new CollectionPresentationSelectorHelper(
                model, getComponentFactoryRegistry(), componentHintKey);
        this.presentationChoices = selectorHelper.collectionPresentationChoices();
    }

    /**
     * Build UI only after added to parent.
     */
    @Override
    public void onInitialize() {
        super.onInitialize();
        addDropdown();
    }

    private void addDropdown() {

        final String selected = selectorHelper.honourViewHintElseDefault(this);

        // selector
        if (!presentationChoices.isCardinalityMultiple()) {
            WktComponents.permanentlyHide(this, ID_VIEWS);
            return;
        }

        this.selectedCollectionPresentationChoice = selectorHelper.find(presentationChoices, selected);

        final WebMarkupContainer views = new WebMarkupContainer(ID_VIEWS);
        final WebMarkupContainer container = new WebMarkupContainer(ID_VIEW_LIST);

        views.addOrReplace(container);
        views.setOutputMarkupId(true);

        this.setOutputMarkupId(true);

        final Label viewButtonIcon = Wkt.labelAdd(views, ID_VIEW_BUTTON_ICON, "");

        Wkt.listViewAdd(container, ID_VIEW_ITEM, sorted(presentationChoices), item->{
            var menuable = item.getModelObject();
            var menuableKind = menuable.menuableKind();

            Wkt.cssAppend(item, menuableKind.getCssClassForLiElement());

            switch(menuableKind) {
            case SECTION_SEPARATOR:
                WktComponents.permanentlyHide(item, ID_SECTION_LABEL);
                WktComponents.permanentlyHide(item, ID_VIEW_LINK);
                Wkt.labelAdd(item, ID_SECTION_SEPARATOR, "");
                return;
            case SECTION_LABEL:
                WktComponents.permanentlyHide(item, ID_SECTION_SEPARATOR);
                WktComponents.permanentlyHide(item, ID_VIEW_LINK);
                Wkt.labelAdd(item, ID_SECTION_LABEL, ((Menuable.SectionLabel)menuable).sectionLabel());
                return;
            case LINK:
                WktComponents.permanentlyHide(item, ID_SECTION_SEPARATOR);
                WktComponents.permanentlyHide(item, ID_SECTION_LABEL);
                break; // fall through
            default:
                return;
            }

            var linkEntry = (LinkEntry) menuable;
            var choice = linkEntry.choice;

            // add direct download link instead of a panel
            if(choice.isExporter()) {

                final DownloadLink downloadLink = (DownloadLink)
                        choice.componentFactory().createComponent(ID_VIEW_LINK, getModel());
                WktComponents.permanentlyHide(downloadLink, ID_VIEW_ITEM_CHECKMARK);

                item.addOrReplace(downloadLink);

                // add title and icon to the link
                addLinkWithIconAndTitle(item, downloadLink);
                return;
            }

            // on click: make the clicked item the new selected item
            var link = Wkt.linkAdd(item, ID_VIEW_LINK, target->{
                final CollectionPresentationSelectorPanel linksSelectorPanel = CollectionPresentationSelectorPanel.this;
                linksSelectorPanel.setViewHintAndBroadcast(choice.id(), target);

                linksSelectorPanel.selectedCollectionPresentationChoice = choice;

                CollectionPresentationSelectorPanel.this.getModel().parentedHintingBookmark()
                    .ifPresent(bookmark->componentHintKey.set(bookmark, choice.id()));

                /* [CAUSEWAY-3415] do a full page reload when required,
                 * to properly trigger all client side java-script, that decorates HTML (datatable.net, vega, ...) */
                if(linkEntry.isPageReloadRequiredOnTableViewActivation()) {
                    PageUtils.pageReload();
                } else {
                    target.add(linksSelectorPanel, views);
                }
            });

            // add title and icon to the link
            addLinkWithIconAndTitle(item, link);
            var checkmarkForSelectedPresentation = Wkt.labelAdd(link, ID_VIEW_ITEM_CHECKMARK, "");

            var isSelectedPresentation = linkEntry.isSelectedIn(this);
            checkmarkForSelectedPresentation.setVisible(isSelectedPresentation);
            if (isSelectedPresentation) {
                linkEntry.markAsSelected(viewButtonIcon, link);
            }

        });

        addOrReplace(views);
    }

    /**
     * Sorts given CollectionContentsAsFactory(s) by their orderOfAppearanceInUiDropdown,
     * in order of discovery otherwise.
     * @param filter
     * @see CollectionContentsAsFactory#orderOfAppearanceInUiDropdown()
     */
    private List<Menuable> sorted(final Can<CollectionPresentationChoice> presentationChoices) {
        var presentations = sorted(presentationChoices, CollectionPresentationChoice::isPresenter);
        var exports = sorted(presentationChoices, CollectionPresentationChoice::isExporter);
        var sortedWithSeparators = new ArrayList<Menuable>(
                presentations.size() + exports.size() + 2); // heap optimization, not strictly required

        boolean needsSpacer = false;

        if(!presentations.isEmpty()) {
            sortedWithSeparators.add(Menuable.sectionLabel(translate("Presentations")));
            sortedWithSeparators.addAll(presentations);
            needsSpacer = true;
        }
        if(!exports.isEmpty()) {
            if(needsSpacer) {
                sortedWithSeparators.add(Menuable.sectionSeparator());
            }
            sortedWithSeparators.add(Menuable.sectionLabel(translate("Exports")));
            sortedWithSeparators.addAll(exports);
        }

        return sortedWithSeparators;
    }

    private List<LinkEntry> sorted(
            final Can<CollectionPresentationChoice> presentationChoices,
            final Predicate<? super CollectionPresentationChoice> filter) {
        final List<LinkEntry> sorted = presentationChoices.stream()
            .filter(filter)
            .sorted(CollectionPresentationChoice.orderByOrderOfAppearanceInUiDropdown())
            .map(LinkEntry::linkEntry)
            .collect(Collectors.toList());
        return sorted;
    }

    protected void setViewHintAndBroadcast(final String viewName, final AjaxRequestTarget target) {
        final CollectionPresentationSelectorPanel component = CollectionPresentationSelectorPanel.this;
        send(getPage(), Broadcast.EXACT,
                new CausewaySelectorEvent(component, CollectionPresentationSelectorHelper.UIHINT_EVENT_VIEW_KEY, viewName, target));
    }

    // -- UTILITY

    @SuppressWarnings("unchecked")
    static void addLinkWithIconAndTitle(
            final @NonNull ListItem<? extends Menuable> item,
            final @NonNull MarkupContainer link) {
        WktLinks.listItemAsDropdownLink((ListItem<LinkEntry>)item, link,
                ID_VIEW_ITEM_TITLE, LinkEntry::nameFor,
                ID_VIEW_ITEM_ICON, null,
                LinkEntry::cssClassFor);
    }

    record LinkEntry(CollectionPresentationChoice choice) implements Menuable {

        // -- FACTORIES

        public static LinkEntry linkEntry(final @NonNull CollectionPresentationChoice choice) {
            return new LinkEntry(choice);
        }

        // --

        @Override
        public Kind menuableKind() { return Kind.LINK; }

        // -- PREDICATES

        boolean isSelectedIn(final CollectionPresentationSelectorPanel panel) {
           return Objects.equals(this.choice.factoryKey(), panel.selectedCollectionPresentationChoice.factoryKey());
        }
        boolean isPageReloadRequiredOnTableViewActivation() {
            return choice.isPageReloadRequiredOnTableViewActivation();
        }
        boolean isPresenter() {
            return choice.isPresenter();
        }
        boolean isExporter() {
            return choice.isExporter();
        }

        // -- MARK

        /**
         * Disables the selected presentation's link,
         * also sets the icon left of the drop-down caret to the one,
         * that corresponds to the selected presentation.
         */
        void markAsSelected(final Label viewButtonIcon, final AjaxLinkNoPropagate link) {
            initViewIcon(this, viewButtonIcon);
            Wkt.cssReplace(viewButtonIcon, "ViewLinkItem " + choice.cssClass());
            link.setEnabled(false);
        }

        // -- HELPER

        private static IModel<String> nameFor(final LinkEntry linkEntry) {
            return Model.of(linkEntry.choice.label());
        }
        private static IModel<String> cssClassFor(final LinkEntry linkEntry, final Label viewIcon) {
            initViewIcon(linkEntry, viewIcon);
            return Model.of(linkEntry.choice.cssClass());
        }

        private static void initViewIcon(final LinkEntry linkEntry, final Label viewIcon) {
            if(CollectionContentsAsFactory.class.isAssignableFrom(componentFactoryClass(linkEntry))) {
                viewIcon.setDefaultModelObject("");
                viewIcon.setEscapeModelStrings(true);
            } else {
                // Small hack: if there is no specific CSS class then we assume that background-image is used
                // the span.ViewItemLink should have some content to show it
                // FIX: find a way to do this with CSS (width and height don't seems to help)
                viewIcon.setDefaultModelObject("&#160;&#160;&#160;&#160;&#160;");
                viewIcon.setEscapeModelStrings(false);
            }
        }

        private static Class<? extends ComponentFactory> componentFactoryClass(final LinkEntry linkEntry) {
            return linkEntry.choice.factoryKey().factoryClass();
        }
    }

}
