/* BdfServer_HtmlProducers - Copyright (c) 2010-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.htmlproducers.edition;

import fr.exemole.bdfserver.api.instruction.BdfParameters;
import fr.exemole.bdfserver.api.interaction.Domains;
import fr.exemole.bdfserver.api.interaction.InteractionConstants;
import fr.exemole.bdfserver.api.interaction.domains.EditionDomain;
import fr.exemole.bdfserver.api.managers.UiManager;
import fr.exemole.bdfserver.api.namespaces.BdfUserSpace;
import fr.exemole.bdfserver.api.namespaces.FicheFormSpace;
import fr.exemole.bdfserver.api.ui.CommentUi;
import fr.exemole.bdfserver.api.ui.DataUi;
import fr.exemole.bdfserver.api.ui.FieldUi;
import fr.exemole.bdfserver.api.ui.IncludeUi;
import fr.exemole.bdfserver.api.ui.UiComponent;
import fr.exemole.bdfserver.api.ui.UiComponents;
import fr.exemole.bdfserver.commands.edition.FicheChangeCommand;
import fr.exemole.bdfserver.commands.edition.FicheCreationCommand;
import fr.exemole.bdfserver.html.BdfServerHtmlProducer;
import fr.exemole.bdfserver.html.ParameterMap;
import fr.exemole.bdfserver.html.consumers.Button;
import fr.exemole.bdfserver.html.consumers.attributes.Deploy;
import fr.exemole.bdfserver.html.forms.CommonFormHtml;
import fr.exemole.bdfserver.html.forms.FieldFormEntryHtml;
import fr.exemole.bdfserver.html.forms.FormHandler;
import fr.exemole.bdfserver.html.forms.FormParameters;
import fr.exemole.bdfserver.html.forms.IncludeFormEntryHtml;
import fr.exemole.bdfserver.html.jslib.FicheJsLibs;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.pointeurs.FichePointeur;
import net.fichotheque.utils.CorpusMetadataUtils;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.html.HA;
import net.mapeadores.util.html.JsObject;
import net.mapeadores.util.html.TrustedHtml;
import fr.exemole.bdfserver.api.ficheform.FormEntry;
import fr.exemole.bdfserver.api.ficheform.FormEntryProvider;


/**
 *
 * @author Vincent Calame
 */
public class FicheFormHtmlProducer extends BdfServerHtmlProducer {

    private final BdfParameters bdfParameters;
    private final FichePointeur mainFichePointeur;
    private final PermissionSummary permissionSummary;
    private final Corpus mainCorpus;
    private String title;
    private SubsetItem masterSubsetItem = null;
    private final FormEntryProvider formEntryProvider;
    private String ficheResultPage = EditionDomain.FICHE_RESULT_PAGE;
    private String resultPageOptions = null;
    private String commandName;
    private String gotoAtStart = "";
    private boolean discarded = false;
    private final PrefixEngine prefixEngine;
    int id = -1;

    private FicheFormHtmlProducer(BdfParameters bdfParameters, FichePointeur mainFichePointeur, FormEntryProvider formEntryProvider) {
        super(bdfParameters);
        addJsLib(FicheJsLibs.FORM);
        addThemeCss("edition.css");
        this.bdfParameters = bdfParameters;
        this.mainFichePointeur = mainFichePointeur;
        this.permissionSummary = bdfParameters.getPermissionSummary();
        this.mainCorpus = mainFichePointeur.getCorpus();
        this.formEntryProvider = formEntryProvider;
        setMainStorageKey("FF_" + mainCorpus.getSubsetKeyString());
        this.prefixEngine = initPrefixEngine();
    }

    public void setFicheResultPage(String ficheResultPage) {
        this.ficheResultPage = ficheResultPage;
    }

    public void setResultPageOptions(String resultPageOptions) {
        this.resultPageOptions = resultPageOptions;
    }

    public void setGotoAtStart(String gotoAtStart) {
        this.gotoAtStart = gotoAtStart;
    }

