'use strict';
define(function() {
  var gcelement = function(
    gclayers,
    EditFactory,
    FeatureTypeFactory,
    FeatureAttachmentFactory,
    $filter,
    SelectManager,
    $q,
    gceditsaveAttachmentFactory,
    $rootScope,
    gaJsUtils,
    AssociationFactory,
    QueryFactory,
    gaDomUtils,
    GeometryFactory,
    RightsFactory,
    ngDialog,
    isUtils,
    FormsHelper,
    $timeout
  ) {
    return {
      templateUrl: 'js/XG/widgets/utilities/edit/views/gceditsave.html',
      restrict: 'A',
      scope: {
        map: '=map',
        fti: '=fti',
        ftid: '=ftid',
        features: '=feats',
        related: '=related',
        finish: '&finish',
        beforeFinish: '&beforeFinish',
        result: '=res',
        fields: '=?fields',
        uploadfile: '=uploadfile',
        updatesToSave: '=updatestosave',
        allfields: '=?',
        globalsave: '=?',
        // this is the config of the save button that have been clicked
        //warning! there can be multiple save button.
        saveFieldConfig: '=?',
        associations: '=?',
        objectCibleCrudList: '=?',
        objectCibleToAdd: '=?',
        breadcrumb: '=?',       // fil d'ariane d'une IS
        isStepLabels: '=?',     // labels personnalisés des étapes du fil d'ariane d'une IS
        initObjetsCibles: '&?', // initialise les objets cibles après création et sauvegarde d'une IS
        formType: '=?',         // type du formulaire (ex. 'intervention_simple', 'table_simple'...)
        activeTab: '=?',        // rang de l'onglet actif de l'étape "Attributs" d'une IS (curtemplate.activeTab)
        popupId: '=?'           // id de la ngDialog afin de limiter la recherche d'éléments HTML à la popup courante
      },
      link: function(scope, elt) {
        if (angular.isUndefined(scope.fti)) {
          scope.curfti = FeatureTypeFactory.getFeatureByUid(scope.ftid);
        } else {
          scope.curfti = scope.fti;
        }

        // Variable utilisée pour ne pas afficher le bouton d'enregistrement
        // dans le cas ou l'utilisateur n'a pas les droits sur le composant
        scope.userHasWriteRights = scope.curfti ? RightsFactory.isAllowedToWriteFeature(
          $rootScope.xgos.user, scope.curfti.uid) : true;


        /**
         * Fonction utilisée lors de la sauvegarde pour nettoyer la carte des sélections
         * et vider les variables ajoutées à "variablesToClean"
         * @type {*[]}
         */
        const variablesToClean = [];
        const cleanMapAndVariables = () => {
          // Clean map
          if (scope.map) {
            gclayers.clearhighLightFeatures();
            gclayers.clearEditLayer();
            SelectManager.clear();
          }
          // Clean variables
          for(const variable of variablesToClean) {
            scope.result[variable] = null;
          }
        };



        /**
         *
         * @returns a promise
         */
        var setRelatedObjects = function() {
          scope.currentSaveStepIndex ++;
          var defer = $q.defer();
          if (scope.related) {
            console.log(scope.related);
            scope.curfti.relations.forEach(function(r) {
              var data = angular.copy(scope.templatefeatcollect);
              if (scope.related.hasOwnProperty('features')) {
                scope.related['features'].forEach(function(fet) {
                  if (fet.id.indexOf(r.componentEnd + '.') > -1) {
                    data['features'].push(fet);
                    console.log('add' + fet.id);
                  }
                });
              } else {
                if (scope.related instanceof Array == false) {
                  data['features'].push(scope.related);
                } else {
                  scope.related.forEach(function(fet) {
                    if (fet.id.indexOf(r.componentEnd + '.') > -1) {
                      data['features'].push(fet);
                      console.log('add' + fet.id);
                    }
                  });
                }
              }

              if (data.features.length) {
                let id = gaJsUtils.getIdInCaseEsriId(scope.features,scope.curfti);
                EditFactory.r_add(data,scope.curfti.uid,id,r.name);
              }
            });
          }
          defer.resolve();
          return defer.promise;
        };

        /**
         * clearOneRelation
         */
        const clearOneRelation = (fet, r) => {
          if (fet.id.indexOf(r.componentEnd + '.') > -1) {
            console.log('clear relations with ' + fet.id);
            return EditFactory.r_delete(
              scope.curfti.uid,
              r.name,
              scope.features.id,
              false
            );
          } else {
            console.error('clearOneRelation, this object have a wrong id: ' + fet.id);
            let defer = $q.defer();
            defer.resolve();
            return defer.promise;
          }
        };

        /**
         * clear all relations in CAJ_REL_AR
         * @returns a promise
         */
        const clearAllRelations = function() {
          let promises = [];
          if (scope.related) {
            for (let componentRelation of scope.curfti.relations) {
              let relatedObjects;
              if (scope.related.hasOwnProperty('features')) {
                relatedObjects = scope.related['features'];
              } else {
                relatedObjects = scope.related;
              }
              if (Array.isArray(relatedObjects)) {
                for(let relatedObject of relatedObjects) {
                  promises.push(
                    clearOneRelation(relatedObject, componentRelation));
                }
              }
            }
          } else {
            let defer = $q.defer();
            defer.resolve();
            promises.push(defer.promise);
          }
          return $q.all(promises);
        };

        /**
         * handles the saving process
         *
         */
        scope.handleSaving = () => {
          //
          // NO CODE HERE. ONLY FUNCTIONS CALLS
          //
          if (canWeSave() === true) {
            // pre Save
            handleRelationAndAssossiation()
              .then(verifyMandatoryFields)
              .then(AppliquerPreValidationFunctions)
              .then(executeBeforeFinish)
              .then(checkMustHaveGeometryOk)
            // save
            .then(save)
            // after save
            .then(clearAllRelations)
            .then(setRelatedObjects)
            .then(scope.finish)
            .then(manageObjectsCible)
            .then(afterObjectsCibleManagement)
            .then(modifyBreadcrumbAfterSave)
            .then(cleanMapAndVariables)
            // affiche l'étape "Pièces-jointes" et remplace "Objets sélectionnés" par "Objets cibles"
            .finally(hideProgressPopup);
          }
        };

        /**
         * check if we need to save
         * @returns
         */
        const canWeSave = () => {
          let doSave = true;

          // check if we know what feature type we need to save
          if (!(scope.curfti && scope.curfti.uid && scope.curfti.uid.length
              > 0)) {
            //the fti needs to be set for the button (in the formbuilder)
            require('toastr').error($filter('translate')
            ('tools.builder.intervention_simple.fti_is_not_set'));
            doSave = false;
          }

          // we may be in formBuilder in this case
          if (scope.features === undefined) {
            require('toastr').warning($filter('translate')
            ('tools.builder.intervention_simple.no_feature_to_save'));
            doSave = false;
          }

          return doSave;
        };

        /**
         * @returns resolve if mandatory fields are filled
         *    reject otherwise
         */
        const verifyMandatoryFields = function() {
          var defer = $q.defer();
          if (scope.allfields && scope.allfields.length > 0 || scope.fields) {
            let required = false;
            //spécial HPO (we can remove it when we add required on data list)
            if (scope.curfti.alias === 'HPO Chantiers'
                && (scope.features.geometry === undefined
                || scope.features.geometry === '')) {
              require('toastr').error(
                $filter('translate')(
                  'hpo.chantiers.creation.error_no_canalisation')
              );
              defer.reject();
              return defer.promise;
            }
            // recuperation des champs obligatoires
            scope.requiredFields = [];
            if (scope.allfields && scope.allfields.length > 0) {
              for (let j = 0; j < scope.allfields.length; j++) {
                let fields = scope.allfields[j].template;
                if (angular.isDefined(fields)) {
                  fillRequiredFields(fields);
                }
              }
            } else if (angular.isDefined(scope.fields)) {
              fillRequiredFields(scope.fields);
            }

            //voir si un des champs obligatoire n'est pas renseigné
            if (scope.requiredFields.length > 0 && scope.features) {

              // KIS-3172: préciser les champs manquant dans le message de vérification des champs obligatoires
              const missingMandatoryFields = scope.requiredFields.filter(field => !gaJsUtils.notNullAndDefined(scope.features.properties[field.name]));
              if (Array.isArray(missingMandatoryFields) && missingMandatoryFields.length > 0) {
                require('toastr').error($filter('translate')('tools.builder.missMandatoryFields')
                    + '<br>' + missingMandatoryFields.map(field => field.label).join(',<br>'));

                // met en évidence le 1er champ requis manquant
                highlightFirstMissingField(missingMandatoryFields);

                defer.reject();
                return defer.promise;
              }
            }
            if (required === true) {
              require('toastr').error(
                $filter('translate')('calendar.event.info')
              );
              defer.reject();
              return defer.promise;
            }
          }

          defer.resolve();
          return defer.promise;
        };

        const executeBeforeFinish = () => {
          if (scope.beforeFinish) {
            return scope.beforeFinish();
          } else {
            //if we don't have beforeFinish -> just return empty promise
            let defer = $q.defer();
            defer.resolve();
            return defer.promise;
          }
        };

        /**
         * this function should be splitted
         * @returns
         */
        const handleRelationAndAssossiation = () => {
          var defer = $q.defer();
          // Gestion des Objets en relation / association
          if(scope.allfields && scope.saveFieldConfig
            && scope.saveFieldConfig.autoCreateGeometry === true) {

            // Set contenant l'union des features
            const allFeatures = new Set();

            // Features selectionnés dans l'édition en cours par association ou relation
            let newFeatures;
            if(scope.saveFieldConfig.related) {
              newFeatures = scope.result[scope.saveFieldConfig.related];
              variablesToClean.push(scope.saveFieldConfig.related);
            } else if (scope.saveFieldConfig.association) {

              newFeatures = scope.result[scope.saveFieldConfig.association];
              variablesToClean.push(scope.saveFieldConfig.association);
            }
            if(newFeatures && newFeatures.features.length > 0) {
              for (const feature of newFeatures.features) {
                allFeatures.add(feature);
              }
            }

            // Features déjà présente dans l'intervention simple
            if(scope.saveFieldConfig.association
                && scope.allfields.association && scope.allfields.association.associationResultat) {
              const oldFeatures = scope.result[scope.allfields.association.associationResultat];
              if (oldFeatures && Array.isArray(oldFeatures.features) && oldFeatures.features.length > 0) {
                for (const feature of oldFeatures.features) {
                  allFeatures.add(feature);
                }
              }
            }

            // On refait la géométrie seulement si
            // 1. Il y a de nouveaux éléments
            // 2. Des éléments ont été supprimés
            if(allFeatures.size > 0 &&
              (newFeatures && newFeatures.features.length > 0
              || scope.result.objectCibleCrudList.length > 0)) {
              // Création de la géométrie
              GeometryFactory.unionandbuffer(Array.from(allFeatures), 'ROUND', 3)
                .then((res1) => {
                  scope.result.current.geometry = res1.data;
                  defer.resolve();
                }, () => {
                  swal({
                    title: '',
                    text: $filter('translate')
                    ('tools.builder.intervention_simple.error_auto_create_geometry'),
                    type: 'warning',
                    confirmButtonColor: '#F50072',
                    showCancelButton: true,
                    cancelButtonText: $filter('translate')('common.no'),
                    confirmButtonText: $filter('translate')('common.yes'),
                    showConfirmButton: true,
                  },
                  (isConfirm) => {
                    if (isConfirm) {
                      defer.resolve();
                    } else {
                      defer.reject();
                    }
                  });
                }
                );
            } else {
              defer.resolve();
            }
          } else {
            defer.resolve();
          }
          return defer.promise;
        };

        const checkMustHaveGeometryOk = () => {
          var defer = $q.defer();
          //verifier si la geometrie est obligatoire et si elle est définie
          if (scope.saveFieldConfig.mustHaveGeometry === true && scope.features
              && scope.features.geometry === undefined) {
            require('toastr').warning($filter('translate')
            ('tools.builder.intervention_simple.geometry_must_be_defined'));
            defer.reject();
          } else {
            defer.resolve();
          }
          return defer.promise;
        };

        function AppliquerPreValidationFunctions() {

          if(angular.isDefined(scope.fields) && angular.isDefined(scope.fields.tabs)){
            scope.fields.tabs.forEach((tab) => {
              if(angular.isDefined(tab.fields)){
                tab.fields.forEach((field) => {
                  if (angular.isDefined(field.preValidationFunction) && angular.isDefined(field.preValidationFunction.leftData) &&
                    field.preValidationFunction.leftData.length > 0) {
                    AppliquerPreValidationFunctionsToAttribute(field.config.name, field.preValidationFunction.leftData);
                  }
                });
              }
            });
          }
          if(angular.isDefined(scope.fields) && angular.isDefined(scope.fields.fields)){
            scope.fields.fields.forEach((field) => {
              if (angular.isDefined(field.preValidationFunction) && angular.isDefined(field.preValidationFunction.leftData) &&
                field.preValidationFunction.leftData.length > 0) {
                AppliquerPreValidationFunctionsToAttribute(field.config.name, field.preValidationFunction.leftData);
              }
            });
          }
          let defer = $q.defer();
          defer.resolve();
          return defer.promise;
        }

        function AppliquerPreValidationFunctionsToAttribute(attribute,functionList) {
          functionList.forEach((func) => {
            switch (func.name) {
              case 'setUserAttribut':
                scope.features.properties[attribute] = $rootScope.xgos.user.login;
                break;
              case 'setDateAttribut':
                scope.features.properties[attribute] = new Date();
                break;
              case 'setUserCreationOnly':
                if (!scope.features.id) {
                  scope.features.properties[attribute] = $rootScope.xgos.user.login;
                }
                break;
              case 'setDateCreationOnly':
                if (!scope.features.id) {
                  scope.features.properties[attribute] = new Date();
                }
                break;
              default: break;
            }
          });
        }

        var fillRequiredFields = function (fields) {

          // Parcourir les champs principaux
          for (let i = 0; i < fields.fields.length; i++) {
            if (fields.fields[i].required) {
              scope.requiredFields.push({
                name: fields.fields[i].config.name,
                label: fields.fields[i].label
              });
            }
          }

          // Vérifier les onglets (s'il y en a)
          if (angular.isDefined(fields.tabs)) {
            for (let i = 0; i < fields.tabs.length; i++) {
              for (let j = 0; j < fields.tabs[i].fields.length; j++)
                if (fields.tabs[i].fields[j].required) {
                  scope.requiredFields.push({
                    name: fields.tabs[i].fields[j].config.name,
                    label: fields.tabs[i].fields[j].label,
                    tab: i
                  });
                }
            }
          }
        };

        let clearAssociation = () => {
          for (const field of scope.fields.fields) {
            if(field.type === 'associations' && field.config.clearAssociation){
              scope.associations[field.config.name] = [];
            }
          }

          if (angular.isDefined(scope.fields.tabs)) {
            for (const tab of scope.fields.tabs) {
              for (const field of tab.fields) {
                if(field.type === 'associations' && field.config.clearAssociation){
                  scope.associations[field.config.name] = [];
                }
              }
            }
          }
        };


        const manageObjectsCible = () => {
          scope.currentSaveStepIndex ++;
          const promises = [];
          if(scope.associations && gaJsUtils.notNullAndDefined($rootScope.xgos, 'portal.parameters.mainDB')){
            const updateAllAssociations = AssociationFactory.updateAllAssociations($rootScope.xgos.portal.parameters.mainDB, scope.features.id, scope.associations).finally(() => {
              clearAssociation();
            });
            promises.push(updateAllAssociations);
          }
          if(scope.objectCibleCrudList && scope.objectCibleCrudList.length>0){
            let count = 0;
            scope.objectCibleCrudList.forEach((objCible) => {
              count++;
              if (objCible.action === 'd') {
                let removeObjectCible = AssociationFactory.removeObjectCible($rootScope.xgos.portal.parameters.mainDB, objCible.associationToLoad, objCible.FeatureID, objCible.couche, objCible.identifiant).then(() => {
                  if (count == scope.objectCibleCrudList.length) {
                    scope.$emit('refreshObjectCible');
                  }
                }, (err) => {
                  require('toastr').error($filter('translate')(err.data));
                });
                promises.push(removeObjectCible);
              }
              if (objCible.action === 'u') {
                let updateObjectCible = AssociationFactory.updateObjectCible($rootScope.xgos.portal.parameters.mainDB, objCible.associationToLoad, objCible.FeatureID, objCible.couche, objCible.identifiant, objCible.alias).then(() => {
                  if (count == scope.objectCibleCrudList.length) {
                    scope.$emit('refreshObjectCible');
                  }
                }, (err) => {
                  require('toastr').error($filter('translate')(err.data));
                });
                promises.push(updateObjectCible);
              }
            });
          }
          gclayers
            .getDrawLayer()
            .getSource()
            .clear();

          return $q.all(promises);
        };

        const afterObjectsCibleManagement = () => {
          let defer = $q.defer();
          if(scope.objectCibleToAdd && scope.objectCibleToAdd.features && scope.objectCibleToAdd.features.length>0 &&
            scope.fields && scope.fields.association && scope.fields.association.associationToLoad &&
            scope.fields.association.associationToLoad.length>0){
            const sendData = {
              'associationToLoad': scope.fields.association.associationToLoad,
              'objectCibleToAdd': scope.objectCibleToAdd.features
            };
            AssociationFactory.addObjectsCibles($rootScope.xgos.portal.parameters.mainDB, scope.features.id, sendData)
              .then(()=>{
                scope.$emit('refreshObjectCible');
                // re-initialise la liste des objets sélectionnés
                scope.objectCibleToAdd = null;
                defer.resolve();
              },(err)=>{
                require('toastr').error($filter('translate')(err.data));
                defer.resolve();
              });
          }else{
            defer.resolve();
          }
          return defer.promise;
        };


        /**
         * [save description]
         * @return {Promise} retourne une promise afin d'être chaîné dans l'appel handleSaving
         */

        const save = () => {

          const defer = $q.defer();

          displaySaveStepProgressPopup();

          scope.templatefeatcollect = {
            type: 'FeatureCollection',
            features: [],
          };

          scope.editfeatures = angular.copy(scope.templatefeatcollect);

          if (scope.features instanceof Array) {
            scope.editfeatures.features = scope.features;
          } else {
            scope.editfeatures.features.push(scope.features);
          }

          const srid = scope.map
            ? scope.map
              .getView()
              .getProjection()
              .getCode()
            : '';

          // Suppression des éléments de la feature sur lesquels l'utilisateur n'a pas les droits en écriture
          scope.limitedByRightsFeatures = angular.copy(scope.editfeatures);
          for (const feature of scope.limitedByRightsFeatures.features) {
            for (const key of Object.keys(feature.properties)) {
              if (!RightsFactory.isAllowedToWriteAttributesInFeature($rootScope.xgos.user,
                scope.curfti.uid, key)) {
                delete feature.properties[key];
              }
            }
          }

          if (scope.features.id) {
            // UPDATE
            scope.isNew = false;
            return updateFeature(srid, defer);
          } else {
            // CREATE
            scope.isNew = true;
            return createFeature(srid, defer);
          }
        };

        /**
         * Met à jour les propriétés de l'objet (limité aux attributs accessibles pour l'utilisateur)
         * @param srid code de la projection de la carte
         * @param defer conteneur de la promesse issu de la méthode appelante #save
         * @return {Promise}
         */
        const updateFeature = (srid, defer) => {

          if (scope.editfeatures && scope.editfeatures.features) {

            // supprime les caractères spéciaux du nom des fichiers attachés (plante applyEdits)
            replaceAttachmentAttributeSpecialChars(scope.editfeatures.features);

            // supprime la propriété isReprog d'une IS reprogrammée
            deleteIsreprogProperty(scope.editfeatures.features);
          }

          // Ne plus plus à jour que les properties concernées
          EditFactory.updatespecifiedproperties(scope.curfti.uid, scope.limitedByRightsFeatures, srid)
            .then((res) => {
              if (Array.isArray(res.data.update) && res.data.update.length === 1) {

                // Renvoie vers le formRender pour que celui-ci mette à jour l'affichage des données dans le gcDatatable
                scope.$emit('updateGcdatatable', {isNew: false, idPopup: FormsHelper.getIdDiv(elt)});
              } else {
                require('toastr').error($filter('translate')('tools.builder.form.edit_update_error'));
              }
            })
            .catch((error) => {
              console.error(error);
              require('toastr').error($filter('translate')('tools.builder.form.edit_update_error'));
            }).finally(() => {
              defer.resolve();
          });
          return defer.promise;
        };

        /**
         * Enregistre un nouvel objet depuis un formulaire
         * @param srid code de la projection de la carte
         * @param defer conteneur de la promesse issu de la méthode appelante #save
         * @return {Promise}
         */
        const createFeature = (srid, defer) => {

          if (scope.editfeatures && scope.editfeatures.features) {

            // supprime les caractères spéciaux du nom des fichiers attachés (plante applyEdits)
            replaceAttachmentAttributeSpecialChars(scope.editfeatures.features);

            // supprime la propriété isReprog d'une IS reprogrammée
            deleteIsreprogProperty(scope.editfeatures.features);
          }

          EditFactory.add(scope.curfti.uid, scope.editfeatures, srid, '', '', true)
            .then((res) => {
              const createData = res.data.create;
              if (createData.length === 1) {
                require('toastr').success('ok');

                const completeId = createData[0].id;
                const completeFeature = JSON.parse(createData[0].json);

                // Suite à la création d’une intervention simple telle qu’un curage,
                // le champ id, calculé à l’enregistrement, est inscrit dans la variable current
                // comme un texte (encadré par des guillemets) alors que c’est un champ de type ENTIER (KIS-3224)
                convertJsonNumberStringToNumber(completeFeature);

                Object.assign(scope.features, completeFeature);

                // aucune précaution n'est prise ci-dessous pour savoir si features est un array ou un objet
                scope.features.id = completeId;

                if (Array.isArray(scope.features)) {
                  for (const feature of scope.features) {
                    attachFeatureFiles(feature, createData);
                  }
                } else {
                  const feature = scope.features;
                  attachFeatureFiles(feature, createData);
                }
                // mise à jour de la datatable de la liste des résultats de l'IS
                // on identifie la popup concernée par son id
                scope.$emit('updateGcdatatable', {isNew: true, idPopup: FormsHelper.getIdDiv(elt)});
                gclayers.refreshlayerByid(scope.curfti.uid, scope.map);

              } else {
                alert($filter('translate')(
                  'tools.builder.intervention_simple.intervention_not_saved'));
              }
            })
            .catch((error) => {
              if (scope.curfti.type === 'esri') {
                alert($filter('translate')
                ('tools.builder.intervention_simple.intervention_not_saved'));
              }
              require('toastr').error($filter('translate')('tools.builder.form.edit_create_error'));
              console.error(error);
            }).finally(() => {
              defer.resolve();
          });
          return defer.promise;
        };

        /**
         * display a dégueu success message only for arcgis
         *
         * @param {object} fti définition du composant maître de l'IS
         * @param {object} feat
         * @param {object} createdFeature objet créé retourné du back {esriId, json}
         */
        const displayEsriSuccessMessage = (fti, feat, createdFeature) => {
          if (fti.type === 'esri' && createdFeature) {
            let esriId;
            if (fti.esriIdField !== 'objectid' && createdFeature.hasOwnProperty('esriId') && gaJsUtils.notNullAndDefined(createdFeature.esriId)) {
              // je ne sais pas pourquoi on fait le changement de valeur de l'id içi c'est dégueu ...
              // j'ai parsé l'id pour corriger le problème du ticket KIS-3224, j'ai eu peur de casser autre chose sinon il faut supprimer toutes la ligne
              feat.properties[fti.esriIdField] = isNaN(createdFeature.esriId)?createdFeature.esriId:parseInt(createdFeature.esriId);
              esriId = createdFeature.esriId;
            } else if (gaJsUtils.notNullAndDefined(feat.id)) {
              if (typeof feat.id === 'string' && feat.id.includes('.')) {
                esriId = feat.id.split('.')[feat.id.split('.').length - 1];
              } else {
                esriId = feat.id;
              }
            }
            //display creation success message
            alert($filter('translate')('tools.builder.intervention_simple.intervention_created') + ' \'' + esriId + '\'');
          }
        };

        /**
         * [removefeatures description]
         * @return {No} [description]
         */
        scope.removefeatures = function() {
          scope.features = {};
          /*
                     scope.$apply(function(){
                     scope.features={};
                     });
                     */
        };

        /**
         * Récupère la configuration du champ dans le formulaire IS dont le nom d'attribut est fourni en paramètre
         * @param {string} attributeName nom de l'attribut dont on veut récupérer le champ
         * @return {null|object} configuration du formulaire concernant l'attribut dont le nom est fourni en paramètre (scope.fields.tabs.fields[i])
         */
        const findAttributeFieldInTabs = (attributeName) => {
          if (scope.fields) {
            const allFields = [
              ...(Array.isArray(scope.fields.tabs) && scope.fields.tabs.length >0 ? scope.fields.tabs : []),
              ...(Array.isArray(scope.fields.fields) && scope.fields.fields.length > 0 ? [scope.fields] : []),
            ];
        
            for (const tab of allFields) {
              const attachmentField = tab.fields.find(field => field.config.name === attributeName);
              if (attachmentField) {
                return attachmentField;
              }
            }
          }
    
          return null;
        };

        /**
         * Détermine quels sont les attributs de type gcattachment(s) du fti courant ayant des fichiers pour l'objet courant
         * @param feature objet contenant potentiellement un champ attachment saisi
         * @return {object[]} tableau des attributs du fti présents et remplis dans l'objet du formulaire
         */
        const findAttachmentAttributes = (feature) => {
          const attachmentTypes = ['g2c.attachments', 'g2c.attachment'];
          return scope.curfti.attributes.filter(attr => attachmentTypes.includes(attr.type))
              .filter(attr => feature.properties.hasOwnProperty(attr.name)
                  && feature.properties[attr.name]!== null && feature.properties[attr.name].length > 0);
        };

        /**
         * Après enregistrement de l'objet et attachement des fichiers,
         * affiche une alerte (cas esri) ou bien un message toastr (cas postgis)
         * @param createData tableau des objets créés par le formulaire après renvoi du backend
         */
        const displaySuccessMessage = (createData) => {
          if (scope.curfti.type === 'esri' && Array.isArray(createData) && createData.length > 0
              && scope.formType === 'intervention_simple') {
            displayEsriSuccessMessage(scope.curfti, scope.features, createData[0]);
          } else {
            if (scope.formType === 'intervention_simple') {
              require('toastr').success($filter('translate')('tools.builder.intervention_simple.intervention_created_withoutId'));
            } else {
              require('toastr').success($filter('translate')('tools.builder.intervention_simple.object_form_created'));
            }
          }
        };

        /**
         * Copie les fichiers attachés du dossier "UPLOAD" vers le dossier "ATTACHMENTS"
         * @param feature objet en cours de création dans le formulaire (scope.current)
         * @param createData tableau de tous les objets renvoyés du back après enregistrement
         */
        const attachFeatureFiles = (feature, createData) => {
          const attachmentAttributes = findAttachmentAttributes(feature);
          if (attachmentAttributes.length > 0) {
            const attachAttributeFilesPromises = [];
            for (const attachedAttribute of attachmentAttributes) {

              // on recherche le champ du formulaire car celui-ci contient le nom du sous-dossier
              // du répertoire "UPLOAD" contenant les fichiers attachés
              const attachmentField = findAttributeFieldInTabs(attachedAttribute.name);
              if (attachmentField && attachmentField.attachmentId) {

                // copie les fichiers depuis le dossier UPLOAD vers le dossier ATTACHMENTS
                const promise = FeatureAttachmentFactory.attachImportedFiles(attachmentField.attachmentId,
                    scope.curfti.uid, [attachedAttribute.name], createData);
                attachAttributeFilesPromises.push(promise);
              }
            }
            if (attachAttributeFilesPromises.length > 0) {
              $q.all(attachAttributeFilesPromises).then(
                  () => {
                    displaySuccessMessage(createData);
                  },
                  () => {
                    require('toastr').warning(
                        $filter('translate')('tools.builder.failtoAttachFilesAfterCreation'));
                  }
              );
            }
          } else {
            displaySuccessMessage(createData);
          }
        };

        /**
         * Remplace les caractère spéciaux des attributs attachment(s).
         * Impossible d'effectuer l'applyEdits ArcGIS en présence de caractères spéciaux dans ces champs.
         * @param {object|object[]} feature objet ou tableau d'objets du formulaire à enregistrer
         */
        const replaceAttachmentAttributeSpecialChars = (feature) => {
          if (Array.isArray(feature)) {
            for (const subFeature of feature) {
              replaceAttachmentAttributeSpecialChars(subFeature);
            }
          } else {
            const attachmentAttributes = findAttachmentAttributes(feature);
            for (const attribute of attachmentAttributes) {
              if (typeof feature[attribute.name] === 'string') {
                feature[attribute.name] = gaJsUtils.replaceSpecialChars(feature[attribute.name]);
              }
            }
          }
        };

        let saveProgressPopup;
        /**
         * Ouvre la popup de l'état d'avancement de la sauvegarde de l'objet du formulaire
         */
        const displaySaveStepProgressPopup = () => {
          scope.saveSteps = [
            $filter('translate')('tools.builder.saveSteps.saveFeature'),
            $filter('translate')('tools.builder.saveSteps.saveRelations'),
            $filter('translate')('tools.builder.saveSteps.saveAssociations')
          ];
          scope.saveStepsLength = 3;
          scope.currentSaveStepIndex = 0;

          saveProgressPopup = ngDialog.open({
            template:'js/XG/widgets/utilities/edit/views/modals/gceditsave.step.progress.html',
            className: 'ngdialog-theme-plain width400 noclose',
            closeByDocument: false,
            scope: scope,
          });
        };

        /**
         * Ferme la popup de l'état d'avancement de la sauvegarde de l'objet du formulaire
         */
        const hideProgressPopup = () => {
          if (saveProgressPopup) {
            saveProgressPopup.close();
          }
        };

        /**
         * Après enregistrement d'un nouvel objet, modifie le fil d'ariane de l'IS.<ul><li>
         * L'étape "Pièces-jointes" est apparu dans le fil d'ariane.</li><li>
         * Le bouton « Suivant » est apparu dans le bandeau supérieur afin de passer à l'étape Pièces-jointes.</li><li>
         * L'élément Objets sélectionnés a été remplacé par Objets cibles.</li></ul>
         */
        const modifyBreadcrumbAfterSave = () => {
          // si le formulaire possède un breadcrumb alors il s'agit d'une IS
          if (scope.breadcrumb) {
            if (scope.isNew) {

              // l'ordre des étapes d'un nouvel objet après enregistrement
              // est différent de l'ordre des étapes d'un objet existant édité
              isUtils.navigationArray.edition = [0, 1, 2];

              if (!scope.breadcrumb.hideAttachmentsStep) {
                // ajouter l'étape "Pièces-jointes"
                isUtils.addAttachmentsStep(scope.breadcrumb.steps, scope.isStepLabels);
                if (!isUtils.navigationArray.edition.includes(3)) {
                  isUtils.navigationArray.edition.push(3);
                }

                scope.breadcrumb.goNext = true;
              }
            }

            // mode création: changer l'étape "Objets sélectionnés" pour "Objets cibles"
            // mode édition: supprime l'étape "Objets sélectionnés"
            isUtils.replaceSelectedObjStep(scope.breadcrumb.steps, scope.isStepLabels);
            if (!scope.isNew && scope.breadcrumb.currentStep === 4) {
              scope.breadcrumb.currentStep = 1;
            }

            // supprime la variable des objets sélectionnés simplifiés (servant uniquement à remplir la table de l'étape "Objets sélectionnés")
            if (scope.result.isSelectedObj) {
              scope.result.isSelectedObj = null;
            }

            // variables à nettoyer
            variablesToClean.push(scope.saveFieldConfig.related);
            variablesToClean.push(scope.saveFieldConfig.association);

            // met à jour la table de l'étape "Objets cibles"
            scope.initObjetsCibles();

            // réinitialise le flag de la sélection d'objets
            isUtils.hasSelectedFeatures = false;

            // réinitialise le flag de la modification de l'intervention simple
            isUtils.isForm[elt[0].closest('.popupContainer').id].isDirty = false;
          }
          hideProgressPopup();
        };

        /**
         * Met en évidence le 1er champ requis non rempli au clic sur le bouton "Enregistrer"
         * La mise en évidence est un clignotement de la bordure de couleur rouge.
         * @param {object[]} missingMandatoryFields champs requis non remplis
         */
        const highlightFirstMissingField = (missingMandatoryFields) => {
          if (Array.isArray(missingMandatoryFields) && missingMandatoryFields.length > 0) {
            if (missingMandatoryFields[0].hasOwnProperty('tab') && Number.isInteger(scope.activeTab) && missingMandatoryFields[0].tab !== scope.activeTab) {
              scope.activeTab = missingMandatoryFields[0].tab;
            }
            if (gaJsUtils.notNullAndDefined(scope.popupId) && missingMandatoryFields[0].hasOwnProperty('name')) {

              // recherche la popup du formulaire
              const popup = document.getElementById(scope.popupId);
              if (popup) {
                const dialog = popup.nextElementSibling;
                if (dialog) {

                  // recherche le champ requis non saisi dans le formulaire (soit un input, soit un select)
                  const firstMissingFormField = dialog.querySelector('#' + missingMandatoryFields[0].name);
                  if (firstMissingFormField) {
                    let firstMissingElement = firstMissingFormField.querySelector('input');
                    if (!firstMissingElement) {
                      firstMissingElement = firstMissingFormField.querySelector('select');
                    }

                    if (firstMissingElement) {
                      // ajoute la classe highlight
                      firstMissingElement.classList.add('input-blink');
                      $timeout(() => {
                        // supprime la classe highlight et ré-initialise l'effet pour le prochain clic
                        firstMissingElement.classList.remove('input-blink');
                      }, 3000);
                    }
                  }
                }
              }
            }
          }
        };
        /* Supprime la propriété isReprog d'un objet IS avant sauvegarde
        * @param {object|object[]} features objet ou tableau d'objets IS
        */
        const deleteIsreprogProperty = (features) => {
          if (Array.isArray(features)) {
            for (const feature of features) {
              deleteIsreprogProperty(feature);
            }
          } else {
            if (features.hasOwnProperty('isReprog')) {
              delete features.isReprog;
            }
          }
        };

        /**
         * Assure le respect du type des propriétés numériques après enregistrement
         * et parse du json issu du back-end.<br>
         * <a href="https://altereo-informatique.atlassian.net/browse/KIS-3224">
         *   [DEA - IS] : mauvais formatage du champ id dans la variable geojson après création
         * </a>
         * @param {object} completeFeature objet enregistré renvoyé du back-end et parsé depuis la string json
         */
        const convertJsonNumberStringToNumber = (completeFeature) => {
          if (completeFeature.hasOwnProperty('properties')
              && Object.keys(completeFeature.properties).length > 0
              && Array.isArray(scope.curfti.attributes) && scope.curfti.attributes.length > 0) {
            const numberTypes = ['Integer', 'Double'];
            for (let [prop, value] of Object.entries(completeFeature.properties)) {
              if (typeof value === 'string') {
                const attribute = scope.curfti.attributes.find(attr => attr.name === prop);
                if (attribute && numberTypes.includes(attribute.type)) {
                  try {
                    switch(attribute.type) {
                      case 'java.lang.Integer':
                        value = Number.parseInt(value);
                        break;
                      case 'java.lang.Double':
                        value = Number.parseFloat(value);
                        break;
                      default:
                        value = Number.parseFloat(value);
                    }
                  } catch(e) {
                    console.error(e);
                  }
                }
              }
            }
          }
        };
      },
    };
  };

  gcelement.$inject = [
    'gclayers',
    'EditFactory',
    'FeatureTypeFactory',
    'FeatureAttachmentFactory',
    '$filter',
    'SelectManager',
    '$q',
    'gceditsaveAttachmentFactory',
    '$rootScope',
    'gaJsUtils',
    'AssociationFactory',
    'QueryFactory',
    'gaDomUtils',
    'GeometryFactory',
    'RightsFactory',
    'ngDialog',
    'isUtils',
    'FormsHelper',
    '$timeout'
  ];
  return gcelement;
});
