/*
 *  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.attributes;

import java.util.Optional;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.model.Model;

import org.jspecify.annotations.Nullable;

import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.internal.collections._Maps;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.metamodel.interactions.managed.InteractionVeto;
import org.apache.causeway.viewer.wicket.model.models.UiAttributeWkt;
import org.apache.causeway.viewer.wicket.ui.components.attributes.AttributeFragmentFactory.FieldFragment;
import org.apache.causeway.viewer.wicket.ui.components.attributes.AttributeFragmentFactory.FieldFrame;
import org.apache.causeway.viewer.wicket.ui.components.attributes.AttributeFragmentFactory.FrameFragment;
import org.apache.causeway.viewer.wicket.ui.components.attributes.AttributeFragmentFactory.InputFragment;
import org.apache.causeway.viewer.wicket.ui.components.attributes.choices.AttributePanelWithSelect;
import org.apache.causeway.viewer.wicket.ui.components.widgets.bootstrap.FormGroup;
import org.apache.causeway.viewer.wicket.ui.util.Wkt;
import org.apache.causeway.viewer.wicket.ui.util.WktTooltips;
import org.apache.causeway.viewer.wicket.ui.util.XrayWkt;

public abstract class AttributePanelWithFormField<T>
extends AttributePanelWithInlinePrompt {

    private static final long serialVersionUID = 1L;

    protected final Class<T> type;

    protected AttributePanelWithFormField(
            final String id,
            final UiAttributeWkt attributeModel,
            final Class<T> type) {
        super(id, attributeModel);
        this.type = type;
    }

    @Override
    protected final Component getValidationFeedbackReceiver() {
        return getFormComponent();
    }

    // -- FIELD FRAME

    /**
     * Builds the field frame when in REGULAR format.
     * <p>Is added to {@link #getRegularFrame()}.
     */
    protected MarkupContainer createFieldFrame() {
        var renderScenario = getRenderScenario();
        final FieldFragment fieldFragment;
        switch (renderScenario) {
        case READONLY:
            // setup as output-format (no links)
            fieldFragment = FieldFragment.NO_LINK_VIEWING;
            break;
        case CAN_EDIT:
        case CAN_EDIT_INLINE:
        case CAN_EDIT_INLINE_VIA_ACTION:
        case EDITING_WITH_LINK_TO_NESTED:
            // setup as output-format (with links to edit)
            fieldFragment = FieldFragment.LINK_TO_PROMT;
            break;
        case EDITING:
            // setup as input-format
            fieldFragment = attributeModel().isEditingMode()
                ? FieldFragment.NO_LINK_EDITING // supports additional buttons (clear, ...)
                : FieldFragment.NO_LINK_VIEWING;
            break;

        default:
            throw _Exceptions.unmatchedCase(renderScenario);
        }
        return Wkt.fragment(fieldFragment.getContainerId(),
                fieldFragment.getFragmentId(),
                this);
    }

    // -- FORM COMPONENT

    private FormComponent<T> formComponent;
    @Nullable
    protected final FormComponent<T> getFormComponent() { return formComponent; }

    /**
     * Builds the component to render the form input field.
     */
    protected abstract FormComponent<T> createFormComponent(String id, UiAttributeWkt attributeModel);

    // -- REGULAR

    @Override
    protected final MarkupContainer createRegularFrame() {
        var attributeModel = attributeModel();

        var friendlyNameModel = Model.of(attributeModel.getFriendlyName());

        formComponent = createFormComponent(ID_SCALAR_VALUE, attributeModel);
        formComponent.setLabel(friendlyNameModel);

        var formGroup = FrameFragment.REGULAR
                .createComponent(id->new FormGroup(id, formComponent));
        formGroup.add(formComponent);

        formGroup.add(fieldFrame = createFieldFrame());

        formComponent.setRequired(attributeModel.isRequired());

        if(attributeModel.isShowMandatoryIndicator()) {
            Wkt.cssAppend(formGroup, "mandatory");
        }

        scalarNameLabelAddTo(formGroup, friendlyNameModel);

        formComponent.add(_Util.createValidatorFor(attributeModel));

        var renderScenario = getRenderScenario();

        XrayWkt.ifEnabledDo(()->{
            // debug (wicket viewer x-ray)
            var xrayDetails = _Maps.<String, String>newLinkedHashMap();
            xrayDetails.put("panel", this.getClass().getSimpleName());
            xrayDetails.put("renderScenario", renderScenario.name());
            xrayDetails.put("inputFragmentType", getInputFragmentType().map(x->x.name()).orElse("(none)"));
            xrayDetails.put("formComponent", _Strings.nonEmpty(formComponent.getClass().getSimpleName())
                    .orElseGet(()->formComponent.getClass().getName()));
            xrayDetails.put("formComponent.id", formComponent.getId());
            xrayDetails.put("formComponent.validators (count)", ""+_NullSafe.size(formComponent.getValidators()));
            xrayDetails.put("attributeModel.disableReason", ""+attributeModel().disabledReason().map(InteractionVeto::getReason).orElse(null));
            xrayDetails.put("attributeModel.whetherHidden", ""+attributeModel().whetherHidden());
            xrayDetails.put("attributeModel.identifier", ""+attributeModel().getIdentifier());
            xrayDetails.put("attributeModel.choices (count)", ""+attributeModel().getChoices().size());
            xrayDetails.put("attributeModel.metaModel.featureIdentifier", ""+attributeModel().getMetaModel().getFeatureIdentifier());
            xrayDetails.put("attributeModel.elementTypeSpec", ""+attributeModel().getElementType().toString());
            xrayDetails.put("attributeModel.proposedValue", ""+attributeModel().proposedValue().getValue().getValue());
//                    getSpecialization()
//                    .fold(
//                            param->""+param.getValue(),
//                            prop->""+prop.getPendingPropertyModel().getValueAsTitle().getValue()));
            Wkt.markupAdd(fieldFrame, ID_XRAY_DETAILS, XrayWkt.formatAsListGroup(xrayDetails));

        });

        if(renderScenario.isReadonly()) {
            fieldFrame.add(FieldFrame.SCALAR_VALUE_CONTAINER
                    .createComponent(this::createComponentForOutput));
        } else if(renderScenario.isViewingAndCanEditAny()) {

            // this results in a link created;
            // link stuff is handled later in ScalarPanelAbstract2.setupInlinePrompt

        } else {
            getInputFragmentType()
            .ifPresent(inputFragmentType->
                fieldFrame.add(inputFragmentType.createFragment(this, formComponent)));
        }

        onFormGroupCreated(formGroup);

        formComponent.setVisible(true);
        formComponent.setVisibilityAllowed(true);

        return formGroup;
    }

    // -- COMPACT

    @Override
    protected final Component createCompactFrame() {
        return FrameFragment.COMPACT
                .createComponent(this::createComponentForOutput);
    }

    // -- HOOKS

    protected Optional<InputFragment> getInputFragmentType() {
        return Optional.empty();
    }

    /**
     * Optional hook, to eg. add additional components (like Blob which adds preview image)
     */
    protected void onFormGroupCreated(final FormGroup formGroup) {}

    @Override
    protected void onInitializeNotEditable() {
        if(getFormComponent()!=null) {
            //keep inlinePromptLink (if any) enabled
            getFormComponent().setEnabled(false);
        }
        replaceDisabledTagWithReadonlyTagIfApplicable();
        clearTooltip();
    }

    @Override
    protected void onInitializeReadonly(final String disableReason) {
        formComponentEnable(false);
        replaceDisabledTagWithReadonlyTagIfApplicable();
        setTooltip(disableReason);
    }

    @Override
    protected void onInitializeEditable() {
        formComponentEnable(true);
        clearTooltip();
    }

    @Override
    protected void onMakeNotEditable(final String disableReason) {
        super.onMakeNotEditable(disableReason);
        formComponentEnable(false);
        setTooltip(disableReason);
    }

    @Override
    protected void onMakeEditable() {
        super.onMakeEditable();
        formComponentEnable(true);
        clearTooltip();
    }

    // -- XRAY

    @Override
    public String getVariation() {
        return XrayWkt.isEnabled()
                ? "xray"
                : super.getVariation();
    }

    // -- HELPER

    private void formComponentEnable(final boolean b) {
        if(getFormComponent()!=null) {
            getFormComponent().setEnabled(b);
        }
        if(inlinePromptLink!=null) {
            inlinePromptLink.setEnabled(b);
        }
    }

    private void setTooltip(final String tooltip) {
        WktTooltips.addTooltip(getFormComponent(), tooltip);
        WktTooltips.addTooltip(inlinePromptLink, tooltip);
    }

    private void clearTooltip() {
        WktTooltips.clearTooltip(getFormComponent());
        WktTooltips.clearTooltip(inlinePromptLink);
    }

    private void replaceDisabledTagWithReadonlyTagIfApplicable() {
        if(!getWicketViewerSettings().isReplaceDisabledTagWithReadonlyTag()) return;
        //[CAUSEWAY-3727] select2 does not seem to support this replacement behavior
        if(this instanceof AttributePanelWithSelect) return;
        Wkt.behaviorAddReplaceDisabledTagWithReadonlyTag(getFormComponent());
    }
}