    @Override
    public void printHtml() {
        String formId = generateId();
        ParameterMap parameterMap = ParameterMap.init()
                .command(commandName)
                .page(ficheResultPage)
                .subset(mainCorpus)
                .param(InteractionConstants.PAGE_OPTIONS_PARAMNAME, resultPageOptions);
        if (id != -1) {
            parameterMap.param(InteractionConstants.ID_PARAMNAME, String.valueOf(id));
        }
        if (!gotoAtStart.isEmpty()) {
            int idx = gotoAtStart.indexOf('@');
            String resultGoto = gotoAtStart;
            if (idx > 0) {
                resultGoto = gotoAtStart.substring(0, idx);
            }
            parameterMap.param(InteractionConstants.GOTO_PARAMNAME, resultGoto);
        }
        prefixEngine.checkParameters(parameterMap);
        start();
        this
                .SCRIPT()
                .__jsObject("Ficheform.ARGS", initFicheformArgs())
                ._SCRIPT();
        this
                .DIV("edition-Page edition-form-Page")
                .__(EditionHtmlUtils.printFicheHeader(this, title, discarded, masterSubsetItem, workingLang, formatLocale, formId))
                .MAIN("edition-Unit")
                .FORM_post(HA.action(Domains.EDITION).id(formId)
                        .attr("data-ficheform-role", "form")
                        .attr("data-ficheform-corpus", mainCorpus.getSubsetName())
                        .attr("data-submit-process", "wait"))
                .INPUT_hidden(parameterMap)
                .DIV(HA.classes("edition-form-Entries").attr("data-ficheform-role", "viewport"))
                .__(FicheFormHtmlProducer.this.printFormEntries())
                .__(Button.COMMAND,
                        Button.submit("action-Save", "_ link.global.ok").formId(formId).ficheForm(true).shortcutKey("mod+s").shortcutTooltip("MOD+S"))
                ._DIV()
                ._FORM()
                ._MAIN()
                ._DIV();
        end();
    }

    private boolean printFormEntries() {
        if (prefixEngine instanceof SatellitesPrefixEngine) {
            printFormEntries(mainFichePointeur, getUiComponents(mainCorpus), new MainFormEntryProvider(formEntryProvider), prefixEngine.getMainPrefix(), "");
            printSatelliteBlocks((SatellitesPrefixEngine) prefixEngine);
        } else {
            printFormEntries(mainFichePointeur, getUiComponents(mainCorpus), formEntryProvider, null, "");
        }
        return true;
    }

    private boolean printSatelliteBlocks(SatellitesPrefixEngine satelittesPrefixEngine) {
        FormEntryProvider satelliteFormEntryProvider = new SatelliteFormEntryProvider(formEntryProvider);
        for (Corpus satelliteCorpus : satelittesPrefixEngine.satelliteList) {
            SubsetKey satelliteKey = satelliteCorpus.getSubsetKey();
            FichePointeur satellitePointeur = (FichePointeur) mainFichePointeur.getParentagePointeur(satelliteKey);
            boolean deploy = ((satellitePointeur.isEmpty()) && (isWithJavascript()));
            String targetId = null;
            String satelliteGoto = "satellite_" + satelliteCorpus.getSubsetName();
            this
                    .SECTION(HA.classes("ficheform-satellite-Block").attr("data-goto", satelliteGoto));
            if (deploy) {
                String inputId = generateId();
                targetId = generateId();
                this
                        .H1("ficheform-satellite-Title")
                        .INPUT_checkbox(HA.id(inputId).name(InteractionConstants.PREFIXLIST_PARAMNAME).value(satelliteKey.getSubsetName()).populate(Deploy.checkbox(targetId)))
                        .__space()
                        .LABEL(HA.forId(inputId))
                        .__escape(CorpusMetadataUtils.getSatelliteLabel(satelliteCorpus, workingLang))
                        ._LABEL()
                        ._H1();
            } else {
                this
                        .H1("ficheform-satellite-Title")
                        .__escape(CorpusMetadataUtils.getSatelliteLabel(satelliteCorpus, workingLang))
                        ._H1()
                        .INPUT_hidden(InteractionConstants.PREFIXLIST_PARAMNAME, satelliteKey.getSubsetName());
            }
            if (deploy) {
                this
                        .DIV(HA.id(targetId).classes("hidden"));
            }
            printFormEntries(satellitePointeur, getUiComponents(satelliteCorpus), satelliteFormEntryProvider, satelliteKey.getSubsetName(), satelliteGoto);
            if (deploy) {
                this
                        ._DIV();
            }
            this
                    ._SECTION();
        }
        return true;
    }


