/*
 *  Copyright 2023 Anyware Services
 *
 *  Licensed 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.
 */

/**
 * Actions on Workflow transtitions
 */
Ext.define('Ametys.plugins.workflow.actions.WorkflowTransitionAction', {
    singleton: true,
    
    /**
    * @private
    * @property {String} _mode 'create', 'add' or 'edit' 
    */
    
    /**
    * @private
    * @property {String} _workflowName id of the workflow
    */
    
    /**
    * @private
    * @property {String} _stepId id of current step parent
    */
   
    /**
    * @private
    * @property {String} _actionId id of current transition
    */
   
    /**
    * @private
    * @property {Ametys.window.DialogBox} _addDialogBox the addition dialog box for transitions
    */
    
    /**
    * @private
    * @property {Ametys.window.DialogBox} _createOrEditDialogBox the common dialog box for transitions edition and creation
    */
    
    /**
     * Create a new transition
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    create: function(controller)
    {
        var target = Ametys.message.MessageTargetHelper.findTarget(controller.getMatchingTargets(), Ametys.message.MessageTarget.WORKFLOW_STEP);
        if (!target)
        {
            return;
        }
        var targetParameters = target.getParameters();
        this._stepId = targetParameters.id;
        this._workflowName = targetParameters.workflowId;
        this._mode = "create";
        if (!this._createOrEditDialogBox)
        {
            this._createOrEditDialogBox = this._createDialogBox(this._getCommonItems());
        }
        else
        {
            this._createOrEditDialogBox.setTitle("{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_TITLE}}");
        }
        Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getTransitionInfos([this._workflowName, null], Ext.bind(this._setCreationFieldValues, this));
     },
     
     /**
     * Create an existing transition to selected step
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
     add: function(controller)
     {
        var target = Ametys.message.MessageTargetHelper.findTarget(controller.getMatchingTargets(), Ametys.message.MessageTarget.WORKFLOW_STEP);
        if (!target)
        {
            return;
        }
        var targetParameters = target.getParameters();
        this._stepId = targetParameters.id;
        this._workflowName = targetParameters.workflowId;
        this._mode = "add";
        
        if (!this._addDialogBox)
        {
            this._addDialogBox = this._createDialogBox(this._getAddBoxItems());
        }
        
        var availableActions = this._addDialogBox.getComponent('availableAction');
        availableActions.getStore().load({
            callback: function(){
                var selectByDefault = availableActions.getStore().getData().items[0];
                if (selectByDefault)
                {
                    availableActions.setValue(selectByDefault.getId());
                }
                else
                {
                    availableActions.setValue("");
                }
            }
        });
        
        this._addDialogBox.show();
     },
     
    /**
     * @private
     * Get box fields for adding an existing transition
     * @returns {Ext.form.field[]} the box items
     */
    _getAddBoxItems: function()
    {
        return [
            {
                xtype: 'tagfield',
                name: 'availableAction',
                itemId: 'availableAction',
                fieldLabel: "{{i18n PLUGINS_WORKFLOW_ADD_TRANSITION_AVAILABLE_LABEL}} *",
                ametysDescription: "{{i18n PLUGINS_WORKFLOW_ADD_TRANSITION_AVAILABLE_DESC}}",
                store: this._getAvailableActions(),
                queryMode: 'local',
                displayField: 'label',
                valueField: 'id',
                value: "-1",
                allowBlank: false,
                multiselect: true
            }
        ];
    },
     
    /**
     * @private
     * Get common items between new and edit mode
     * @returns {Ext.form.field[]} the box items
     */
    _getCommonItems: function()
    {
        return [
            {
                xtype: "multilingualstring",
                name: 'label',
                itemId: 'label',
                allowBlank: false,
                fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_LABEL}} *",
                ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_LABEL_DESC}}"
            },
            {
                xtype: "numberfield",
                name: 'id',
                itemId: 'id',
                allowBlank: false,
                minValue: 0,
                fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID}} *",
                ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID_DESC}}",
                allowDecimals: false,
                validator: Ext.bind(this._idValidator, this)
            },
            {
                xtype: "combobox",
                name: 'finalStep',
                itemId: 'finalStep',
                fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_FINAL_STATE_LABEL}} *",
                ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_FINAL_STATE_DESC}}",
                store: this._getStates(),
                queryMode: 'local',
                displayField: 'label',
                valueField: 'id',
                allowBlank: false,
                editable: false
            }
        ];
    },
    
    _idValidator: function(id)
    {
        return this.unavailableIds.includes(parseInt(id)) 
                ? "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID_ERROR}}" 
                : true;
    },
    
    /**
     * @private
     * Get the final states combobox's store data
     * @returns {Ext.data.Store} the final states store
     */
    _getStates: function()
    {
        var store = Ext.create('Ext.data.Store', {
            model: 'Ametys.plugins.workflow.models.WorkflowElementModel', 
            proxy: {
                type: 'ametys',
                role: 'org.ametys.plugins.workflow.dao.WorkflowStepDAO',
                methodName: 'getStatesToJson',
                methodArguments: ["workflowName", "actionId", "isInitialState"],
                reader: {
                    type: 'json',
                    rootProperty: 'data'
                }
             },
             autoLoad: true,
             listeners: {
                 'beforeload': {fn: this._onBeforeLoad, scope: this},
             }
        })
        return store;
    },
    
    /**
     * @private
     * Get the available actions combobox's store data
     * @returns {Ext.data.Store} the available actions store
     */
    _getAvailableActions: function()
    {
        var store = Ext.create('Ext.data.Store', {
            model: 'Ametys.plugins.workflow.models.WorkflowElementModel', 
            proxy: {
                type: 'ametys',
                role: 'org.ametys.plugins.workflow.dao.WorkflowTransitionDAO',
                methodName: 'getAvailableActions',
                methodArguments: ["workflowName", "stepId"],
                reader: {
                    type: 'json',
                    rootProperty: 'data'
                }
             },
             autoLoad: true,
             listeners: {
                 'beforeload': {fn: this._onBeforeLoad, scope: this},
             }
        })
        return store;
    },
    
    /**
     * @private
     * Set the workflow parameter to the request
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
     */
    _onBeforeLoad: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            workflowName: this._workflowName,
            stepId: parseInt(this._stepId),
            actionId: this._actionId ? parseInt(this._actionId) : null,
            isInitialState: this._isInitialStep()
        }));
    },
    
    /**
      * @private
      * Return true if current step is the initial step
      * @returns {Boolean} true if current step is the initial step
      */
     _isInitialStep: function()
     {
         return this._stepId == "0";
     },
     
    /**
     * @private
     * Create a dialog box
     * @param {Ext.form.field[]} items the fields for the box
     * @return {Ametys.window.DialogBox} the dialog box
     */
    _createDialogBox(items)
    {
        return Ext.create('Ametys.window.DialogBox',  {
            title : this._mode =="edit" ? "{{i18n PLUGINS_WORKFLOW_EDIT_TRANSITION_DIALOG_TITLE}}" : "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_TITLE}}",
            iconCls: "ametysicon-squares",
            width:400,
            defaultFocus: 'label',
            selectDefaultFocus: true,
            layout: 'anchor',
            defaults:
            {
                msgTarget: "side",
                cls: 'ametys',
                padding: 5,
                anchor: '100%',
                labelAlign: "top",
                labelSeparator: "",
                labelStyle: "font-weight:bold",
            },
            items : items,
            
            closeAction: 'hide',
            
            referenceHolder: true,
            defaultButton: 'validate',
            
            buttons : [
                {
                    reference: 'validate',
                    text: "{{i18n PLUGINS_WORKFLOW_CREATE_DIALOG_OK_BTN}}",
                    handler: Ext.bind(this._ok, this)
                },
                {
                    text: "{{i18n PLUGINS_WORKFLOW_CREATE_DIALOG_CANCEL_BTN}}",
                    handler: Ext.bind(this._hideDialogBox, this)
                }
            ]
        });
    },
     
    /**
     * @private
     * Create or edit transition with field values 
     */
    _ok: function ()
    {
        if (this._mode =="edit" || this._mode == "create")
        {
            if (this._hasInvalid(this._createOrEditDialogBox))
            {
                return;
            }
            var id = this._createOrEditDialogBox.getComponent('id').getValue();
            var label = this._createOrEditDialogBox.getComponent('label').getValue();
            var finalStepId = this._createOrEditDialogBox.getComponent('finalStep').getValue();
            
            if (this._mode == "create")
            {
                Ametys.plugins.workflow.dao.WorkflowTransitionDAO.createTransition([this._workflowName, parseInt(this._stepId), id, label, finalStepId], Ext.bind(this._hideDialogBox, this));
            }
            else
            {
                var actionId = parseInt(this._actionId);
                this._actionId = null
                
                Ametys.plugins.workflow.dao.WorkflowTransitionDAO.editTransition([this._workflowName, parseInt(this._stepId), actionId, id, label, finalStepId], Ext.bind(this._hideDialogBox, this));
            }
        }
        else
        {
            var availableActionField = this._addDialogBox.getComponent('availableAction');
            if (!availableActionField.isValid())
            {
                return;
            }
            var records = availableActionField.getValueRecords();
            var transitionIds = [];
            for (var i = 0; i < records.length; i++)
            {
                transitionIds.push(records[i].data.id)
            }
            
            Ametys.plugins.workflow.dao.WorkflowTransitionDAO.addTransitions([this._workflowName, parseInt(this._stepId), transitionIds], Ext.bind(this._hideDialogBox, this));
        }
    },
    
    /**
     * @private
     * Return true if a field is invalid
     * {Ametys.window.DialogBox} the dialog box
     */
    _hasInvalid: function(box)
    {
        var fields = box.query("[isFormField]");
        var hasInvalid = false;
        for (let field of fields)
        {
            if (!field.isValid())
            {
                hasInvalid = true;
            }
        }
        return hasInvalid;
    },
    
    /**
     * Hide the dialog box
     * @param {Object} response the server response
     * @param {String} response.message an error message if defined
     */
    _hideDialogBox: function (response)
    {
        if (!response || !response.message)
        {
            var dialogBox = this._mode == "add" ? this._addDialogBox : this._createOrEditDialogBox;
            dialogBox.hide();
        }
    },
    
    /**
     * @private
     * Set the create or edit box's fields initial values
     * @param {Object} response the server response 
     * @param {String} response.labels the transitions's labels
     * @param {String} response.id the transitions's id
     * @param {String[]} response.ids an array of all the workflow's transitions id
     */
    _setCreationFieldValues: function(response)
    {
        var label = response.labels;
        this.unavailableIds = response.ids;
        
        this._createOrEditDialogBox.getComponent('label').setValue(!Ext.isEmpty(label) ? label : '');
        var finalStep = this._createOrEditDialogBox.getComponent('finalStep');
        finalStep.getStore().load({
            callback: function(){
                var selectByDefault = finalStep.getStore().getData().items[0];
                if (selectByDefault)
                {
                    finalStep.setValue(selectByDefault.getId());
                }
            }
        });
        
        //generate unique id
        var id = this._getUniqueId()
        this._createOrEditDialogBox.getComponent('id').setValue(id);
        this._createOrEditDialogBox.show();
    },
    
    /**
     * @private
     * Get a unique id
     * @returns {Number} a unique id
     */
    _getUniqueId: function()
    {
        var id;
        var i = 1;
        while (Ext.isEmpty(id))
        {
            if(!this.unavailableIds.includes(i))
            {
                id = i;
            }
            i++;
        }
        return id;
    },
    
    /**
     * Edit the transition
     * @param {Ametys.message.MessageTarget} target The transition target
     */
     edit: function(target)
     {
        var targetParameters = target.getParameters();
        this._actionId = targetParameters.id;
        this._stepId = targetParameters.stepId;
        this._workflowName = targetParameters.workflowId;
        this._mode = "edit";
        if (!this._createOrEditDialogBox)
        {
            this._createOrEditDialogBox = this._createDialogBox(this._getCommonItems());
        }
        else
        {
            this._createOrEditDialogBox.setTitle("{{i18n PLUGINS_WORKFLOW_EDIT_TRANSITION_DIALOG_TITLE}}");
        }
        Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getTransitionInfos([this._workflowName, parseInt(this._actionId)], Ext.bind(this._setEditionFieldValues, this));
     },
     
    /**
     * @private
     * Set the edition form's fields with current transition values
     * @param {Object} response the server response 
     * @param {String} response.labels the transitions's label
     * @param {String} response.id the transitions's id
     * @param {String} response.finalStep the transitions's iunconditional step
     * @param {String[]} response.ids an array of all the workflow's transitions id
     */
    _setEditionFieldValues: function(response)
    {
        var label = response.labels;
        var id = response.id;
        var finalStep = response.finalStep;
        var finalStepCombo = this._createOrEditDialogBox.getComponent('finalStep');
        this.unavailableIds = response.ids;
        
        this._createOrEditDialogBox.getComponent('label').setValue(!Ext.isEmpty(label) ? label : '');
        this._createOrEditDialogBox.getComponent('id').setValue(id);
        finalStepCombo.getStore().load({
            callback: function()
            {
                finalStepCombo.setValue(finalStep);
            }
        });
        
        this._createOrEditDialogBox.show();
    },
    
     /**
      * Remove transition from parent step
      * @param {Ametys.message.MessageTarget} target The transition target
      * @param {Ametys.message.MessageTarget} stepTarget The parent step target
      */
     removeTransition: function(target, stepTarget)
     {
        var targetParameters = target.getParameters();
        this._actionId = targetParameters.id;
        this._stepId = targetParameters.stepId;
        this._workflowName = targetParameters.workflowId;
        Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getNumberOfUse([this._workflowName, parseInt(this._actionId)], Ext.bind(this._removeTransitionCB, this, [target, stepTarget], 1))
     },
     
     /**
      * @private
      * Callback of removeTransition, send confirmation message for removal
      * @param {Number} numberOfUses, how many steps use this transition
      * @param {Ametys.message.MessageTarget} target the transition to remove
      * @param {Ametys.message.MessageTarget} stepTarget The parent step target
      */
     _removeTransitionCB(numberOfUses, target, stepTarget)
     {
        var actionLabel = target.getParameters().label;
        var stepLabel = stepTarget.getParameters().label;;
        var msg = Ext.String.format("{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_CONFIRM}}", actionLabel, this._actionId, stepLabel, this._stepId);
        if (numberOfUses == 1)
        {
            msg += "{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_DEFINITLY_CONFIRM}}";
        }
        Ametys.Msg.confirm("{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_LABEL}}",
                msg,
                Ext.bind(this._doRemove, this, [target], 1),
                this
        );
     },
     
    /**
     * @private
     * Proceed to the transition removal
     * @param {String} btn The pressed button. Can only be 'yes'/'no'
     * @param {Ametys.message.MessageTarget} target The transition target to remove.
     */
    _doRemove: function(btn, target)
    {
        if (btn == 'yes')
        {
            var id = parseInt(this._actionId);
            this._actionId = null
            Ametys.plugins.workflow.dao.WorkflowTransitionDAO.removeTransition([this._workflowName, parseInt(this._stepId), id]);
        }   
    }
 });
 
 /**
  * Data model for filling  workflow element combobox store
  */
 Ext.define('Ametys.plugins.workflow.models.WorkflowElementModel', {
    extend: 'Ext.data.Model',
    
    fields: [
        {name: 'id', type: 'number'},
        {name: 'label', type: 'string'}
    ]
});