text: 'use strict';
  
  var getBusinessObject = require('bpmn-js/lib/util/ModelUtil').getBusinessObject,
      getExtensionElements = require('../../../helper/ExtensionElementsHelper').getExtensionElements,
      removeEntry = require('../../../helper/ExtensionElementsHelper').removeEntry,
      extensionElements = require('./implementation/ExtensionElements'),
      properties = require('./implementation/Properties'),
      entryFactory = require('../../../factory/EntryFactory'),
      elementHelper = require('../../../helper/ElementHelper'),
      cmdHelper = require('../../../helper/CmdHelper'),
      formHelper = require('../../../helper/FormHelper'),
      utils = require('../../../Utils'),
      is = require('bpmn-js/lib/util/ModelUtil').is,
      find = require('lodash/find'),
      each = require('lodash/forEach'),
      isUndefined = require('lodash/isUndefined');
  
  function generateValueId() {
    return utils.nextId('Value_');
  }
  
  /**
   * Generate a form field specific textField using entryFactory.
   *
   * @param {Function} translate
   * @param {string} options.id
   * @param {string} options.label
   * @param {string} options.modelProperty
   * @param {Function} options.validate
   *
   * @return {Object} an entryFactory.textField object
   */
  function formFieldTextField(translate, options, getSelectedFormField) {
  
    var id = options.id,
        label = options.label,
        modelProperty = options.modelProperty,
        validate = options.validate;
  
    return entryFactory.textField(translate, {
      id: id,
      label: label,
      modelProperty: modelProperty,
      get: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node) || {},
            values = {};
  
        values[modelProperty] = selectedFormField[modelProperty];
  
        return values;
      },
  
      set: function(element, values, node) {
        var commands = [];
  
        if (typeof options.set === 'function') {
          var cmd = options.set(element, values, node);
  
          if (cmd) {
            commands.push(cmd);
          }
        }
  
        var formField = getSelectedFormField(element, node),
            properties = {};
  
        properties[modelProperty] = values[modelProperty] || undefined;
  
        commands.push(cmdHelper.updateBusinessObject(element, formField, properties));
  
        return commands;
      },
      hidden: function(element, node) {
        return !getSelectedFormField(element, node);
      },
      validate: validate
    });
  }
  
  module.exports = function(group, element, bpmnFactory, translate) {
  
    if (!isFormSupported(element)) {
      return;
    }
  
  
    /**
     * Return the currently selected form field querying the form field select box
     * from the DOM.
     *
     * @param  {djs.model.Base} element
     * @param  {DOMElement} node - DOM element of any form field text input
     *
     * @return {ModdleElement} the currently selected form field
     */
    function getSelectedFormField(element, node) {
      var selected = formFieldsEntry.getSelected(element, node.parentNode);
  
      if (selected.idx === -1) {
        return;
      }
  
      return formHelper.getFormField(element, selected.idx);
    }
  
    // [FormType] form type select field
    group.entries.push(entryFactory.selectBox(translate, {
      id : 'form-type',
      label: translate('Type'),
      selectOptions: [
        { value: '', name: translate('<none>') },
        { value: 'formKey', name: translate('Embedded or External Task Forms') },
        { value: 'formRef', name: translate('Camunda Forms') }
      ],
      modelProperty: 'formType',
      get: function(element, node) {
        var businessObject = getBusinessObject(element);
  
        var values = {
          formType: ''
        };
  
        if (!isUndefined(businessObject.get('camunda:formKey'))) {
          values.formType = 'formKey';
        } else if (!isUndefined(businessObject.get('camunda:formRef'))) {
          values.formType = 'formRef';
        }
  
        return values;
      },
      set: function(element, values, node) {
        var properties = {};
  
        var value = values.formType;
  
        if (value === 'formKey') {
          properties = {
            'camunda:formKey': ''
          };
        } else if (value === 'formRef') {
          properties = {
            'camunda:formRef': ''
          };
        } else {
          properties = {
            'camunda:formRef': undefined,
            'camunda:formRefBinding': undefined,
            'camunda:formRefVersion': undefined,
            'camunda:formKey': undefined
          };
        }
  
        var businessObject = getBusinessObject(element);
  
        return [ cmdHelper.updateBusinessObject(element, businessObject, properties) ];
      }
    }));
  
    // [FormKey] form key text input field
    group.entries.push(entryFactory.textField(translate, {
      id : 'form-key',
      label : translate('Form Key'),
      modelProperty: 'formKey',
      get: function(element, node) {
        var bo = getBusinessObject(element);
  
        return {
          formKey: bo.get('camunda:formKey')
        };
      },
      set: function(element, values, node) {
        var bo = getBusinessObject(element),
            formKey = values.formKey;
  
        return cmdHelper.updateBusinessObject(element, bo, { 'camunda:formKey': formKey });
      },
      hidden: function(element, node) {
        var formType = getFormType(element);
  
        return formType !== 'formKey';
      }
    }));
  
    // [FormRef] form ref text input field
    group.entries.push(entryFactory.textField(translate, {
      id : 'form-ref',
      label : translate('Form Ref'),
      modelProperty: 'formRef',
      get: function(element, node) {
        var businessObject = getBusinessObject(element);
  
        return {
          formRef: businessObject.get('camunda:formRef')
        };
      },
      set: function(element, values, node) {
        var businessObject = getBusinessObject(element),
            formRef = values.formRef;
  
        return cmdHelper.updateBusinessObject(element, businessObject, { 'camunda:formRef': formRef });
      },
      hidden: function(element, node) {
        var formType = getFormType(element);
  
        return formType !== 'formRef';
      }
    }));
  
    // [FormRefBinding] form ref binding select field
    group.entries.push(entryFactory.selectBox(translate, {
      id : 'form-ref-binding',
      label: translate('Binding'),
      selectOptions: [
        { value: 'deployment', name: translate('deployment') },
        { value: 'latest', name: translate('latest') },
        { value: 'version', name: translate('version') }
      ],
      modelProperty: 'formRefBinding',
  
      get: function(element, node) {
        return {
          formRefBinding: getFormRefBinding(element)
        };
      },
  
      set: function(element, values, node) {
        var value = values.formRefBinding;
  
        var properties = {
          'camunda:formRefBinding': value
        };
  
        var businessObject = getBusinessObject(element);
  
        return [ cmdHelper.updateBusinessObject(element, businessObject, properties) ];
      },
      hidden: function(element, node) {
        var formType = getFormType(element);
  
        return formType !== 'formRef';
      }
    }));
  
    // [FormRefVersion] form ref version text input field
    group.entries.push(entryFactory.textField(translate, {
      id : 'form-ref-version',
      label : translate('Version'),
      modelProperty: 'formRefVersion',
      get: function(element, node) {
        var businessObject = getBusinessObject(element);
  
        return {
          formRefVersion: businessObject.get('camunda:formRefVersion')
        };
      },
      set: function(element, values, node) {
        var businessObject = getBusinessObject(element),
            formRefVersion = values.formRefVersion || undefined;
  
        return cmdHelper.updateBusinessObject(element, businessObject, { 'camunda:formRefVersion': formRefVersion });
      },
      hidden: function(element, node) {
        var bindingType = getFormRefBinding(element);
  
        return bindingType !== 'version';
      }
    }));
  
    // [FormData] form field select box
    var formFieldsEntry = extensionElements(element, bpmnFactory, {
      id: 'form-fields',
      label: translate('Form Fields'),
      modelProperty: 'id',
      prefix: 'FormField',
      createExtensionElement: function(element, extensionElements, value) {
        var bo = getBusinessObject(element), commands = [];
  
        if (!extensionElements) {
          extensionElements = elementHelper.createElement('bpmn:ExtensionElements', { values: [] }, bo, bpmnFactory);
          commands.push(cmdHelper.updateProperties(element, { extensionElements: extensionElements }));
        }
  
        var formData = formHelper.getFormData(element);
  
        if (!formData) {
          formData = elementHelper.createElement('camunda:FormData', { fields: [] }, extensionElements, bpmnFactory);
          commands.push(cmdHelper.addAndRemoveElementsFromList(
            element,
            extensionElements,
            'values',
            'extensionElements',
            [formData],
            []
          ));
        }
  
        var field = elementHelper.createElement('camunda:FormField', { id: value }, formData, bpmnFactory);
        if (typeof formData.fields !== 'undefined') {
          commands.push(cmdHelper.addElementsTolist(element, formData, 'fields', [ field ]));
        } else {
          commands.push(cmdHelper.updateBusinessObject(element, formData, {
            fields: [ field ]
          }));
        }
        return commands;
      },
      removeExtensionElement: function(element, extensionElements, value, idx) {
        var formData = getExtensionElements(getBusinessObject(element), 'camunda:FormData')[0],
            entry = formData.fields[idx],
            commands = [];
  
        if (formData.fields.length < 2) {
          commands.push(removeEntry(getBusinessObject(element), element, formData));
        } else {
          commands.push(cmdHelper.removeElementsFromList(element, formData, 'fields', null, [entry]));
  
          if (entry.id === formData.get('businessKey')) {
            commands.push(cmdHelper.updateBusinessObject(element, formData, { 'businessKey': undefined }));
          }
        }
  
        return commands;
      },
      getExtensionElements: function(element) {
        return formHelper.getFormFields(element);
      },
      hideExtensionElements: function(element, node) {
        return false;
      }
    });
    group.entries.push(formFieldsEntry);
  
    // [FormData] business key form field select box
    var formBusinessKeyFormFieldEntry = entryFactory.selectBox(translate, {
      id: 'form-business-key',
      label: translate('Business Key'),
      modelProperty: 'businessKey',
      selectOptions: function(element, inputNode) {
        var selectOptions = [{ name: '', value: '' }];
        var formFields = formHelper.getFormFields(element);
        each(formFields, function(field) {
          if (field.type !== 'boolean') {
            selectOptions.push({ name: field.id, value: field.id });
          }
        });
        return selectOptions;
      },
      get: function(element, node) {
        var result = { businessKey: '' };
        var bo = getBusinessObject(element);
        var formDataExtension = getExtensionElements(bo, 'camunda:FormData');
        if (formDataExtension.length) {
          var formData = formDataExtension[0];
          var storedValue = formData.get('businessKey');
          result = { businessKey: storedValue };
        }
        return result;
      },
      set: function(element, values, node) {
        var formData = getExtensionElements(getBusinessObject(element), 'camunda:FormData')[0];
        return cmdHelper.updateBusinessObject(element, formData, { 'businessKey': values.businessKey || undefined });
      },
      hidden: function(element, node) {
        var isStartEvent = is(element,'bpmn:StartEvent');
        return !(isStartEvent && formHelper.getFormFields(element).length > 0);
      }
    });
    group.entries.push(formBusinessKeyFormFieldEntry);
  
    // [FormData] Form Field label
    group.entries.push(entryFactory.label({
      id: 'form-field-header',
      labelText: translate('Form Field'),
      showLabel: function(element, node) {
        return !!getSelectedFormField(element, node);
      }
    }));
  
    // [FormData] form field id text input field
    group.entries.push(entryFactory.validationAwareTextField(translate, {
      id: 'form-field-id',
      label: translate('ID (process variable name)'),
      modelProperty: 'id',
  
      getProperty: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node) || {};
  
        return selectedFormField.id;
      },
  
      setProperty: function(element, properties, node) {
        var formField = getSelectedFormField(element, node);
  
        return cmdHelper.updateBusinessObject(element, formField, properties);
      },
  
      hidden: function(element, node) {
        return !getSelectedFormField(element, node);
      },
  
      validate: function(element, values, node) {
  
        var formField = getSelectedFormField(element, node);
  
        if (formField) {
  
          var idValue = values.id;
  
          if (!idValue || idValue.trim() === '') {
            return { id: 'Form field id must not be empty' };
          }
  
          var formFields = formHelper.getFormFields(element);
  
          var existingFormField = find(formFields, function(f) {
            return f !== formField && f.id === idValue;
          });
  
          if (existingFormField) {
            return { id: 'Form field id already used in form data.' };
          }
        }
      }
    }));
  
    // [FormData] form field type combo box
    group.entries.push(entryFactory.comboBox(translate, {
      id: 'form-field-type',
      label: translate('Type'),
      selectOptions: [
        { name: 'string', value: 'string' },
        { name: 'long', value: 'long' },
        { name: 'boolean', value: 'boolean' },
        { name: 'date', value: 'date' },
        { name: 'enum', value: 'enum' }
      ],
      modelProperty: 'type',
      emptyParameter: true,
  
      get: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node);
  
        if (selectedFormField) {
          return { type: selectedFormField.type };
        } else {
          return {};
        }
      },
      set: function(element, values, node) {
        var selectedFormField = getSelectedFormField(element, node),
            formData = getExtensionElements(getBusinessObject(element), 'camunda:FormData')[0],
            commands = [];
  
        if (selectedFormField.type === 'enum' && values.type !== 'enum') {
  
          // delete camunda:value objects from formField.values when switching from type enum
          commands.push(cmdHelper.updateBusinessObject(element, selectedFormField, { values: undefined }));
        }
        if (values.type === 'boolean' && selectedFormField.get('id') === formData.get('businessKey')) {
          commands.push(cmdHelper.updateBusinessObject(element, formData, { 'businessKey': undefined }));
        }
        commands.push(cmdHelper.updateBusinessObject(element, selectedFormField, values));
  
        return commands;
      },
      hidden: function(element, node) {
        return !getSelectedFormField(element, node);
      }
    }));
  
    // [FormData] form field label text input field
    group.entries.push(formFieldTextField(translate, {
      id: 'form-field-label',
      label: translate('Label'),
      modelProperty: 'label'
    }, getSelectedFormField));
  
    // [FormData] form field defaultValue text input field
    group.entries.push(formFieldTextField(translate, {
      id: 'form-field-defaultValue',
      label: translate('Default Value'),
      modelProperty: 'defaultValue'
    }, getSelectedFormField));
  
  
    // [FormData] form field enum values label
    group.entries.push(entryFactory.label({
      id: 'form-field-enum-values-header',
      labelText: translate('Values'),
      divider: true,
      showLabel: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node);
  
        return selectedFormField && selectedFormField.type === 'enum';
      }
    }));
  
    // [FormData] form field enum values table
    group.entries.push(entryFactory.table(translate, {
      id: 'form-field-enum-values',
      labels: [ translate('Id'), translate('Name') ],
      modelProperties: [ 'id', 'name' ],
      show: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node);
  
        return selectedFormField && selectedFormField.type === 'enum';
      },
      getElements: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node);
  
        return formHelper.getEnumValues(selectedFormField);
      },
      addElement: function(element, node) {
        var selectedFormField = getSelectedFormField(element, node),
            id = generateValueId();
  
        var enumValue = elementHelper.createElement(
          'camunda:Value',
          { id: id, name: undefined },
          getBusinessObject(element),
          bpmnFactory
        );
  
        return cmdHelper.addElementsTolist(element, selectedFormField, 'values', [enumValue]);
      },
      removeElement: function(element, node, idx) {
        var selectedFormField = getSelectedFormField(element, node),
            enumValue = selectedFormField.values[idx];
  
        return cmdHelper.removeElementsFromList(element, selectedFormField, 'values', null, [enumValue]);
      },
      updateElement: function(element, value, node, idx) {
        var selectedFormField = getSelectedFormField(element, node),
            enumValue = selectedFormField.values[idx];
  
        value.name = value.name || undefined;
        return cmdHelper.updateBusinessObject(element, enumValue, value);
      },
      validate: function(element, value, node, idx) {
  
        var selectedFormField = getSelectedFormField(element, node),
            enumValue = selectedFormField.values[idx];
  
        if (enumValue) {
  
          // check if id is valid
          var validationError = utils.isIdValid(enumValue, value.id, translate);
  
          if (validationError) {
            return { id: validationError };
          }
        }
      }
    }));
  
    // [FormData] Validation label
    group.entries.push(entryFactory.label({
      id: 'form-field-validation-header',
      labelText: translate('Validation'),
      divider: true,
      showLabel: function(element, node) {
        return !!getSelectedFormField(element, node);
      }
    }));
  
    // [FormData] form field constraints table
    group.entries.push(entryFactory.table(translate, {
      id: 'constraints-list',
      modelProperties: [ 'name', 'config' ],
      labels: [ translate('Name'), translate('Config') ],
      addLabel: translate('Add Constraint'),
      getElements: function(element, node) {
        var formField = getSelectedFormField(element, node);
  
        return formHelper.getConstraints(formField);
      },
      addElement: function(element, node) {
  
        var commands = [],
            formField = getSelectedFormField(element, node),
            validation = formField.validation;
  
        if (!validation) {
  
          // create validation business object and add it to form data, if it doesn't exist
          validation = elementHelper.createElement('camunda:Validation', {}, getBusinessObject(element), bpmnFactory);
  
          commands.push(cmdHelper.updateBusinessObject(element, formField, { 'validation': validation }));
        }
  
        var newConstraint = elementHelper.createElement(
          'camunda:Constraint',
          { name: undefined, config: undefined },
          validation,
          bpmnFactory
        );
  
        commands.push(cmdHelper.addElementsTolist(element, validation, 'constraints', [ newConstraint ]));
  
        return commands;
      },
      updateElement: function(element, value, node, idx) {
        var formField = getSelectedFormField(element, node),
            constraint = formHelper.getConstraints(formField)[idx];
  
        value.name = value.name || undefined;
        value.config = value.config || undefined;
  
        return cmdHelper.updateBusinessObject(element, constraint, value);
      },
      removeElement: function(element, node, idx) {
        var commands = [],
            formField = getSelectedFormField(element, node),
            constraints = formHelper.getConstraints(formField),
            currentConstraint = constraints[idx];
  
        commands.push(cmdHelper.removeElementsFromList(
          element,
          formField.validation,
          'constraints',
          null,
          [ currentConstraint ]
        ));
  
        if (constraints.length === 1) {
  
          // remove camunda:validation if the last existing constraint has been removed
          commands.push(cmdHelper.updateBusinessObject(element, formField, { validation: undefined }));
        }
  
        return commands;
      },
      show: function(element, node) {
        return !!getSelectedFormField(element, node);
      }
    }));
  
    // [FormData] Properties label
    group.entries.push(entryFactory.label({
      id: 'form-field-properties-header',
      labelText: translate('Properties'),
      divider: true,
      showLabel: function(element, node) {
        return !!getSelectedFormField(element, node);
      }
    }));
  
    // [FormData] camunda:properties table
    group.entries.push(properties(element, bpmnFactory, {
      id: 'form-field-properties',
      modelProperties: [ 'id', 'value' ],
      labels: [ translate('Id'), translate('Value') ],
      getParent: function(element, node) {
        return getSelectedFormField(element, node);
      },
      show: function(element, node) {
        return !!getSelectedFormField(element, node);
      }
    }, translate));
  };
  
  // helpers //////////
  
  function getFormRefBinding(element) {
    var businessObject = getBusinessObject(element);
  
    return businessObject.get('camunda:formRefBinding');
  }
  
  function getFormType(element) {
    var businessObject = getBusinessObject(element);
  
    if (!isUndefined(businessObject.get('camunda:formKey'))) {
      return 'formKey';
    } else if (!isUndefined(businessObject.get('camunda:formRef'))) {
      return 'formRef';
    }
  }
  
  function isFormSupported(element) {
    return (is(element, 'bpmn:StartEvent') && !is(element.parent, 'bpmn:SubProcess'))
      || is(element, 'bpmn:UserTask');
  }