    private boolean printFormEntries(FichePointeur fichePointeur, UiComponents uiComponents, FormEntryProvider formEntryProvider, String namePrefix, String gotoPrefix) {
        FormParameters formParameters = FormParameters.init(bdfParameters).namePrefix(namePrefix).gotoPrefix(gotoPrefix);
        FicheMeta currentFicheMeta = fichePointeur.getCurrentFicheMeta();
        if (currentFicheMeta != null) {
            formParameters.ficheLang(currentFicheMeta.getLang());
        }
        FormHandler formHandler = FormHandler.init(formParameters);
        List<Object> formObjectList = new ArrayList<Object>();
        Set<String> existingGroupSet = new HashSet<String>();
        for (UiComponent uiComponent : uiComponents.getUiComponentList()) {
            if (uiComponent instanceof FieldUi) {
                FormEntry.Field formEntry = formEntryProvider.getFormEntry(fichePointeur, (FieldUi) uiComponent);
                if (formEntry != null) {
                    checkGroups(uiComponent, existingGroupSet);
                    formObjectList.add(formEntry);
                }
            } else if (uiComponent instanceof IncludeUi) {
                FormEntry.Include formEntry = formEntryProvider.getFormEntry(fichePointeur, (IncludeUi) uiComponent);
                if (formEntry != null) {
                    checkGroups(uiComponent, existingGroupSet);
                    formObjectList.add(formEntry);
                }
            } else if (uiComponent instanceof CommentUi) {
                formObjectList.add(uiComponent);
            } else if (uiComponent instanceof DataUi) {
            } else {
                throw new IllegalArgumentException("unknown ComponentUi implementation : " + uiComponent.getClass().getName());
            }
        }
        for (Object formObject : formObjectList) {
            if (formObject instanceof FormEntry.Field) {
                this
                        .__(FieldFormEntryHtml.print(this, (FormEntry.Field) formObject, formHandler));
            } else if (formObject instanceof FormEntry.Include) {
                this
                        .__(IncludeFormEntryHtml.print(this, (FormEntry.Include) formObject, formHandler));
            } else if (formObject instanceof CommentUi) {
                CommentUi commentUi = (CommentUi) formObject;
                boolean display = true;
                Attribute condition = commentUi.getAttributes().getAttribute(FicheFormSpace.CONDITION_ATTRIBUTEKEY);
                if (condition != null) {
                    String firstValue = condition.getFirstValue();
                    if ((firstValue.startsWith("~")) && (!existingGroupSet.contains(firstValue.substring(1)))) {
                        display = false;
                    }
                }
                if (display) {
                    this
                            .__(printCommentUi(commentUi, gotoPrefix));
                }
            }
        }
        return true;
    }

    private void checkGroups(UiComponent uiComponent, Set<String> existingGroupSet) {
        Attribute groups = uiComponent.getAttributes().getAttribute(FicheFormSpace.GROUPS_ATTRIBUTEKEY);
        if (groups != null) {
            for (String value : groups) {
                existingGroupSet.add(value);
            }
        }
    }

    private boolean printCommentUi(CommentUi commentUi, String gotoPrefix) {
        if (commentUi.isForm()) {
            TrustedHtml html = commentUi.getHtmlByLang(workingLang);
            if (html != null) {
                String goTo = gotoPrefix + commentUi.getCloneName();
                CommonFormHtml.printComment(this, html, goTo);
            }
        }
        return true;
    }

    private JsObject initFicheformArgs() {
        boolean noTab = bdfUser.getPrefs().getBoolean(BdfUserSpace.NOTAB_KEY);
        if (!noTab) {
            Attribute attribute = bdfUser.getRedacteur().getAttributes().getAttribute(BdfUserSpace.FICHEFORM_KEY);
            if (attribute != null) {
                for (String value : attribute) {
                    if (value.equals("noTab")) {
                        noTab = true;
                    }
                }
            }
        }
        boolean syntaxActive = bdfUser.getPrefs().getBoolean(BdfUserSpace.SYNTAXACTIVE_KEY);
        JsObject permissionsObject = new JsObject();
        for (Corpus corpus : bdfParameters.getFichotheque().getCorpusList()) {
            if (permissionSummary.canCreate(corpus)) {
                permissionsObject.put(corpus.getSubsetName(), "create");
            }
        }
        String dateInputsType = bdfUser.getPrefs().getDateInputsType();
        return JsObject.init()
                .put("noTab", noTab)
                .put("dateInputs", dateInputsType)
                .put("syntaxActive", syntaxActive)
                .put("permissions", permissionsObject)
                .put("goto", gotoAtStart);
    }

