text: (function ($) {
      "use strict";
      window.Mapbender.Digitizer = Mapbender.Digitizer || {};
  
      /**
       * @param {*} owner jQueryUI widget instance
       * @param {ol.PluggableMap} olMap
       * @param {Mapbender.Digitizer.DigitizingControlFactory} controlFactory
       *
       * @constructor
       */
      Mapbender.Digitizer.FeatureEditor = function(owner, olMap, controlFactory) {
          this.owner = owner;
          this.olMap = olMap;
          this.controlFactory = controlFactory;
          this.activeInteraction = null;
          this.paused_ = false;
          this.tools_ = {};
          this.modifyingCollection_ = new ol.Collection([]);
      };
  
      Object.assign(Mapbender.Digitizer.FeatureEditor.prototype, {
          /**
           * @param {boolean} state
           */
          setActive: function(state) {
              if (this.activeInteraction) {
                  this.activeInteraction.setActive(state);
              }
              // cannot resume from this
              this.paused_ = false;
          },
          pause: function() {
              if (!this.paused_ && this.activeInteraction && this.activeInteraction.getActive()) {
                  this.activeInteraction.setActive(false);
                  this.paused_ = true;
              }
          },
          resume: function() {
              if (this.paused_ && this.activeInteraction) {
                  this.activeInteraction.setActive(true);
              }
              this.paused_ = false;
          },
          toggleTool: function(toolName, schema, state) {
              var tool = this.getDrawingTool(toolName, schema);
              if (state) {
                  if (this.activeInteraction && this.activeInteraction !== tool) {
                      console.warn("WARNING: enabling multiple drawing tools at the same time", toolName, this.activeInteraction);
                  }
              }
              this.activeInteraction = state && tool || null;
              tool.setActive(!!state);
          },
          setEditFeature: function(feature) {
              this.modifyingCollection_.forEach(function(feature) {
                  feature.set('editing', false);
              });
              this.modifyingCollection_.clear();
              if (feature) {
                  feature.set('editing', true);
                  this.modifyingCollection_.push(feature);
              }
          }
      });
  
      Mapbender.Digitizer.Toolset = function(owner) {
          this.owner = owner;
          this.listenedSchemas_ = {};
          this.unsavedFeatures_ = {};
      };
  
      Mapbender.Digitizer.Toolset.prototype = {
          iconMap_: {
              drawCircle: "icon-draw-circle",
              drawDonut: "icon-draw-donut",
              drawEllipse: "icon-draw-ellipse",
              drawLine: "icon-draw-line",
              drawPoint: "icon-draw-point",
              drawPolygon: "icon-draw-polygon",
              drawRectangle: "icon-draw-rectangle",
              modifyFeature: "icon-modify-feature",
              moveFeature: "icon-move-feature"
          },
          /**
           * @param {Object} schema
           * @param {Array<Element>} [dmToolset] for reintegration (data-manager's entire tool set is a few [optional] buttons)
           */
          renderButtons: function(schema, dmToolset) {
              var $groupWrapper = $(document.createElement('div'))
                  // @see https://getbootstrap.com/docs/3.4/components/#btn-groups-toolbar
                  .addClass('btn-toolbar')
              ;
              var utilityButtons = _.union(this.extractDmButtons_(dmToolset || []), this.renderUtilityButtons(schema));
              if (utilityButtons.length) {
                  $groupWrapper.append(this.renderButtonGroup_(utilityButtons));
              }
              var geometryButtons = this.renderGeometryToolButtons(schema);
              if (geometryButtons.length) {
                  $groupWrapper.append(this.renderButtonGroup_(geometryButtons));
              }
              return $groupWrapper.get(0);
          },
          /**
           * @param {Object} schema
           * @return {Array<String>}
           * @static
           */
          getValidToolNames: function(schema) {
              switch (schema.featureType.geomType) {
                  case 'point':
                  case 'multipoint':
                      return ['drawPoint', 'moveFeature'];
                  case 'line':
                  case 'multiline':
                      return ['drawLine', 'modifyFeature', 'moveFeature'];
                  case 'polygon':
                  case 'multipolygon':
                      return ['drawPolygon', 'drawRectangle', 'drawCircle', 'drawEllipse', 'drawDonut', 'modifyFeature', 'moveFeature'];
                  default:
                      // everything
                      return [
                          'drawPoint',
                          'drawLine',
                          'drawPolygon', 'drawRectangle', 'drawCircle', 'drawEllipse', 'drawDonut',
                          'modifyFeature', 'moveFeature'
                      ];
              }
          },
          pause: function() {
              // @todo: reintegrate with FeatureEditor :)
              $('button.-fn-toggle-tool', this.owner.element).prop('disabled', true);
          },
          resume: function() {
              // @todo: reintegrate with FeatureEditor :)
              $('button.-fn-toggle-tool', this.owner.element).prop('disabled', false);
          },
          getDefaultGeometryToolNames: function(schema) {
              return this.getValidToolNames(schema);
          },
          getGeometryToolNames: function(schema) {
              if (schema.allowDigitize) {
                  var toolNames = schema.toolset && schema.toolset.map(function(tc) {
                      // Historic Digitizer / vis-ui dependency quirk: toolset
                      // configuration is a list of objects with (only) a type property
                      return tc.type;
                  });
                  if (!toolNames) {
                      toolNames = this.getDefaultGeometryToolNames(schema);
                  }
                  var validNames = this.getValidToolNames(schema);
                  // always append modify and move
                  var standardTools = ['modifyFeature', 'moveFeature'];
                  // Filter repeats / earlier appearances
                  toolNames = toolNames.filter(function(name) {
                      return -1 === standardTools.indexOf(name);
                  });
                  toolNames = toolNames.concat(standardTools);
                  // Reduce to valid set (NOTE: may drop "modifyFeature" again for point / multipoint
                  return toolNames.filter(function(name) {
                      return -1 !== validNames.indexOf(name);
                  });
              } else {
                  return [];
              }
          },
          renderGeometryToolButtons: function(schema) {
              var toolNames = this.getGeometryToolNames(schema);
              var buttons = [];
              for (var i = 0; i < toolNames.length; ++i) {
                  var toolName = toolNames[i];
                  var iconClass = this.iconMap_[toolName];
                  var $icon = $(document.createElement('span')).addClass(iconClass);
                  var tooltip = Mapbender.trans('mb.digitizer.toolset.' + toolName);
                  var $button = $(document.createElement('button'))
                      .attr({
                          type: 'button',
                          'data-toolname': toolName,
                          title: tooltip
                      })
                      .addClass('-fn-toggle-tool btn btn-default')
                      .append($icon)
                      .data({
                          schema: schema
                      })
                  ;
                  buttons.push($button);
              }
              return buttons;
          },
          registerEvents: function() {
              var widget = this.owner;
              widget.element.on('click', '.-fn-hide-all', function() {
                  var source = widget.getSchemaLayer(widget._getCurrentSchema()).getSource();
                  source.getFeatures().forEach(function (feature) {
                      feature.set('hidden', true);
                  });
              });
              widget.element.on('click', '.-fn-show-all', function() {
                  var source = widget.getSchemaLayer(widget._getCurrentSchema()).getSource();
                  source.getFeatures().forEach(function (feature) {
                      feature.set('hidden', false);
                  });
              });
              widget.element.on('click', '.-fn-save-all', function() {
                  var schema = widget._getCurrentSchema();
                  var source = widget.getSchemaLayer(schema).getSource();
                  var features = source.getFeatures().filter(function (feature) {
                      return feature.get('dirty');
                  });
                  widget.updateMultiple(schema, features);
              });
          },
          setSchema: function(schema) {
              var schemaName = schema.schemaName;
              if (!this.listenedSchemas_[schemaName]) {
                  var widget = this.owner;
                  this.unsavedFeatures_[schemaName] = []
  
                  var source = this.owner.getSchemaLayer(schema).getSource();
                  var unsavedFeatureList = this.unsavedFeatures_[schemaName];
                  var updateSaveAll = function() {
                      $('.-fn-save-all', widget.element).prop('disabled', !unsavedFeatureList.length);
                  }
                  var trackModified = function(feature, dirtyState) {
                      var index = unsavedFeatureList.indexOf(feature);
                      // Ignore newly created features (empty id) in tracking
                      if (dirtyState && -1 === index && widget._getUniqueItemId(schema, feature)) {
                          unsavedFeatureList.push(feature);
                          updateSaveAll();
                      }
                      if (!dirtyState && -1 !== index) {
                          unsavedFeatureList.splice(index, 1);
                          updateSaveAll();
                      }
                  };
                  var addFeature = function(feature) {
                      feature.on(ol.ObjectEventType.PROPERTYCHANGE, function (event) {
                          if (event.key === 'dirty') {
                              trackModified(feature, feature.get('dirty'));
                          }
                      });
                      trackModified(feature, feature.get('dirty'));
                  };
                  source.getFeatures().forEach(function(feature) {
                      addFeature(feature);
                  });
                  source.on(ol.source.VectorEventType.ADDFEATURE, function(event) {
                      addFeature(event.feature);
                  });
                  source.on(ol.source.VectorEventType.REMOVEFEATURE, function(event) {
                      // feature going away, remove from tracking
                      trackModified(event.feature, false);
                  });
                  // Avoid binding events again for the same schema
                  this.listenedSchemas_[schemaName] = true;
              } else {
                  // empty unsaved list (in-place)
                  this.unsavedFeatures_[schemaName].splice(0, -1);
              }
          },
          /**
           * Renders buttons that do NOT represent geometry interactions
           * @param schema
           * @return {{}}
           * @static
           */
          renderUtilityButtons: function(schema) {
              var buttons = [];
              var $button;
  
              if (schema.allowChangeVisibility) {
                  $button = $('<button type="button" class="btn -fn-hide-all btn-default" />');
                  $button.append('<i class="fa far fa-eye-slash">');
                  $button.attr("title", Mapbender.trans('mb.digitizer.toolset.hideAll'));
                  buttons.push($button);
  
                  $button = $('<button type="button" class="btn -fn-show-all btn-default" />');
                  $button.append('<i class="fa far fa-eye">');
                  $button.attr("title", Mapbender.trans('mb.digitizer.toolset.showAll'));
                  buttons.push($button);
              }
              // If geometry modification is allowed, we must offer a way to save
              if (schema.allowDigitize) {
                  $button = $('<button type="button" class="btn -fn-save-all btn-success" />');
                  $button.append('<i class="fa fas fa-save">');
                  $button.attr("title", Mapbender.trans('mb.digitizer.toolset.saveAll'));
                  $button.prop('disabled', true);
                  buttons.push($button);
              }
              return buttons;
          },
          renderCurrentExtentSwitch: function (schema) {
              var widget = this.owner;
              var $checkbox = $('<input type="checkbox" name="current-extent" />');
              var title = Mapbender.trans('mb.digitizer.toolset.current-extent');
              $checkbox.prop('checked', schema.searchType === 'currentExtent');
              $checkbox.change(function (e) {
                  widget._getData(schema);
              });
              var $div = $("<div/>");
              $div.addClass("form-group checkbox");
              var $label = $("<label/>");
              $label.text(title);
              $label.prepend($checkbox);
              $div.append($label);
              return $div;
          },
          renderButtonGroup_: function(contents) {
              var $group = $(document.createElement('div'))
                  // @see https://getbootstrap.com/docs/3.4/components/#btn-groups
                  .addClass('btn-group')
              ;
              $group.append(contents);
              return $group.get(0);
          },
          /**
           * @param {Array<Element>} dmToolset
           * @private
           * @return {Array<Element>}
           */
          extractDmButtons_: function(dmToolset) {
              var $buttons = $('button,.btn,.button', $(dmToolset));
              // Prevent top-level item creation
              // Unlinke DataManager, all item creation happens with a drawing tool,
              // and doesn't work when using pure form entry
              $buttons = $buttons.not('.-fn-create-item');
              return $buttons.get();
          }
      };
  
      Object.assign(Mapbender.Digitizer.FeatureEditor.prototype, {
          getDrawingTool: function(type, schema) {
              if (!this.tools_[schema.schemaName]) {
                  this.tools_[schema.schemaName] = {};
              }
              if (!this.tools_[schema.schemaName][type]) {
                  var newInteraction = this.createDrawingTool(type);
                  this.tools_[schema.schemaName][type] = newInteraction;
                  var widget = this.owner;
                  newInteraction.on(ol.interaction.DrawEventType.DRAWEND, function(event) {
                      var feature = event.feature;
                      widget.initializeNewFeature(schema, feature);
                      widget._openEditDialog(schema, event.feature);
                  });
                  newInteraction.on([ol.interaction.ModifyEventType.MODIFYEND, ol.interaction.ModifyEventType.MODIFYSTART, ol.interaction.TranslateEventType.TRANSLATEEND], function(event) {
                      event.features.forEach(function(feature) {
                          feature.set('dirty', true);
                      });
                  });
              }
              return this.tools_[schema.schemaName][type];
          },
          /**
           * @param {String} type
           */
          createDrawingTool: function(type) {
              var widget = this.owner;
              var schema = widget._getCurrentSchema();
              var layer = schema && widget.getSchemaLayer(schema);
              var source = layer && layer.getSource();
              var interaction;
              switch (type) {
                  case 'modifyFeature':
                      interaction = new ol.interaction.Modify({
                          features: this.modifyingCollection_
                      });
                      break;
                  default:
                      interaction = this.controlFactory[type](source);
                      break;
              }
              interaction.setActive(false);
              this.olMap.addInteraction(interaction);
              return interaction;
          }
      });
  
  })(jQuery);
  