    private PrefixEngine initPrefixEngine() {
        PrefixEngine result = null;
        List<Corpus> includedSatelliteList = BdfServerUtils.getIncludeSatelliteList(mainCorpus);
        if (!includedSatelliteList.isEmpty()) {
            List<Corpus> prefixedSatelliteList = new ArrayList<Corpus>();
            for (Corpus satellite : includedSatelliteList) {
                if (checkPermission(satellite)) {
                    prefixedSatelliteList.add(satellite);
                }
            }
            if (!prefixedSatelliteList.isEmpty()) {
                result = new SatellitesPrefixEngine(prefixedSatelliteList);
            }
        }
        if (result == null) {
            result = new NonePrefixEngine();
        }
        return result;
    }

    private boolean checkPermission(Corpus satelliteCorpus) {
        if (id > 0) {
            FicheMeta satelliteFiche = satelliteCorpus.getFicheMetaById(id);
            if (satelliteFiche != null) {
                return permissionSummary.canWrite(satelliteFiche);
            }
        }
        return permissionSummary.canCreate(satelliteCorpus);
    }

    private UiComponents getUiComponents(Corpus corpus) {
        UiManager uiManager = bdfServer.getUiManager();
        return uiManager.getMainUiComponents(corpus);
    }

    public static FicheFormHtmlProducer newCreationInstance(BdfParameters bdfParameters, FichePointeur fichePointeur, FormEntryProvider formEntryProvider) {
        FicheFormHtmlProducer ficheFormHtmlProducer = new FicheFormHtmlProducer(bdfParameters, fichePointeur, formEntryProvider);
        ficheFormHtmlProducer.commandName = FicheCreationCommand.COMMANDNAME;
        ficheFormHtmlProducer.title = CorpusMetadataUtils.getNewFicheLabel(fichePointeur.getCorpus(), ficheFormHtmlProducer.workingLang);
        return ficheFormHtmlProducer;
    }

    public static FicheFormHtmlProducer newCreationInstance(BdfParameters bdfParameters, FichePointeur fichePointeur, int id, FormEntryProvider formEntryProvider) {
        FicheFormHtmlProducer ficheFormHtmlProducer = new FicheFormHtmlProducer(bdfParameters, fichePointeur, formEntryProvider);
        ficheFormHtmlProducer.commandName = FicheChangeCommand.COMMANDNAME;
        ficheFormHtmlProducer.id = id;
        ficheFormHtmlProducer.title = CorpusMetadataUtils.getNewFicheLabel(fichePointeur.getCorpus(), ficheFormHtmlProducer.workingLang) + " (id=" + String.valueOf(id) + ")";
        return ficheFormHtmlProducer;
    }

    public static FicheFormHtmlProducer newCreationInstance(BdfParameters bdfParameters, FichePointeur fichePointeur, SubsetItem masterSubsetItem, FormEntryProvider formEntryProvider) {
        FicheFormHtmlProducer ficheFormHtmlProducer = new FicheFormHtmlProducer(bdfParameters, fichePointeur, formEntryProvider);
        ficheFormHtmlProducer.commandName = FicheChangeCommand.COMMANDNAME;
        ficheFormHtmlProducer.id = masterSubsetItem.getId();
        ficheFormHtmlProducer.masterSubsetItem = masterSubsetItem;
        ficheFormHtmlProducer.title = CorpusMetadataUtils.getNewFicheLabel(fichePointeur.getCorpus(), ficheFormHtmlProducer.workingLang) + " (id=" + String.valueOf(masterSubsetItem.getId()) + ")";
        return ficheFormHtmlProducer;
    }

    public static FicheFormHtmlProducer newChangeInstance(BdfParameters bdfParameters, FichePointeur fichePointeur, FormEntryProvider formEntryProvider) {
        FicheFormHtmlProducer ficheFormHtmlProducer = new FicheFormHtmlProducer(bdfParameters, fichePointeur, formEntryProvider);
        FicheMeta ficheMeta = fichePointeur.getCurrentFicheMeta();
        ficheFormHtmlProducer.commandName = FicheChangeCommand.COMMANDNAME;
        ficheFormHtmlProducer.id = ficheMeta.getId();
        ficheFormHtmlProducer.title = CorpusMetadataUtils.getFicheTitle(ficheMeta, ficheFormHtmlProducer.workingLang, ficheFormHtmlProducer.formatLocale);
        ficheFormHtmlProducer.discarded = ficheMeta.isDiscarded();
        Subset masterSubset = ficheMeta.getCorpus().getMasterSubset();
        if (masterSubset != null) {
            ficheFormHtmlProducer.masterSubsetItem = masterSubset.getSubsetItemById(ficheMeta.getId());
        }
        return ficheFormHtmlProducer;
    }


    private abstract class PrefixEngine {

        protected abstract void checkParameters(ParameterMap parameterMap);

        protected abstract String getMainPrefix();

    }


    private class NonePrefixEngine extends PrefixEngine {

        @Override
        protected void checkParameters(ParameterMap parameterMap) {

        }

        @Override
        protected String getMainPrefix() {
            return null;
        }

    }


    private class SatellitesPrefixEngine extends PrefixEngine {

        private final List<Corpus> satelliteList;

        private SatellitesPrefixEngine(List<Corpus> satelliteList) {
            this.satelliteList = satelliteList;
        }

        @Override
        protected void checkParameters(ParameterMap parameterMap) {
            StringBuilder buf = new StringBuilder();
            buf.append(mainCorpus.getSubsetName());
            for (Corpus corpus : satelliteList) {
                buf.append(",");
                buf.append(corpus.getSubsetName());
            }
            parameterMap
                    .param(InteractionConstants.PREFIXTYPE_PARAMNAME, InteractionConstants.SATELLITES_PREFIXTYPE_PARAMVALUE) /* .param(InteractionConstants.PREFIXLIST_PARAMNAME, buf.toString())*/;
        }

        @Override
        protected String getMainPrefix() {
            return mainCorpus.getSubsetKey().getSubsetName();
        }

    }


    private static class MainFormEntryProvider implements FormEntryProvider {

        private final FormEntryProvider orginalFormEntryProvider;

        private MainFormEntryProvider(FormEntryProvider orginalFormEntryProvider) {
            this.orginalFormEntryProvider = orginalFormEntryProvider;
        }

        @Override
        public FormEntry.Field getFormEntry(FichePointeur fichePointeur, FieldUi fieldUi) {
            return orginalFormEntryProvider.getFormEntry(fichePointeur, fieldUi);
        }

        @Override
        public FormEntry.Include getFormEntry(FichePointeur fichePointeur, IncludeUi includeUi) {
            switch (includeUi.getName()) {
                case FichothequeConstants.PARENTAGE_NAME:
                    return null;
                default:
                    return orginalFormEntryProvider.getFormEntry(fichePointeur, includeUi);
            }
        }

    }


    private static class SatelliteFormEntryProvider implements FormEntryProvider {

        private final FormEntryProvider orginalFormEntryProvider;

        private SatelliteFormEntryProvider(FormEntryProvider orginalFormEntryProvider) {
            this.orginalFormEntryProvider = orginalFormEntryProvider;
        }

        @Override
        public FormEntry.Field getFormEntry(FichePointeur fichePointeur, FieldUi fieldUi) {
            if (ignoreFormEntry(fieldUi)) {
                return null;
            }
            return orginalFormEntryProvider.getFormEntry(fichePointeur, fieldUi);
        }

        @Override
        public FormEntry.Include getFormEntry(FichePointeur fichePointeur, IncludeUi includeUi) {
            switch (includeUi.getName()) {
                case FichothequeConstants.PARENTAGE_NAME:
                case FichothequeConstants.LIAGE_NAME:
                    return null;
                default:
                    return orginalFormEntryProvider.getFormEntry(fichePointeur, includeUi);
            }
        }

    }

    private static boolean ignoreFormEntry(FieldUi fieldUi) {
        switch (fieldUi.getFieldString()) {
            case FieldKey.SPECIAL_OWNERS:
                return true;
            default:
                return false;
        }
    }


}
