UNPKG

bpmn-js-element-templates

Version:
17,928 lines 497 kB
import { isNil, flatten, values, isObject, filter, isString, isUndefined as isUndefined$1, reduce, has, find, isArray, forEach, isFunction, without, assign, groupBy, isNumber, sortBy, keys, bind, set, pick, findIndex } from 'min-dash';
import { getBusinessObject, is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import { v4 } from 'uuid';
import { satisfies, coerce, valid, validRange } from 'semver';
import semverCompare from 'semver-compare';
import { getSchemaVersion as getSchemaVersion$1, validate as validate$1, getZeebeSchemaVersion, getZeebeSchemaPackage, validateZeebe } from '@bpmn-io/element-templates-validator';
import Ids from 'ids';
import { isEventSubProcess } from 'bpmn-js/lib/util/DiUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import translateModule from 'diagram-js/lib/i18n/translate';
import { useLayoutState, HeaderButton, ArrowIcon, CreateIcon, DropdownButton, TextAreaEntry, TextFieldEntry, FeelTextAreaEntry as FeelTextAreaEntry$1, FeelEntry as FeelEntry$1, SelectEntry, FeelCheckboxEntry, CheckboxEntry, FeelNumberEntry, NumberFieldEntry, Group, isFeelEntryEdited, isNumberFieldEntryEdited, isCheckboxEntryEdited, isSelectEntryEdited, isTextFieldEntryEdited, isTextAreaEntryEdited, usePrevious, ToggleSwitchEntry, ListGroup } from '@bpmn-io/properties-panel';
import classnames from 'classnames';
import { useService, CamundaPlatformPropertiesProviderModule } from 'bpmn-js-properties-panel';
import { jsxs, jsx, Fragment } from '@bpmn-io/properties-panel/preact/jsx-runtime';
import { createElement as createElement$1, h, Component } from '@bpmn-io/properties-panel/preact';
import { useCallback, useState, useEffect, useMemo } from '@bpmn-io/properties-panel/preact/hooks';
import { getVariablesForElement } from '@bpmn-io/extract-process-variables/zeebe';
import { query } from 'min-dom';
import { getLabel, setLabel } from 'bpmn-js/lib/features/label-editing/LabelUtil';
import { isPlane, getShapeIdFromPlane } from 'bpmn-js/lib/util/DrilldownUtil';
import StaticResolver from 'bpmnlint/lib/resolver/static-resolver';
import EventBus from 'diagram-js/lib/core/EventBus';

/**
 * Check if the property is cast to FEEL expression:
 * - Boolean and Number properties with feel set to 'optional' or 'static'
 * - Boolean and Number input/output parameters have default feel=static
 *
 * @returns {boolean}
 */
const shouldCastToFeel = property => {
  const feel = getFeelValue(property);
  return ['optional', 'static'].includes(feel) && ['Boolean', 'Number'].includes(property.type);
};
const ALWAYS_CAST_TO_FEEL = ['zeebe:input', 'zeebe:output'];
function getFeelValue(property) {
  if (ALWAYS_CAST_TO_FEEL.includes(property.binding.type)) {
    return property.feel || 'static';
  }
  return property.feel;
}
const toFeelExpression = (value, type) => {
  if (typeof value === 'string' && value.startsWith('=')) {
    return value;
  }
  if (type === 'Boolean') {
    value = value === 'false' ? false : value;
    return '=' + !!value;
  }
  if (typeof value === 'undefined') {
    return value;
  }
  return '=' + value.toString();
};

/**
 * The BPMN 2.0 extension attribute name under
 * which the element template ID is stored.
 *
 * @type {String}
 */
const TEMPLATE_ID_ATTR$1 = 'zeebe:modelerTemplate';

/**
 * The BPMN 2.0 extension attribute name under
 * which the element template version is stored.
 *
 * @type {String}
 */
const TEMPLATE_VERSION_ATTR$1 = 'zeebe:modelerTemplateVersion';

/**
 * The BPMN 2.0 extension attribute name under
 * which the element template icon is stored.
 *
 * @type {String}
 */
const TEMPLATE_ICON_ATTR = 'zeebe:modelerTemplateIcon';

/**
 * Get template id for a given diagram element.
 *
 * @param {djs.model.Base} element
 *
 * @return {String}
 */
function getTemplateId$1(element) {
  const businessObject = getBusinessObject(element);
  if (businessObject) {
    return businessObject.get(TEMPLATE_ID_ATTR$1);
  }
}

/**
 * Get template version for a given diagram element.
 *
 * @param {djs.model.Base} element
 *
 * @return {String}
 */
function getTemplateVersion$1(element) {
  const businessObject = getBusinessObject(element);
  if (businessObject) {
    return businessObject.get(TEMPLATE_VERSION_ATTR$1);
  }
}

/**
 * Get template icon for a given diagram element.
 *
 * @param {djs.model.Base} element
 *
 * @return {String}
 */
function getTemplateIcon(element) {
  const businessObject = getBusinessObject(element);
  if (businessObject) {
    return businessObject.get(TEMPLATE_ICON_ATTR);
  }
}

/**
 * Find extension with given type in
 * BPMN element, diagram element or ExtensionElement.
 *
 * @param {ModdleElement|djs.model.Base} element
 * @param {String} type
 *
 * @return {ModdleElement} the extension
 */
function findExtension$1(element, type) {
  const businessObject = getBusinessObject(element);
  let extensionElements;
  if (is(businessObject, 'bpmn:ExtensionElements')) {
    extensionElements = businessObject;
  } else {
    extensionElements = businessObject.get('extensionElements');
  }
  if (!extensionElements) {
    return;
  }
  return extensionElements.get('values').find(value => {
    return is(value, type);
  });
}
function findZeebeProperty(zeebeProperties, binding) {
  return zeebeProperties.get('properties').find(value => {
    return value.name === binding.name;
  });
}
function findInputParameter$1(ioMapping, binding) {
  const parameters = ioMapping.get('inputParameters');
  return parameters.find(parameter => {
    return parameter.target === binding.name;
  });
}
function findOutputParameter$1(ioMapping, binding) {
  const parameters = ioMapping.get('outputParameters');
  return parameters.find(parameter => {
    return parameter.source === binding.source;
  });
}
function findTaskHeader(taskHeaders, binding) {
  const headers = taskHeaders.get('values');
  return headers.find(header => {
    return header.key === binding.key;
  });
}

/**
 * Find message referred to in an event, an event definition, or a task.
 * @param {ModdleElement} businessObject
 */
function findMessage(businessObject) {
  if (is(businessObject, 'bpmn:Event')) {
    const eventDefinitions = businessObject.get('eventDefinitions');
    if (!eventDefinitions || !eventDefinitions.length) {
      return;
    }
    businessObject = eventDefinitions[0];
  }
  if (!businessObject) {
    return;
  }
  return businessObject.get('messageRef');
}

/**
 * Find signal referred to in an event or an event definition.
 * @param {ModdleElement} businessObject
 */
function findSignal(businessObject) {
  if (is(businessObject, 'bpmn:Event')) {
    const eventDefinitions = businessObject.get('eventDefinitions');
    if (!eventDefinitions || !eventDefinitions.length) {
      return;
    }
    businessObject = eventDefinitions[0];
  }
  if (!businessObject) {
    return;
  }
  return businessObject.get('signalRef');
}

/**
 * Find timer event definition in an event.
 * @param {ModdleElement|element} element
 */
function findTimerEventDefinition(element) {
  const businessObject = getBusinessObject(element);
  if (is(businessObject, 'bpmn:Event')) {
    const eventDefinitions = businessObject.get('eventDefinitions');
    if (!eventDefinitions.length) {
      return;
    }
    return eventDefinitions.find(def => is(def, 'bpmn:TimerEventDefinition'));
  }
}

/**
 * Get the default value disregarding generated values.
 */
function getDefaultFixedValue(property) {
  if (shouldCastToFeel(property) || property.feel === 'required') {
    return toFeelExpression(property.value, property.type);
  }
  return property.value;
}
function getDefaultValue(property) {
  const value = getDefaultFixedValue(property);
  if (value !== undefined) {
    return value;
  }
  if (property.generatedValue) {
    const {
      type
    } = property.generatedValue;
    if (type === 'uuid') {
      return v4();
    }
  }
}

/**
 * The BPMN 2.0 extension attribute name under
 * which the element template ID is stored.
 *
 * @type {String}
 */
const TEMPLATE_ID_ATTR = 'camunda:modelerTemplate';

/**
 * The BPMN 2.0 extension attribute name under
 * which the element template version is stored.
 *
 * @type {String}
 */
const TEMPLATE_VERSION_ATTR = 'camunda:modelerTemplateVersion';

/**
 * Returns incompatible engines for a given template.
 *
 * @param {Object} template
 * @param {Object} checkEngines
 *
 * @return {Object}
 */
function getIncompatibleEngines(template, checkEngines) {
  const templateEngines = template.engines;
  return reduce(templateEngines, (result, _, engine) => {
    if (!has(checkEngines, engine)) {
      return result;
    }
    if (!satisfies(checkEngines[engine], templateEngines[engine])) {
      result[engine] = {
        actual: checkEngines[engine],
        required: templateEngines[engine]
      };
    }
    return result;
  }, {});
}

/**
 * Returns whether a template is compatible with the given engines.
 *
 * @param {Object} template
 * @param {Object} checkEngines
 *
 * @return {boolean}
 */
function isCompatible(template, checkEngines) {
  return !Object.keys(getIncompatibleEngines(template, checkEngines)).length;
}

/**
 * Build a map of templates grouped by id.
 *
 * @param {Array<Object>} templates
 * @param {Object} engines
 *
 * @return {Object}
 */
function buildTemplatesById(templates, engines) {
  const templatesById = {};
  templates.forEach(template => {
    const id = template.id;
    const version = isUndefined$1(template.version) ? '_' : template.version;
    if (!templatesById[id]) {
      templatesById[id] = {};
    }
    templatesById[id][version] = template;
    const latest = templatesById[id].latest;
    if (isCompatible(template, engines)) {
      if (!latest || isUndefined$1(latest.version) || latest.version < version) {
        templatesById[id].latest = template;
      }
    }
  });
  return templatesById;
}

/**
 * Finds the list of templates that match the given criteria within a template index.
 *
 * @param {string|djs.model.Base} [elementOrTemplateId]
 * @param {Object} templatesIndex
 * @param {Object} [options]
 * @param {boolean} [options.latest]
 * @param {boolean} [options.deprecated]
 *
 * @return {Array<Object>}
 */
function findTemplates(elementOrTemplateId, templatesIndex, options = {}) {
  const {
    latest: includeLatestOnly,
    deprecated: includeDeprecated
  } = options;
  const getVersions = template => {
    const {
      latest,
      ...versions
    } = template;
    return includeLatestOnly ? !includeDeprecated && latest && latest.deprecated ? [] : latest ? [latest] : [] : values(versions);
  };
  if (isNil(elementOrTemplateId)) {
    return flatten(values(templatesIndex).map(getVersions));
  }
  if (isObject(elementOrTemplateId)) {
    const element = elementOrTemplateId;
    return filter(findTemplates(null, templatesIndex, options), function (template) {
      return isAny(element, template.appliesTo);
    }) || [];
  }
  if (isString(elementOrTemplateId)) {
    return templatesIndex[elementOrTemplateId] && getVersions(templatesIndex[elementOrTemplateId]);
  }
  throw new Error('argument must be of type {string|djs.model.Base|undefined}');
}

/**
 * Get template id for a given diagram element.
 *
 * @param {djs.model.Base} element
 *
 * @return {String}
 */
function getTemplateId(element) {
  const businessObject = getBusinessObject(element);
  if (businessObject) {
    return businessObject.get(TEMPLATE_ID_ATTR);
  }
}

/**
 * Get template version for a given diagram element.
 *
 * @param {djs.model.Base} element
 *
 * @return {String}
 */
function getTemplateVersion(element) {
  const businessObject = getBusinessObject(element);
  if (businessObject) {
    return businessObject.get(TEMPLATE_VERSION_ATTR);
  }
}

/**
 * Find extension with given type in
 * BPMN element, diagram element or ExtensionElement.
 *
 * @param {ModdleElement|djs.model.Base} element
 * @param {String} type
 *
 * @return {ModdleElement} the extension
 */
function findExtension(element, type) {
  const businessObject = getBusinessObject(element);
  let extensionElements;
  if (is(businessObject, 'bpmn:ExtensionElements')) {
    extensionElements = businessObject;
  } else {
    extensionElements = businessObject.get('extensionElements');
  }
  if (!extensionElements) {
    return null;
  }
  return extensionElements.get('values').find(value => {
    return is(value, type);
  });
}
function findExtensions(element, types) {
  const extensionElements = getExtensionElements(element);
  if (!extensionElements) {
    return [];
  }
  return extensionElements.get('values').filter(value => {
    return isAny(value, types);
  });
}
function findCamundaInOut(element, binding) {
  const extensionElements = getExtensionElements(element);
  if (!extensionElements) {
    return;
  }
  const {
    type
  } = binding;
  let matcher;
  if (type === 'camunda:in') {
    matcher = element => {
      return is(element, 'camunda:In') && isInOut(element, binding);
    };
  } else if (type === 'camunda:out') {
    matcher = element => {
      return is(element, 'camunda:Out') && isInOut(element, binding);
    };
  } else if (type === 'camunda:in:businessKey') {
    matcher = element => {
      return is(element, 'camunda:In') && 'businessKey' in element;
    };
  }
  return extensionElements.get('values').find(matcher);
}
function findCamundaProperty(camundaProperties, binding) {
  return camundaProperties.get('values').find(value => {
    return value.name === binding.name;
  });
}
function findInputParameter(inputOutput, binding) {
  const parameters = inputOutput.get('inputParameters');
  return parameters.find(parameter => {
    return parameter.name === binding.name;
  });
}
function findOutputParameter(inputOutput, binding) {
  const parameters = inputOutput.get('outputParameters');
  return parameters.find(function (parameter) {
    const {
      value
    } = parameter;
    if (!binding.scriptFormat) {
      return value === binding.source;
    }
    const definition = parameter.get('camunda:definition');
    if (!definition || binding.scriptFormat !== definition.get('camunda:scriptFormat')) {
      return false;
    }
    return definition.get('camunda:value') === binding.source;
  });
}
function findCamundaErrorEventDefinition(element, errorRef) {
  const errorEventDefinitions = findExtensions(element, ['camunda:ErrorEventDefinition']);
  let error;

  // error ID has to start with <Error_${ errorRef }_>
  return errorEventDefinitions.find(definition => {
    error = definition.get('bpmn:errorRef');
    if (error) {
      return error.get('bpmn:id').startsWith(`Error_${errorRef}`);
    }
  });
}

// helpers //////////

function getExtensionElements(element) {
  const businessObject = getBusinessObject(element);
  if (is(businessObject, 'bpmn:ExtensionElements')) {
    return businessObject;
  } else {
    return businessObject.get('extensionElements');
  }
}
function isInOut(element, binding) {
  if (binding.type === 'camunda:in') {
    // find based on target attribute
    if (binding.target) {
      return element.target === binding.target;
    }
  }
  if (binding.type === 'camunda:out') {
    // find based on source / sourceExpression
    if (binding.source) {
      return element.source === binding.source;
    }
    if (binding.sourceExpression) {
      return element.sourceExpression === binding.sourceExpression;
    }
  }

  // find based variables / local combination
  if (binding.variables) {
    return element.variables === 'all' && (binding.variables !== 'local' || element.local);
  }
}

// eslint-disable-next-line no-undef
const packageVersion = "2.18.0";

/**
 * @typedef {import('bpmn-js/lib/model/Types').Element} Element
 */

/**
 * Registry for element templates.
 */
let ElementTemplates$1 = class ElementTemplates {
  constructor(commandStack, eventBus, modeling, injector, config) {
    this._commandStack = commandStack;
    this._eventBus = eventBus;
    this._injector = injector;
    this._modeling = modeling;
    this._templatesById = {};
    this._templates = [];
    config = config || {};
    this._engines = this._coerceEngines(config.engines || {});
    eventBus.on('elementTemplates.engines.changed', event => {
      this.set(this._templates);
    });
  }

  /**
   * Get template with given ID and optional version or for element.
   *
   * @param {String|Element} elementOrTemplateId
   * @param {number} [version]
   *
   * @return {ElementTemplate}
   */
  get(elementOrTemplateId, version) {
    const templates = this._templatesById;
    let element;
    if (isUndefined$1(elementOrTemplateId)) {
      return null;
    } else if (isString(elementOrTemplateId)) {
      if (isUndefined$1(version)) {
        version = '_';
      }
      if (templates[elementOrTemplateId] && templates[elementOrTemplateId][version]) {
        return templates[elementOrTemplateId][version];
      } else {
        return null;
      }
    } else {
      element = elementOrTemplateId;
      return this.get(this._getTemplateId(element), this._getTemplateVersion(element));
    }
  }

  /**
   * Get default template for given element.
   *
   * @param {Element} element
   *
   * @return {ElementTemplate}
   */
  getDefault(element) {
    return find(this.getAll(element), function (template) {
      return template.isDefault;
    }) || null;
  }

  /**
   * Get all templates (with given ID or applicable to element).
   *
   * @param {string|Element} [elementOrTemplateId]
   * @return {Array<ElementTemplate>}
   */
  getAll(elementOrTemplateId) {
    return findTemplates(elementOrTemplateId, this._templatesById, {
      deprecated: true
    });
  }

  /**
   * Get all templates (with given ID or applicable to element) with the latest
   * version.
   *
   * @param {String|Element} [elementOrTemplateId]
   * @param {{ deprecated?: boolean }} [options]
   *
   * @return {Array<ElementTemplate>}
   */
  getLatest(elementOrTemplateId, options = {}) {
    return findTemplates(elementOrTemplateId, this._templatesById, {
      ...options,
      latest: true
    });
  }

  /**
   * Get templates compatible with a given engine configuration override.
   *
   * @param {string|Element} [elementOrTemplateId]
   * @param {Object} enginesOverrides
   * @param {Object} [options]
   * @param {boolean} [options.deprecated=false]
   * @param {boolean} [options.latest=true]
   *
   * @returns {Array<ElementTemplate>}
   */
  getCompatible(elementOrTemplateId, enginesOverrides = {}, options = {}) {
    const overridenEngines = this._coerceEngines({
      ...this._engines,
      ...enginesOverrides
    });
    const templatesById = buildTemplatesById(this._templates, overridenEngines);
    return findTemplates(elementOrTemplateId, templatesById, {
      latest: true,
      ...options
    });
  }

  /**
   * Set templates.
   *
   * @param {Array<ElementTemplate>} templates
   */
  set(templates) {
    this._templates = templates;
    this._templatesById = buildTemplatesById(this._templates, this._engines);
    this._fire('changed');
  }
  getEngines() {
    return this._engines;
  }
  setEngines(engines) {
    this._engines = this._coerceEngines(engines);
    this._fire('engines.changed');
  }

  /**
   * Ensures that only valid engines are kept around
   *
   * @param { Record<string, string> } engines
   *
   * @return { Record<string, string> } filtered, valid engines
   */
  _coerceEngines(engines) {
    // we provide <elementTemplates> engine with the current
    // package version; templates may use that engine to declare
    // compatibility with this library
    engines = {
      elementTemplates: packageVersion,
      ...engines
    };
    return reduce(engines, (validEngines, version, engine) => {
      const coercedVersion = coerce(version);
      if (!valid(coercedVersion)) {
        console.error(new Error(`Engine <${engine}> specifies unparseable version <${version}>`));
        return validEngines;
      }
      return {
        ...validEngines,
        [engine]: coercedVersion.raw
      };
    }, {});
  }

  /**
   * Check if template is compatible with currently set engine version.
   *
   * @param {ElementTemplate} template
   *
   * @return {boolean} - true if compatible or no engine is set for elementTemplates or template.
   */
  isCompatible(template) {
    return isCompatible(template, this._engines);
  }

  /**
   * Get engines that are incompatible with the template.
   *
   * @param {any} template
   *
   * @return { Record<string, { required: string, found: string } } - incompatible engines along with their template and local versions
   */
  getIncompatibleEngines(template) {
    return getIncompatibleEngines(template, this._engines);
  }

  /**
   * Get template versions for a given element or template ID.
   *
   * @param {object|string|null} id
   * @param { { latest?: boolean, deprecated?: boolean } [options]
   *
   * @return {Array<ElementTemplate>}
   */
  _getTemplateVerions(id, options = {}) {
    return findTemplates(id, this._templatesById, options);
  }
  _getTemplateId(element) {
    return getTemplateId(element);
  }
  _getTemplateVersion(element) {
    return getTemplateVersion(element);
  }

  /**
   * Apply element template to a given element.
   *
   * @param {Element} element
   * @param {ElementTemplate} newTemplate
   *
   * @return {Element} the updated element
   */
  applyTemplate(element, newTemplate) {
    const oldTemplate = this.get(element);
    const context = {
      element,
      newTemplate,
      oldTemplate
    };
    const event = oldTemplate?.id === newTemplate?.id ? 'update' : 'apply';
    this._commandStack.execute('propertiesPanel.camunda.changeTemplate', context);
    this._fire(event, {
      element,
      newTemplate,
      oldTemplate
    });
    return context.element;
  }
  _fire(action, payload) {
    return this._eventBus.fire(`elementTemplates.${action}`, payload);
  }

  /**
   * Remove template from a given element.
   *
   * @param {Element} element
   *
   * @return {Element} the updated element
   */
  removeTemplate(element) {
    const oldTemplate = this.get(element);
    const context = {
      element,
      oldTemplate
    };
    this._commandStack.execute('propertiesPanel.removeTemplate', context);
    this._fire('remove', {
      element,
      oldTemplate
    });
    return context.newElement;
  }

  /**
   * Unlink template from a given element.
   *
   * @param {Element} element
   *
   * @return {Element} the updated element
   */
  unlinkTemplate(element) {
    const oldTemplate = this.get(element);
    const context = {
      element,
      oldTemplate
    };
    this._commandStack.execute('propertiesPanel.unlinkTemplate', context);
    this._fire('unlink', {
      element,
      oldTemplate
    });
    return context.element;
  }
};
ElementTemplates$1.$inject = ['commandStack', 'eventBus', 'modeling', 'injector', 'config.elementTemplates'];

const PROPERTY_TYPE$1 = 'property';
const ZEBBE_PROPERTY_TYPE = 'zeebe:property';
const ZEBBE_INPUT_TYPE = 'zeebe:input';
const ZEEBE_OUTPUT_TYPE = 'zeebe:output';
const ZEEBE_PROPERTY_TYPE = 'zeebe:property';
const ZEEBE_TASK_DEFINITION_TYPE_TYPE = 'zeebe:taskDefinition:type';
const ZEEBE_TASK_DEFINITION = 'zeebe:taskDefinition';
const ZEEBE_TASK_HEADER_TYPE = 'zeebe:taskHeader';
const MESSAGE_PROPERTY_TYPE = 'bpmn:Message#property';
const MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE = 'bpmn:Message#zeebe:subscription#property';
const SIGNAL_PROPERTY_TYPE = 'bpmn:Signal#property';
const TIMER_EVENT_DEFINITION_PROPERTY_TYPE = 'bpmn:TimerEventDefinition#property';
const ZEEBE_CALLED_ELEMENT = 'zeebe:calledElement';
const ZEEBE_LINKED_RESOURCE_PROPERTY = 'zeebe:linkedResource';
const ZEEBE_USER_TASK = 'zeebe:userTask';
const ZEEBE_CALLED_DECISION = 'zeebe:calledDecision';
const ZEEBE_FORM_DEFINITION = 'zeebe:formDefinition';
const ZEEBE_SCRIPT_TASK = 'zeebe:script';
const ZEEBE_ASSIGNMENT_DEFINITION = 'zeebe:assignmentDefinition';
const ZEEBE_PRIORITY_DEFINITION = 'zeebe:priorityDefinition';
const ZEEBE_AD_HOC = 'zeebe:adHoc';
const ZEEBE_TASK_SCHEDULE = 'zeebe:taskSchedule';
const EXTENSION_BINDING_TYPES$1 = [MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE, ZEBBE_INPUT_TYPE, ZEEBE_OUTPUT_TYPE, ZEEBE_PROPERTY_TYPE, ZEEBE_TASK_DEFINITION_TYPE_TYPE, ZEEBE_TASK_DEFINITION, ZEEBE_TASK_HEADER_TYPE, ZEEBE_CALLED_ELEMENT, ZEEBE_LINKED_RESOURCE_PROPERTY, ZEEBE_CALLED_DECISION, ZEEBE_FORM_DEFINITION, ZEEBE_SCRIPT_TASK, ZEEBE_ASSIGNMENT_DEFINITION, ZEEBE_PRIORITY_DEFINITION, ZEEBE_AD_HOC, ZEEBE_TASK_SCHEDULE];
const TASK_DEFINITION_TYPES = [ZEEBE_TASK_DEFINITION_TYPE_TYPE, ZEEBE_TASK_DEFINITION];
const IO_BINDING_TYPES$1 = [ZEBBE_INPUT_TYPE, ZEEBE_OUTPUT_TYPE];
const MESSAGE_BINDING_TYPES = [MESSAGE_PROPERTY_TYPE, MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE];
const PROPERTY_BINDING_TYPES = [PROPERTY_TYPE$1, MESSAGE_PROPERTY_TYPE, SIGNAL_PROPERTY_TYPE];

/**
 * Check whether a given timer expression type is supported for a given element.
 *
 * @param {string} type - 'timeDate', 'timeCycle', or 'timeDuration'
 * @param {Element} element
 * @return {boolean}
 */
function isTimerExpressionTypeSupported(type, element) {
  const businessObject = getBusinessObject(element);
  switch (type) {
    case 'timeDate':
      return isAny(element, ['bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:StartEvent']);
    case 'timeCycle':
      if (is(element, 'bpmn:StartEvent') && (!hasParentEventSubProcess(businessObject) || !isInterrupting(businessObject))) {
        return true;
      }
      if (is(element, 'bpmn:BoundaryEvent') && !isInterrupting(businessObject)) {
        return true;
      }
      return false;
    case 'timeDuration':
      if (isAny(element, ['bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent'])) {
        return true;
      }
      if (is(element, 'bpmn:StartEvent') && hasParentEventSubProcess(businessObject)) {
        return true;
      }
      return false;
    default:
      return false;
  }
}

/**
 * Check if the template is a timer template and if so, whether it is applicable
 *
 * @param {Object} template
 * @param {Element} element
 * @return {boolean}
 */
function isTimerTemplateApplicable(template, element) {
  // Find timer binding in template
  const timerBinding = template.properties?.find(property => {
    return property.binding?.type === TIMER_EVENT_DEFINITION_PROPERTY_TYPE;
  });

  // No timer binding - template is applicable
  if (!timerBinding) {
    return true;
  }
  const timerType = timerBinding.binding.name;

  // Check if the timer type can be applied (possibly with auto-conversion)
  return canTimerTypeBeAppliedWithConversion(timerType, element);
}

/**
 * Check if a timer type can be applied to an element, possibly with auto-conversion.
 *
 * Only blocks truly impossible combinations:
 * - timeDuration on process-level start events (can't be converted)
 *
 * @param {string} type - 'timeDate', 'timeCycle', or 'timeDuration'
 * @param {Element} element
 * @return {boolean}
 */
function canTimerTypeBeAppliedWithConversion(type, element) {
  const businessObject = getBusinessObject(element);
  switch (type) {
    case 'timeDate':
      return isAny(element, ['bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:StartEvent']);
    case 'timeCycle':
      if (isAny(element, ['bpmn:StartEvent', 'bpmn:BoundaryEvent'])) {
        return true;
      }
      return false;
    case 'timeDuration':
      if (isAny(element, ['bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent'])) {
        return true;
      }

      // timeDuration can only be applied to start events in event subprocesses
      // Process-level start events cannot support timeDuration (no conversion possible)
      if (is(element, 'bpmn:StartEvent') && hasParentEventSubProcess(businessObject)) {
        return true;
      }
      return false;
    default:
      return false;
  }
}

// helpers //////////

function isInterrupting(businessObject) {
  if (is(businessObject, 'bpmn:BoundaryEvent')) {
    return businessObject.get('cancelActivity') !== false;
  }
  return businessObject.get('isInterrupting') !== false;
}
function hasParentEventSubProcess(businessObject) {
  const parent = businessObject.$parent;
  return parent && is(parent, 'bpmn:SubProcess') && parent.get('triggeredByEvent');
}

/**
 * @typedef {import('bpmn-js/lib/model/Types').Element} Element
 */

/**
 * Filters to determine whether a template is applicable to a given element.
 * @type {Array<(template: ElementTemplate, element: Element) => boolean>}
 */
const TEMPLATE_FILTERS = [isTimerTemplateApplicable];

/**
 * Registry for element templates.
 */
class ElementTemplates extends ElementTemplates$1 {
  constructor(templateElementFactory, commandStack, eventBus, modeling, injector, config) {
    super(commandStack, eventBus, modeling, injector, config);
    this._templateElementFactory = templateElementFactory;
  }
  _getTemplateId(element) {
    return getTemplateId$1(element);
  }
  _getTemplateVersion(element) {
    return getTemplateVersion$1(element);
  }

  /**
   * Get all templates (with given ID or applicable to element).
   *
   * @param {string|djs.model.Base} [elementOrTemplateId]
   * @return {Array<ElementTemplate>}
   */
  getAll(elementOrTemplateId) {
    const templates = super.getAll(elementOrTemplateId);
    if (isObject(elementOrTemplateId)) {
      return this._filterApplicableTemplates(templates, elementOrTemplateId);
    }
    return templates;
  }

  /**
   * Get all templates (with given ID or applicable to element) with the latest version.
   *
   * @param {String|djs.model.Base} [elementOrTemplateId]
   * @param {{ deprecated?: boolean }} [options]
   *
   * @return {Array<ElementTemplate>}
   */
  getLatest(elementOrTemplateId, options = {}) {
    const templates = super.getLatest(elementOrTemplateId, options);
    if (isObject(elementOrTemplateId)) {
      return this._filterApplicableTemplates(templates, elementOrTemplateId);
    }
    return templates;
  }

  /**
   * Get compatible templates for element with optional engine overrides.
   *
   * @param {String|djs.model.Base} [elementOrTemplateId]
   * @param {Object} [enginesOverrides]
   * @param {Object} [options]
   *
   * @return {Array<ElementTemplate>}
   */
  getCompatible(elementOrTemplateId, enginesOverrides = {}, options = {}) {
    const templates = super.getCompatible(elementOrTemplateId, enginesOverrides, options);
    if (isObject(elementOrTemplateId)) {
      return this._filterApplicableTemplates(templates, elementOrTemplateId);
    }
    return templates;
  }
  _filterApplicableTemplates(templates, element) {
    return TEMPLATE_FILTERS.reduce((filteredTemplates, filterFn) => filteredTemplates.filter(template => filterFn(template, element)), templates);
  }

  /**
   * Create an element based on an element template. This is, for example,
   * called from the create-append anything menu.
   *
   * @param {ElementTemplate} template
   * @returns {djs.model.Base}
   */
  createElement(template) {
    if (!template) {
      throw new Error('template is missing');
    }
    const element = this._templateElementFactory.create(template);
    return element;
  }

  /**
   * Apply element template to a given element.
   *
   * @param {Element} element
   * @param {ElementTemplate} newTemplate
   *
   * @return {Element} the updated element
   */
  applyTemplate(element, newTemplate) {
    const oldTemplate = this.get(element);
    const context = {
      element,
      newTemplate,
      oldTemplate
    };
    const event = oldTemplate?.id === newTemplate?.id ? 'update' : 'apply';
    this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', context);
    this._fire(event, {
      element,
      newTemplate,
      oldTemplate
    });
    return context.element;
  }

  /**
   * Remove template from a given element.
   *
   * @param {Element} element
   *
   * @return {Element} the updated element
   */
  removeTemplate(element) {
    const oldTemplate = this.get(element);
    const context = {
      element,
      oldTemplate
    };
    this._commandStack.execute('propertiesPanel.removeTemplate', context);
    this._fire('remove', {
      element,
      oldTemplate
    });
    return context.element;
  }
}
ElementTemplates.$inject = ['templateElementFactory', 'commandStack', 'eventBus', 'modeling', 'injector', 'config.elementTemplates'];

const SUPPORTED_SCHEMA_VERSION$1 = getSchemaVersion$1();
const MORPHABLE_TYPES = ['bpmn:Activity', 'bpmn:Event', 'bpmn:Gateway'];

/**
 * A element template validator.
 */
let Validator$1 = class Validator {
  constructor(moddle) {
    this._templatesById = {};
    this._validTemplates = [];
    this._errors = [];
    this._moddle = moddle;
  }

  /**
   * Adds the templates.
   *
   * @param {Array<TemplateDescriptor>} templates
   *
   * @return {Validator}
   */
  addAll(templates) {
    if (!isArray(templates)) {
      this._logError('templates must be []');
    } else {
      templates.forEach(this.add, this);
    }
    return this;
  }

  /**
   * Add the given element template, if it is valid.
   *
   * @param {TemplateDescriptor} template
   *
   * @return {Validator}
   */
  add(template) {
    const err = this._validateTemplate(template);
    let id, version;
    if (!err) {
      id = template.id;
      version = template.version || '_';
      if (!this._templatesById[id]) {
        this._templatesById[id] = {};
      }
      this._templatesById[id][version] = template;
      this._validTemplates.push(template);
    }
    return this;
  }

  /**
   * Validate given template and return error (if any).
   *
   * @param {TemplateDescriptor} template
   *
   * @return {Error} validation error, if any
   */
  _validateTemplate(template) {
    const id = template.id,
      version = template.version || '_',
      schemaVersion = template.$schema && getSchemaVersion(template.$schema);

    // (1) compatibility
    if (schemaVersion && semverCompare(SUPPORTED_SCHEMA_VERSION$1, schemaVersion) < 0) {
      return this._logError(`unsupported element template schema version <${schemaVersion}>. Your installation only supports up to version <${SUPPORTED_SCHEMA_VERSION$1}>. Please update your installation`, template);
    }

    // (2) versioning
    if (this._templatesById[id] && this._templatesById[id][version]) {
      if (version === '_') {
        return this._logError(`template id <${id}> already used`, template);
      } else {
        return this._logError(`template id <${id}> and version <${version}> already used`, template);
      }
    }

    // (3) elementType validation
    const elementTypeError = this._validateElementType(template);
    if (elementTypeError) {
      return elementTypeError;
    }

    // (4) JSON schema compliance
    const schemaValidationResult = validate$1(template);
    const {
      errors: schemaErrors,
      valid
    } = schemaValidationResult;
    if (!valid) {
      filteredSchemaErrors(schemaErrors).forEach(error => {
        this._logError(error.message, template);
      });
      return new Error('invalid template');
    }

    // (5) engines validation
    const enginesError = this._validateEngines(template);
    if (enginesError) {
      return enginesError;
    }
    return null;
  }
  _validateEngines(template) {
    let err;
    forEach(template.engines, (rangeStr, engine) => {
      if (!validRange(rangeStr)) {
        err = this._logError(new Error(`Engine <${engine}> specifies invalid semver range <${rangeStr}>`), template);
      }
    });
    return err;
  }

  /**
   * Validate elementType for given template and return error (if any).
   *
   * @param {TemplateDescriptor} template
   *
   * @return {Error} validation error, if any
   */
  _validateElementType(template) {
    if (template.elementType && template.appliesTo) {
      const elementType = template.elementType.value,
        appliesTo = template.appliesTo;

      // (3.1) template can be applied to elementType
      // prevents cases where the elementType is not part of appliesTo
      if (!appliesTo.find(type => this._isType(elementType, type))) {
        return this._logError(`template does not apply to requested element type <${elementType}>`, template);
      }

      // (3.2) template only applies to same type of element
      // prevent elementTemplates to morph into incompatible types, e.g. Task -> SequenceFlow
      for (const sourceType of appliesTo) {
        if (!this._canMorph(sourceType, elementType)) {
          return this._logError(`can not morph <${sourceType}> into <${elementType}>`, template);
        }
      }
    }
  }

  /**
   * Check if given type is a subtype of given base type.
   *
   * @param {String} type
   * @param {String} baseType
   * @returns {Boolean}
   */
  _isType(type, baseType) {
    const moddleType = this._moddle.getType(type);
    return moddleType && baseType in this._moddle.getElementDescriptor(moddleType).allTypesByName;
  }

  /**
   * Checks if a given type can be morphed into another type.
   *
   * @param {String} sourceType
   * @param {String} targetType
   * @returns {Boolean}
   */
  _canMorph(sourceType, targetType) {
    if (sourceType === targetType) {
      return true;
    }
    const baseType = MORPHABLE_TYPES.find(type => this._isType(sourceType, type));
    if (!baseType) {
      return false;
    }
    return this._isType(targetType, baseType);
  }

  /**
   * Log an error for the given template
   *
   * @param {(String|Error)} err
   * @param {TemplateDescriptor} template
   *
   * @return {Error} logged validation errors
   */
  _logError(err, template) {
    if (isString(err)) {
      if (template) {
        const {
          id,
          name
        } = template;
        err = `template(id: <${id}>, name: <${name}>): ${err}`;
      }
      err = new Error(err);
    }
    this._errors.push(err);
    return err;
  }
  getErrors() {
    return this._errors;
  }
  getValidTemplates() {
    return this._validTemplates;
  }
};

// helpers //////////

/**
 * Extract schema version from schema URI
 *
 * @param {String} schemaUri - for example https://unpkg.com/@camunda/element-templates-json-schema@99.99.99/resources/schema.json
 *
 * @return {String} for example '99.99.99'
 */
function getSchemaVersion(schemaUri) {
  const re = /\d+\.\d+\.\d+/g;
  const match = schemaUri.match(re);
  return match === null ? undefined : match[0];
}

/**
 * Extract only relevant errors of the validation result.
 *
 * The JSON Schema we use under the hood produces more errors than we need for a
 * detected schema violation (for example, unmatched sub-schemas, if-then-rules,
 * `oneOf`-definitions ...).
 *
 * We call these errors "relevant" that have a custom error message defined by us OR
 * are basic data type errors.
 *
 * @param {Array} schemaErrors
 *
 * @return {Array}
 */
function filteredSchemaErrors(schemaErrors) {
  return filter(schemaErrors, err => {
    const {
      instancePath,
      keyword
    } = err;

    // (1) regular errors are customized from the schema
    if (keyword === 'errorMessage') {
      return true;
    }

    // (2) data type errors
    // ignore type errors nested in scopes
    if (keyword === 'type' && instancePath && !instancePath.startsWith('/scopes/')) {
      return true;
    }
    return false;
  });
}

const SUPPORTED_SCHEMA_VERSION = getZeebeSchemaVersion();
const SUPPORTED_SCHEMA_PACKAGE = getZeebeSchemaPackage();

/**
 * A Camunda Cloud element template validator.
 */
class Validator extends Validator$1 {
  constructor(moddle) {
    super(moddle);
  }

  /**
   * Validate given template and return error (if any).
   *
   * @param {TemplateDescriptor} template
   *
   * @return {Error} validation error, if any
   */
  _validateTemplate(template) {
    const id = template.id,
      version = template.version || '_',
      schema = template.$schema,
      schemaVersion = schema && getSchemaVersion(schema);

    // (1) $schema attribute defined
    if (!schema) {
      return this._logError('missing $schema attribute.', template);
    }
    if (!this.isSchemaValid(schema)) {
      return this._logError(`unsupported $schema attribute <${schema}>.`, template);
    }

    // (2) compatibility
    if (schemaVersion && semverCompare(SUPPORTED_SCHEMA_VERSION, schemaVersion) < 0) {
      return this._logError(`unsupported element template schema version <${schemaVersion}>. Your installation only supports up to version <${SUPPORTED_SCHEMA_VERSION}>. Please update your installation`, template);
    }

    // (3) versioning
    if (this._templatesById[id] && this._templatesById[id][version]) {
      if (version === '_') {
        return this._logError(`template id <${id}> already used`, template);
      } else {
        return this._logError(`template id <${id}> and version <${version}> already used`, template);
      }
    }

    // (4) elementType validation
    const elementTypeError = this._validateElementType(template);
    if (elementTypeError) {
      return elementTypeError;
    }

    // (5) JSON schema compliance
    const schemaValidationResult = validateZeebe(template);
    const {
      errors: schemaErrors,
      valid
    } = schemaValidationResult;
    if (!valid) {
      filteredSchemaErrors(schemaErrors).forEach(error => {
        this._logError(error.message, template);
      });
      return new Error('invalid template');
    }

    // (6) engines validation
    const enginesError = this._validateEngines(template);
    if (enginesError) {
      return enginesError;
    }
    return null;
  }
  isSchemaValid(schema) {
    return schema && schema.includes(SUPPORTED_SCHEMA_PACKAGE);
  }
  _validateEngines(template) {
    let err;
    forEach(template.engines, (rangeStr, engine) => {
      if (!validRange(rangeStr)) {
        err = this._logError(new Error(`Engine <${engine}> specifies invalid semver range <${rangeStr}>`), template);
      }
    });
    return err;
  }
}

/**
 * The guy responsible for template loading.
 *
 * Provide the actual templates via the `config.elementTemplates`.
 *
 * That configuration can either be an array of template
 * descriptors or a node style callback to retrieve
 * the templates asynchronously.
 *
 * @param {Array<TemplateDescriptor>|Function} config
 * @param {EventBus} eventBus
 * @param {ElementTemplates} elementTemplates
 * @param {Moddle} moddle
 */
let ElementTemplatesLoader$1 = class ElementTemplatesLoader {
  constructor(config, eventBus, elementTemplates, moddle) {
    this._loadTemplates;
    this._eventBus = eventBus;
    this._elementTemplates = elementTemplates;
    this._moddle = moddle;
    if (isArray(config) || isFunction(config)) {
      this._loadTemplates = config;
    }
    if (config && config.loadTemplates) {
      this._loadTemplates = config.loadTemplates;
    }
    eventBus.on('diagram.init', () => {
      this.reload();
    });
  }
  reload() {
    const loadTemplates = this._loadTemplates;

    // no templates specified
    if (isUndefined$1(loadTemplates)) {
      return;
    }

    // template loader function specified
    if (isFunction(loadTemplates)) {
      return loadTemplates((err, templates) => {
        if (err) {
          return this._templateErrors([err]);
        }
        this.setTemplates(templates);
      });
    }

    // templates array specified
    if (loadTemplates.length) {
      return this.setTemplates(loadTemplates);
    }
  }
  setTemplates(templates) {
    const elementTemplates = this._elementTemplates,
      moddle = this._moddle;
    const validator = new Validator$1(moddle).addAll(templates);
    const errors = validator.getErrors(),
      validTemplates = validator.getValidTemplates();
    elementTemplates.set(validTemplates);
    if (errors.length) {
      this._templateErrors(errors);
    }
  }
  _templateErrors(errors) {
    this._elementTemplates._fire('errors', {
      errors: errors
    });
  }
};
ElementTemplatesLoader$1.$inject = ['config.elementTemplates', 'eventBus', 'elementTemplates', 'moddle'];

/**
 * @param {Object|Array<TemplateDescriptor>|Function} config
 * @param {EventBus} eventBus
 * @param {ElementTemplates} elementTemplates
 * @param {Moddle} moddle
 */
class ElementTemplatesLoader extends ElementTemplatesLoader$1 {
  constructor(config, eventBus, elementTemplates, moddle) {
    super(config, eventBus, elementTemplates, moddle);
    this._elementTemplates = elementTemplates;
  }
  setTemplates(templates) {
    const elementTemplates = this._elementTemplates,
      moddle = this._moddle;
    const validator = new Validator(moddle).addAll(templates);
    const errors = validator.getErrors(),
      validTemplates = validator.getValidTemplates();
    elementTemplates.set(validTemplates);
    if (errors.length) {
      this._templateErrors(errors);
    }
  }
}
ElementTemplatesLoader.$inject = ['config.elementTemplates', 'eventBus', 'elementTemplates', 'moddle'];

/**
 * Create a new element and set its parent.
 *
 * @param {String} elementType of the new element
 * @param {Object} properties of the new element in key-value pairs
 * @param {moddle.object} parent of the new element
 * @param {BpmnFactory} factory which creates the new element
 *
 * @returns {djs.model.Base} element which is created
 */
function createElement(elementType, properties, parent, factory) {
  const element = factory.create(elementType, properties);
  if (parent) {
    element.$parent = parent;
  }
  return element;
}

/**
 * generate a semantic id with given prefix
 */
function nextId(prefix) {
  const ids = new Ids([32, 32, 1]);
  return ids.nextPrefixed(prefix);
}
function getRoot(businessObject) {
  let parent = businessObject;
  while (parent.$parent) {
    parent = parent.$parent;
  }
  return parent;
}
function filterElementsByType(objectList, type) {
  const list = objectList || [];
  return list.filter(element => is(element, type));
}
function findRootElementsByType(businessObject, referencedType) {
  const root = getRoot(businessObject);
  return filterElementsByType(root.get('rootElements'), referencedType);
}
function findRootElementById(businessObject, type, id) {
  const elements = findRootElementsByType(businessObject, type);
  return elements.find(element => element.id === id);
}

/**
 * Check if element type is a subprocess (either regular or AdHoc).
 *
 * @param {string} elementType
 * @returns {boolean}
 */
function isSubprocess(elementType) {
  return elementType === 'bpmn:SubProcess' || elementType === 'bpmn:AdHocSubProcess' || elementType === 'bpmn:Transaction';
}

/**
 * Create an input parameter representing the given
 * binding and value.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createInputParameter$1(binding, value, bpmnFactory) {
  const {
    name
  } = binding;
  return bpmnFactory.create('zeebe:Input', {
    source: value,
    target: name
  });
}

/**
 * Create an output parameter representing the given
 * binding and value.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createOutputParameter$1(binding, value, bpmnFactory) {
  const {
    source
  } = binding;
  return bpmnFactory.create('zeebe:Output', {
    source,
    target: value
  });
}

/**
 * Create a task header representing the given
 * binding and value.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createTaskHeader(binding, value, bpmnFactory) {
  const {
    key
  } = binding;
  return bpmnFactory.create('zeebe:Header', {
    key,
    value
  });
}

/**
 * Create a task definition representing the given value.
 *
 * @param {object} attrs
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createTaskDefinition(attrs = {}, bpmnFactory) {
  return bpmnFactory.create('zeebe:TaskDefinition', attrs);
}

/**
 * Create zeebe:Property from the given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createZeebeProperty(binding, value = '', bpmnFactory) {
  const {
    name
  } = binding;
  return bpmnFactory.create('zeebe:Property', {
    name,
    value
  });
}

/**
 * Retrieves whether an element should be updated for a given property.
 *
 * That matches once
 * a) the property value is not empty, or
 * b) the property is not optional
 *
 * @param {String} value
 * @param {Object} property
 * @returns {Boolean}
 */
function shouldUpdate(value, property) {
  const {
    optional
  } = property;
  return value || !optional;
}

/**
 * Gets or, in case not existent, creates extension element for given element.
 *
 * @param {djs.model.Base} element
 * @param {String} type
 * @param {BpmnFactory} bpmnFactory
 * @returns {ModdleElement}
 */
function ensureExtension(element, type, bpmnFactory) {
  const businessObject = getBusinessObject(element);
  let extensionElements = businessObject.get('extensionElements');
  if (!extensionElements) {
    extensionElements = createElement('bpmn:ExtensionElements', {}, businessObject, bpmnFactory);
    businessObject.set('extensionElements', extensionElements);
  }
  let extension = findExtension$1(extensionElements, type);
  if (!extension) {
    extension = bpmnFactory.create(type);
    extension.$parent = extensionElements;
    extensionElements.get('values').push(extension);
  }
  return extension;
}

function getTaskDefinitionPropertyName(binding) {
  return binding.type === ZEEBE_TASK_DEFINITION_TYPE_TYPE ? 'type' : binding.property;
}

function removeRootElement(rootElement, injector) {
  const modeling = injector.get('modeling'),
    canvas = injector.get('canvas'),
    bpmnjs = injector.get('bpmnjs');
  const element = canvas.getRootElement(),
    definitions = bpmnjs.getDefinitions(),
    rootElements = definitions.get('rootElements');
  const newRootElements = rootElements.filter(e => e !== rootElement);

  // short-circuit to prevent unnecessary updates
  if (newRootElements.length === rootElements.length) {
    return;
  }
  modeling.updateModdleProperties(element, definitions, {
    rootElements: newRootElements
  });
}

/**
 * Remove message from element and the diagram.
 *
 * @param {import('bpmn-js/lib/model/Types').Element} element
 * @param {import('didi').Injector} injector
 */
function removeMessage(element, injector) {
  const modeling = injector.get('modeling');
  const bo = getReferringElement(element);

  // Event does not have an event definition
  if (!bo) {
    return;
  }
  const message = findMessage(bo);
  if (!message) {
    return;
  }
  modeling.updateModdleProperties(element, bo, {
    messageRef: undefined
  });
  removeRootElement(message, injector);
}

/**
 * Remove signal from element and the diagram.
 *
 * @param {import('bpmn-js/lib/model/Types').Element} element
 * @param {import('didi').Injector} injector
 */
function removeSignal(element, injector) {
  const modeling = injector.get('modeling');
  const bo = getReferringElement(element);

  // Event does not have an event definition
  if (!bo) {
    return;
  }
  const signal = findSignal(bo);
  if (!signal) {
    return;
  }
  modeling.updateModdleProperties(element, bo, {
    signalRef: undefined
  });
  removeRootElement(signal, injector);
}
function getReferringElement(element) {
  const bo = getBusinessObject(element);
  if (is(bo, 'bpmn:Event')) {
    return bo.get('eventDefinitions')[0];
  }
  return bo;
}

const EXPRESSION_TYPES$1 = ['bpmn:Expression', 'bpmn:FormalExpression'];
function isExpression(element, propertyName) {
  const bo = getBusinessObject(element);
  const descriptor = bo.$descriptor.propertiesByName[propertyName];
  return descriptor && EXPRESSION_TYPES$1.includes(descriptor.type);
}
function createExpression(value, parent, bpmnFactory) {
  const expression = createElement('bpmn:FormalExpression', {
    body: value
  }, parent, bpmnFactory);
  return expression;
}
function getExpressionValue(element, propertyName) {
  const bo = getBusinessObject(element);
  const expression = bo.get(propertyName);
  return expression?.get('body') || undefined;
}

/**
 * @typedef {import('bpmn-js/lib/model/Types').Element} Element
 */

/**
 * Applies an element template to an element. Sets `zeebe:modelerTemplate` and
 * `zeebe:modelerTemplateVersion`.
 */
let ChangeElementTemplateHandler$1 = class ChangeElementTemplateHandler {
  constructor(bpmnFactory, bpmnReplace, commandStack, modeling, moddleCopy, injector) {
    this._bpmnFactory = bpmnFactory;
    this._bpmnReplace = bpmnReplace;
    this._modeling = modeling;
    this._moddleCopy = moddleCopy;
    this._commandStack = commandStack;
    this._injector = injector;
  }

  /**
   * Change element template. If new template is specified, apply it. If old
   * template is specified, too, update from old to new. If only old template is
   * specified, remove it. Optionally, remove old template properties.
   *
   * @param {Object} context
   * @param {Element} context.element
   * @param {Object} [context.oldTemplate]
   * @param {Object} [context.newTemplate]
   * @param {boolean} [context.removeProperties=false]
   */
  preExecute(context) {
    const {
      newTemplate,
      oldTemplate,
      removeProperties = false
    } = context;
    let element = context.element;

    // update zeebe:modelerTemplate attribute
    this._updateZeebeModelerTemplate(element, newTemplate);

    // update zeebe:modelerTemplateIcon
    this._updateZeebeModelerTemplateIcon(element, newTemplate);

    // update element type
    element = context.element = this._updateElementType(element, oldTemplate, newTemplate);
    if (!newTemplate && !removeProperties) {
      return;
    }

    // update properties
    this._updateProperties(element, oldTemplate, newTemplate);

    // update zeebe:TaskDefinition
    this._updateZeebeTaskDefinition(element, oldTemplate, newTemplate);

    // update zeebe:Input and zeebe:Output properties
    this._updateZeebeInputOutputParameterProperties(element, oldTemplate, newTemplate);

    // update zeebe:Header properties
    this._updateZeebeTaskHeaderProperties(element, oldTemplate, newTemplate);

    // update zeebe:Property properties
    this._updateZeebePropertyProperties(element, oldTemplate, newTemplate);
    this._updateMessage(element, oldTemplate, newTemplate);
    this._updateSignal(element, oldTemplate, newTemplate);
    this._updateTimerEventDefinition(element, oldTemplate, newTemplate);
    this._updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate);
    this._updateCalledElement(element, oldTemplate, newTemplate);
    this._updateLinkedResources(element, oldTemplate, newTemplate);
    this._updateZeebeUserTask(element, newTemplate);
    this._updateCalledDecision(element, oldTemplate, newTemplate);
    this._updateZeebeFormDefinition(element, oldTemplate, newTemplate);
    this._updateScriptTask(element, oldTemplate, newTemplate);
    this._updateZeebeAssignmentDefinition(element, oldTemplate, newTemplate);
    this._updateZeebePriorityDefinition(element, oldTemplate, newTemplate);
    this._updateAdHoc(element, oldTemplate, newTemplate);
    this._updateZeebeTaskSchedule(element, oldTemplate, newTemplate);
  }
  _getOrCreateExtensionElements(element, businessObject = getBusinessObject(element)) {
    const bpmnFactory = this._bpmnFactory,
      modeling = this._modeling;
    let extensionElements = businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
        values: []
      });
      extensionElements.$parent = businessObject;
      modeling.updateModdleProperties(element, businessObject, {
        extensionElements: extensionElements
      });
    }
    return extensionElements;
  }

  /**
   * Update zeebe:modelerTemplate property.
   *
   * @param {Element} element
   * @param {Object} [newTemplate]
   */
  _updateZeebeModelerTemplate(element, newTemplate) {
    const modeling = this._modeling;
    if (!newTemplate) {
      modeling.updateProperties(element, {
        'zeebe:modelerTemplate': undefined,
        'zeebe:modelerTemplateVersion': undefined
      });
      return;
    }
    const newId = newTemplate.id;
    const newVersion = newTemplate.version;
    if (getTemplateId$1(element) !== newId || getTemplateVersion$1(element) !== newVersion) {
      modeling.updateProperties(element, {
        'zeebe:modelerTemplate': newId,
        'zeebe:modelerTemplateVersion': newVersion
      });
    }
  }

  /**
   * Update zeebe:modelerTemplateIcon property.
   *
   * @param {Element} element
   * @param {Object} [newTemplate]
   */
  _updateZeebeModelerTemplateIcon(element, newTemplate) {
    const modeling = this._modeling;
    if (!newTemplate) {
      modeling.updateProperties(element, {
        'zeebe:modelerTemplateIcon': undefined
      });
      return;
    }
    const newIcon = newTemplate.icon;
    const newIconContents = newIcon && newIcon.contents;
    if (getTemplateIcon(element) !== newIconContents) {
      modeling.updateProperties(element, {
        'zeebe:modelerTemplateIcon': newIconContents
      });
    }
  }

  /**
   * Update BPMN properties.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   *
   * @returns {Element}
   */
  _updateProperties(element, oldTemplate, newTemplate) {
    const commandStack = this._commandStack;
    const businessObject = getBusinessObject(element);
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === 'property';
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === 'property';
      });
    }

    // Remove old properties
    const propertiesToRemove = oldProperties.filter(oldProperty => {
      return !newProperties.find(newProperty => newProperty.binding.name === oldProperty.binding.name);
    });
    if (propertiesToRemove.length) {
      const properties = propertiesToRemove.reduce((properties, property) => {
        properties[property.binding.name] = undefined;
        return properties;
      }, {});
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties
      });
    }
    if (!newProperties.length) {
      return;
    }

    // Add new or update old properties
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = getDefaultValue(newProperty),
        changedElement = businessObject;
      const properties = {};
      if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
        return;
      }
      let assignedValue = newPropertyValue;
      if (isExpression(businessObject, newBindingName)) {
        assignedValue = createExpression(newPropertyValue, businessObject, this._bpmnFactory);
      }
      properties[newBindingName] = assignedValue;
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties
      });
    });
  }

  /**
   * Update `zeebe:TaskDefinition` properties of specified business object. This
   * can only exist in `bpmn:ExtensionElements`.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateZeebeTaskDefinition(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: TASK_DEFINITION_TYPES,
      extensionType: 'zeebe:TaskDefinition',
      getPropertyName: getTaskDefinitionPropertyName
    });
  }

  /**
   * Update `zeebe:Input` and `zeebe:Output` properties of specified business
   * object. Both can only exist in `zeebe:ioMapping` which can exist in `bpmn:ExtensionElements`.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateZeebeInputOutputParameterProperties(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    let newProperties = [];
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === 'zeebe:input' || newBindingType === 'zeebe:output';
      });
    }
    const businessObject = this._getOrCreateExtensionElements(element);
    let ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');

    // (1) remove old mappings if no new specified
    if (!newProperties.length) {
      if (!ioMapping) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: without(businessObject.get('values'), ioMapping)
        }
      });
    }
    if (!ioMapping) {
      ioMapping = bpmnFactory.create('zeebe:IoMapping');
      ioMapping.$parent = businessObject;
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: [...businessObject.get('values'), ioMapping]
        }
      });
    }
    const oldInputs = ioMapping.get('zeebe:inputParameters') ? ioMapping.get('zeebe:inputParameters').slice() : [];
    const oldOutputs = ioMapping.get('zeebe:outputParameters') ? ioMapping.get('zeebe:outputParameters').slice() : [];
    let propertyName;
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        inputOrOutput = findBusinessObject(businessObject, newProperty),
        newPropertyValue = getDefaultValue(newProperty),
        newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      let newInputOrOutput, properties;

      // (2) update old inputs and outputs
      if (inputOrOutput) {
        // (2a) exclude old inputs and outputs from cleanup, unless
        // a) optional and has empty value, and
        // b) not changed
        if (shouldUpdate(newPropertyValue, newProperty) || shouldKeepValue(inputOrOutput, oldProperty, newProperty)) {
          if (is(inputOrOutput, 'zeebe:Input')) {
            remove$1(oldInputs, inputOrOutput);
          } else {
            remove$1(oldOutputs, inputOrOutput);
          }
        }

        // (2a) do updates (unless changed)
        if (!shouldKeepValue(inputOrOutput, oldProperty, newProperty)) {
          if (is(inputOrOutput, 'zeebe:Input')) {
            properties = {
              source: newPropertyValue
            };
          } else {
            properties = {
              target: newPropertyValue
            };
          }
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: inputOrOutput,
            properties
          });
        }
      }

      // (3) add new inputs and outputs (unless optional)
      else if (shouldUpdate(newPropertyValue, newProperty)) {
        if (newBindingType === 'zeebe:input') {
          propertyName = 'inputParameters';
          newInputOrOutput = createInputParameter$1(newBinding, newPropertyValue, bpmnFactory);
        } else {
          propertyName = 'outputParameters';
          newInputOrOutput = createOutputParameter$1(newBinding, newPropertyValue, bpmnFactory);
        }
        newInputOrOutput.$parent = ioMapping;
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: ioMapping,
          properties: {
            [propertyName]: [...ioMapping.get(propertyName), newInputOrOutput]
          }
        });
      }
    });

    // (4) remove old inputs and outputs
    if (oldInputs.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: ioMapping,
        properties: {
          inputParameters: without(ioMapping.get('inputParameters'), inputParameter => oldInputs.includes(inputParameter))
        }
      });
    }
    if (oldOutputs.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: ioMapping,
        properties: {
          outputParameters: without(ioMapping.get('outputParameters'), outputParameter => oldOutputs.includes(outputParameter))
        }
      });
    }
  }

  /**
   * Update `zeebe:Header` properties of specified business
   * object. Those can only exist in `zeebe:taskHeaders` which can exist in `bpmn:ExtensionElements`.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateZeebeTaskHeaderProperties(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    let newProperties = [];
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === 'zeebe:taskHeader';
      });
    }
    const businessObject = this._getOrCreateExtensionElements(element);
    let taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');

    // (1) remove old headers if no new specified
    if (!newProperties.length) {
      if (!taskHeaders) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: without(businessObject.get('values'), taskHeaders)
        }
      });
    }
    if (!taskHeaders) {
      taskHeaders = bpmnFactory.create('zeebe:TaskHeaders');
      taskHeaders.$parent = businessObject;
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: [...businessObject.get('values'), taskHeaders]
        }
      });
    }
    const oldHeaders = taskHeaders.get('zeebe:values') ? taskHeaders.get('zeebe:values').slice() : [];
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        oldHeader = findBusinessObject(businessObject, newProperty),
        newPropertyValue = getDefaultValue(newProperty),
        newBinding = newProperty.binding;

      // (2) update old headers
      if (oldHeader) {
        if (!shouldKeepValue(oldHeader, oldProperty, newProperty)) {
          const properties = {
            value: newPropertyValue
          };
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldHeader,
            properties
          });
        }
        remove$1(oldHeaders, oldHeader);
      }

      // (3) add new (non-empty) headers
      else if (newPropertyValue) {
        const newHeader = createTaskHeader(newBinding, newPropertyValue, bpmnFactory);
        newHeader.$parent = taskHeaders;
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: taskHeaders,
          properties: {
            values: [...taskHeaders.get('values'), newHeader]
          }
        });
      }
    });

    // (4) remove old headers
    if (oldHeaders.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: taskHeaders,
        properties: {
          values: without(taskHeaders.get('values'), header => oldHeaders.includes(header))
        }
      });
    }
  }

  /**
   * Update zeebe:Property properties of zeebe:Properties extension element.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateZeebePropertyProperties(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    let newProperties = [];
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === 'zeebe:property';
      });
    }
    const businessObject = this._getOrCreateExtensionElements(element);
    let zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');

    // (1) remove old zeebe:Properties if no new zeebe:Property properties
    if (!newProperties.length) {
      if (!zeebeProperties) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: without(businessObject.get('values'), zeebeProperties)
        }
      });
    }
    if (!zeebeProperties) {
      zeebeProperties = bpmnFactory.create('zeebe:Properties');
      zeebeProperties.$parent = businessObject;
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: [...businessObject.get('values'), zeebeProperties]
        }
      });
    }
    const oldZeebeProperties = zeebeProperties.get('properties') ? zeebeProperties.get('properties').slice() : [];
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        oldZeebeProperty = findBusinessObject(businessObject, newProperty),
        newPropertyValue = getDefaultValue(newProperty),
        newBinding = newProperty.binding;

      // (2) update old zeebe:Property
      if (oldZeebeProperty) {
        if (shouldUpdate(newPropertyValue, newProperty) || shouldKeepValue(oldZeebeProperty, oldProperty, newProperty)) {
          remove$1(oldZeebeProperties, oldZeebeProperty);
        }
        if (!shouldKeepValue(oldZeebeProperty, oldProperty, newProperty)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldZeebeProperty,
            properties: {
              value: newPropertyValue
            }
          });
        }
      }

      // (3) add new zeebe:Property
      else if (shouldUpdate(newPropertyValue, newProperty)) {
        const newProperty = createZeebeProperty(newBinding, newPropertyValue, bpmnFactory);
        newProperty.$parent = zeebeProperties;
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: zeebeProperties,
          properties: {
            properties: [...zeebeProperties.get('properties'), newProperty]
          }
        });
      }
    });

    // (4) remove old zeebe:Property
    if (oldZeebeProperties.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: zeebeProperties,
        properties: {
          properties: without(zeebeProperties.get('properties'), zeebeProperty => oldZeebeProperties.includes(zeebeProperty))
        }
      });
    }
  }
  _updateMessage(element, oldTemplate, newTemplate) {
    // update bpmn:Message properties
    this._updateMessageProperties(element, oldTemplate, newTemplate);

    // update bpmn:Message zeebe:subscription properties
    this._updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate);
    if (!newTemplate || !hasMessageProperties(newTemplate)) {
      removeMessage(element, this._injector);
    }
  }
  _updateSignal(element, oldTemplate, newTemplate) {
    // update bpmn:Signal properties
    this._updateSignalProperties(element, oldTemplate, newTemplate);
    if (!newTemplate || !hasSignalProperties(newTemplate)) {
      removeSignal(element, this._injector);
    }
  }

  /**
   * Update bpmn:Message properties.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateMessageProperties(element, oldTemplate, newTemplate) {
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === MESSAGE_PROPERTY_TYPE;
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === MESSAGE_PROPERTY_TYPE;
      });
    }
    const removedProperties = oldProperties.filter(oldProperty => {
      return !newProperties.find(newProperty => newProperty.binding.name === oldProperty.binding.name);
    });
    let message = this._getMessage(element);
    message && removedProperties.forEach(removedProperty => {
      this._modeling.updateModdleProperties(element, message, {
        [removedProperty.binding.name]: undefined
      });
    });
    if (!newProperties.length) {
      return;
    }
    message = this._ensureMessage(element, newTemplate);
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = getDefaultValue(newProperty),
        changedElement = message;
      let properties = {};
      if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
        return;
      }
      properties[newBindingName] = newPropertyValue;
      this._modeling.updateModdleProperties(element, changedElement, properties);
    });
  }

  /**
   * Update bpmn:Signal properties.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateSignalProperties(element, oldTemplate, newTemplate) {
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === SIGNAL_PROPERTY_TYPE;
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === SIGNAL_PROPERTY_TYPE;
      });
    }
    const removedProperties = oldProperties.filter(oldProperty => {
      return !newProperties.find(newProperty => newProperty.binding.name === oldProperty.binding.name);
    });
    let signal = this._getSignal(element);
    signal && removedProperties.forEach(removedProperty => {
      this._modeling.updateModdleProperties(element, signal, {
        [removedProperty.binding.name]: undefined
      });
    });
    if (!newProperties.length) {
      return;
    }

    // The template schema guarantees that if signal#property is active
    signal = this._ensureSignal(element, newTemplate);
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = getDefaultValue(newProperty),
        changedElement = signal;
      let properties = {};
      if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
        return;
      }
      properties[newBindingName] = newPropertyValue;
      this._modeling.updateModdleProperties(element, changedElement, properties);
    });
  }

  /**
   * Update bpmn:TimerEventDefinition properties.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateTimerEventDefinition(element, oldTemplate, newTemplate) {
    const commandStack = this._commandStack;
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === TIMER_EVENT_DEFINITION_PROPERTY_TYPE;
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === TIMER_EVENT_DEFINITION_PROPERTY_TYPE;
      });
    }
    const removedProperties = oldProperties.filter(oldProperty => {
      return !newProperties.find(newProperty => newProperty.binding.name === oldProperty.binding.name);
    });
    const timerEventDefinition = findTimerEventDefinition(element);
    if (!timerEventDefinition) {
      return;
    }

    // Remove old timer properties that are no longer in the template
    removedProperties.forEach(removedProperty => {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: timerEventDefinition,
        properties: {
          [removedProperty.binding.name]: undefined
        }
      });
    });
    if (!newProperties.length) {
      return;
    }

    // First, check if we need to auto-convert to non-interrupting for timeCycle
    // This must happen BEFORE setting the timer property, otherwise the
    // CleanUpTimerExpressionBehavior will remove it
    const hasTimeCycle = newProperties.some(p => p.binding.name === 'timeCycle');
    if (hasTimeCycle) {
      this._ensureNonInterruptingForTimeCycle(element);
    }

    // Set new timer properties
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = getDefaultValue(newProperty);

      // Check if we should keep the existing value
      if (shouldKeepValue(timerEventDefinition, oldProperty, newProperty)) {
        return;
      }

      // Create or update the FormalExpression
      let expression = timerEventDefinition.get(newBindingName);
      if (!expression) {
        expression = createExpression(newPropertyValue, timerEventDefinition, this._bpmnFactory);
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: timerEventDefinition,
          properties: {
            // remove any existing timer values, satisfying that there is only one
            timeDate: undefined,
            timeDuration: undefined,
            timeCycle: undefined,
            [newBindingName]: expression
          }
        });
      } else {
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: expression,
          properties: {
            body: newPropertyValue
          }
        });
      }
    });
  }

  /**
   * Ensure element is non-interrupting when using timeCycle.
   * - Boundary events: set cancelActivity to false
   * - Event subprocess start events: set isInterrupting to false
   *
   * @param {djs.model.Base} element
   */
  _ensureNonInterruptingForTimeCycle(element) {
    const commandStack = this._commandStack;
    const businessObject = getBusinessObject(element);

    // Boundary events with timeCycle must be non-interrupting
    if (is(element, 'bpmn:BoundaryEvent')) {
      if (businessObject.get('cancelActivity') !== false) {
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties: {
            cancelActivity: false
          }
        });
      }
      return;
    }

    // Start events in event subprocess with timeCycle must be non-interrupting
    if (is(element, 'bpmn:StartEvent')) {
      const parent = businessObject.$parent;
      if (is(parent, 'bpmn:SubProcess') && parent.get('triggeredByEvent')) {
        if (businessObject.get('isInterrupting') !== false) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: businessObject,
            properties: {
              isInterrupting: false
            }
          });
        }
      }
    }
  }

  /**
   * Update bpmn:Message#zeebe:subscription properties.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate) {
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE;
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE;
      });
    }
    const removedProperties = oldProperties.filter(oldProperty => {
      return !newProperties.find(newProperty => newProperty.binding.name === oldProperty.binding.name);
    });
    if (!newProperties.length && !removedProperties.length) {
      return;
    }
    let message = this._getMessage(element);
    if (!newProperties.length && !message) {
      return;
    }
    message = this._ensureMessage(element, newTemplate);
    const messageExtensionElements = this._getOrCreateExtensionElements(element, message);
    const zeebeSubscription = this._getSubscription(element, message);
    const propertiesToSet = newProperties.reduce((properties, newProperty) => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = getDefaultValue(newProperty),
        changedElement = zeebeSubscription;
      if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
        return properties;
      }
      properties[newBindingName] = newPropertyValue;
      return properties;
    }, {});

    // Update zeebe Subscription
    if (zeebeSubscription) {
      this._modeling.updateModdleProperties(element, zeebeSubscription, propertiesToSet);
    } else {
      // create new Subscription
      const newSubscription = createElement('zeebe:Subscription', propertiesToSet, message, this._bpmnFactory);
      this._modeling.updateModdleProperties(element, messageExtensionElements, {
        values: [...messageExtensionElements.get('values'), newSubscription]
      });
    }

    // Remove old properties
    if (!oldTemplate || !zeebeSubscription) {
      return;
    }
    const propertiesToRemove = removedProperties.reduce((properties, removedProperty) => {
      properties[removedProperty.binding.name] = undefined;
      return properties;
    }, {});
    this._modeling.updateModdleProperties(element, zeebeSubscription, propertiesToRemove);
  }
  _updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate) {
    const businessObject = getBusinessObject(element);
    const referencedElement = findMessage(businessObject) || findSignal(businessObject);
    if (referencedElement && newTemplate && getTemplateId$1(referencedElement) !== newTemplate.id) {
      this._modeling.updateModdleProperties(element, referencedElement, {
        'zeebe:modelerTemplate': newTemplate.id
      });
    }
  }
  _getSubscription(element, bo) {
    const extensionElements = this._getOrCreateExtensionElements(element, bo);
    const extension = findExtension$1(extensionElements, 'zeebe:Subscription');
    if (extension) {
      return extension;
    }
  }
  _createMessage(element, template) {
    let bo = getBusinessObject(element);
    if (is(bo, 'bpmn:Event')) {
      bo = bo.get('eventDefinitions')[0];
    }
    const message = this._bpmnFactory.create('bpmn:Message', {
      'zeebe:modelerTemplate': template.id
    });
    message.$parent = getRoot(bo);
    this._modeling.updateModdleProperties(element, bo, {
      messageRef: message
    });
    return message;
  }
  _createSignal(element, template) {
    let bo = getBusinessObject(element);
    if (is(bo, 'bpmn:Event')) {
      bo = bo.get('eventDefinitions')[0];
    }
    const signal = this._bpmnFactory.create('bpmn:Signal', {
      'zeebe:modelerTemplate': template.id
    });
    signal.$parent = getRoot(bo);
    this._modeling.updateModdleProperties(element, bo, {
      signalRef: signal
    });
    return signal;
  }
  _getMessage(element) {
    let bo = getBusinessObject(element);
    if (is(bo, 'bpmn:Event')) {
      bo = bo.get('eventDefinitions')[0];
    }
    return bo && bo.get('messageRef');
  }
  _getSignal(element) {
    let bo = getBusinessObject(element);
    if (!is(bo, 'bpmn:Event')) {
      return;
    }
    const eventDefinition = bo.get('eventDefinitions')[0];
    return eventDefinition && eventDefinition.get('signalRef');
  }
  _ensureMessage(element, newTemplate) {
    const message = this._getMessage(element);
    if (!newTemplate) {
      return message;
    }

    // message is already templated, so we use it
    if (message && message.get('zeebe:modelerTemplate')) {
      return message;
    }
    const newMessage = this._createMessage(element, newTemplate);

    // no message is set on the element, so no properties to copy
    if (!message) {
      return newMessage;
    }
    return this._moddleCopy.copyElement(message, newMessage, ['name', 'extensionElements']);
  }
  _ensureSignal(element, newTemplate) {
    const signal = this._getSignal(element);
    if (!newTemplate) {
      return signal;
    }

    // signal is already templated, so we use it
    if (signal && signal.get('zeebe:modelerTemplate')) {
      return signal;
    }
    const newSignal = this._createSignal(element, newTemplate);

    // no signal is set on the element, so no properties to copy
    if (!signal) {
      return newSignal;
    }
    return this._moddleCopy.copyElement(signal, newSignal, ['name']);
  }

  /**
   * Update `zeebe:CalledElement` properties of specified business object. This
   * can only exist in `bpmn:ExtensionElements`.
   *
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateCalledElement(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_CALLED_ELEMENT],
      extensionType: 'zeebe:CalledElement',
      getPropertyName: binding => binding.property
    });
  }

  /**
   * Update `zeebe:CalledDecision` properties of specified business object. This
   * can only exist in `bpmn:ExtensionElements`.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateCalledDecision(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_CALLED_DECISION],
      extensionType: 'zeebe:CalledDecision',
      getPropertyName: binding => binding.property
    });
  }

  /**
   * Update `zeebe:Script` properties of specified business object. This
   * can only exist in `bpmn:ExtensionElements`.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateScriptTask(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_SCRIPT_TASK],
      extensionType: 'zeebe:Script',
      getPropertyName: binding => binding.property
    });
  }
  _updateZeebeAssignmentDefinition(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_ASSIGNMENT_DEFINITION],
      extensionType: 'zeebe:AssignmentDefinition',
      getPropertyName: binding => binding.property
    });
  }
  _updateZeebePriorityDefinition(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_PRIORITY_DEFINITION],
      extensionType: 'zeebe:PriorityDefinition',
      getPropertyName: binding => binding.property
    });
  }
  _updateAdHoc(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_AD_HOC],
      extensionType: 'zeebe:AdHoc',
      getPropertyName: binding => binding.property
    });
  }
  _updateZeebeTaskSchedule(element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_TASK_SCHEDULE],
      extensionType: 'zeebe:TaskSchedule',
      getPropertyName: binding => binding.property
    });
  }

  /**
   * Replaces the element with the specified elementType.
   * Takes into account the eventDefinition for events.
   *
   * @param {djs.model.Base} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   */
  _updateElementType(element, oldTemplate, newTemplate) {
    if (!newTemplate) {
      return element;
    }

    // determine new task type
    const newType = newTemplate.elementType;
    if (!newType) {
      return element;
    }

    // Do not replace if the element type did not change
    if (!shouldUpdateElementType(element, oldTemplate, newType)) {
      return element;
    }
    const replacement = {
      type: newType.value
    };
    if (newType.eventDefinition) {
      replacement.eventDefinitionType = newType.eventDefinition;
    }
    const replacedElement = this._bpmnReplace.replaceElement(element, replacement);
    return replacedElement;
  }
  _updateLinkedResources(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    let oldProperties = [],
      newProperties = [];
    if (oldTemplate) {
      oldProperties = oldTemplate.properties.filter(oldProperty => {
        const oldBinding = oldProperty.binding,
          oldBindingType = oldBinding.type;
        return oldBindingType === ZEEBE_LINKED_RESOURCE_PROPERTY;
      });
    }
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return newBindingType === ZEEBE_LINKED_RESOURCE_PROPERTY;
      });
    }
    const extensionElements = this._getOrCreateExtensionElements(element);
    let linkedResources = findExtension$1(extensionElements, 'zeebe:LinkedResources');

    // (1) remove properties if no new specified
    if (!newProperties.length) {
      if (!linkedResources) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElements,
        properties: {
          values: without(extensionElements.get('values'), linkedResources)
        }
      });
      return;
    }
    if (!linkedResources) {
      linkedResources = bpmnFactory.create('zeebe:LinkedResources');
      linkedResources.$parent = extensionElements;
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElements,
        properties: {
          values: [...extensionElements.get('values'), linkedResources]
        }
      });
    }
    const unusedLinkedResources = linkedResources.get('values').slice();
    const propertiesToRemove = oldProperties.slice();
    newProperties.forEach(newLinkedResource => {
      const oldProperty = findOldProperty$1(oldTemplate, newLinkedResource),
        oldLinkedResource = findBusinessObject(extensionElements, newLinkedResource),
        newPropertyValue = getDefaultValue(newLinkedResource),
        newBinding = newLinkedResource.binding;
      if (oldProperty) {
        remove$1(propertiesToRemove, oldProperty);
      }

      // (2) update old LinkedResources
      if (oldLinkedResource) {
        if (shouldUpdate(newPropertyValue, newLinkedResource) || shouldKeepValue(oldLinkedResource, oldProperty, newLinkedResource)) {
          remove$1(unusedLinkedResources, oldLinkedResource);
        }
        if (!shouldKeepValue(oldLinkedResource, oldProperty, newLinkedResource)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldLinkedResource,
            properties: {
              [newBinding.property]: newPropertyValue
            }
          });
        }
      }

      // (3) add new linkedResources
      else if (shouldUpdate(newPropertyValue, newLinkedResource)) {
        const newProperties = {
          linkName: newBinding.linkName,
          [newBinding.property]: newPropertyValue
        };
        const newLinkedResource = createElement('zeebe:LinkedResource', newProperties, extensionElements, bpmnFactory);
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: linkedResources,
          properties: {
            values: [...linkedResources.get('values'), newLinkedResource]
          }
        });
      }
    });

    // (4) remove unused linkedResources
    if (unusedLinkedResources.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: linkedResources,
        properties: {
          values: without(linkedResources.get('values'), linkedResource => unusedLinkedResources.includes(linkedResource))
        }
      });
    }

    // (5) remove unused resource properties
    propertiesToRemove.forEach(unusedResourceProperty => {
      const oldLinkedResource = findBusinessObject(extensionElements, unusedResourceProperty);
      const oldBinding = unusedResourceProperty.binding;

      // No property was reused and element was removed in previous step
      if (!oldLinkedResource) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: oldLinkedResource,
        properties: {
          [oldBinding.property]: undefined
        }
      });
    });
  }
  _updateZeebeUserTask = function (element, newTemplate) {
    const commandStack = this._commandStack;
    const bpmnFactory = this._bpmnFactory;

    // check if template has zeebe:userTask binding property
    const hasBinding = newTemplate?.properties.some(property => property.binding.type === ZEEBE_USER_TASK);

    // check if element has zeebe:UserTask extension element
    const extensionElements = getBusinessObject(element).get('extensionElements');
    const userTaskExtension = findExtension$1(element, 'zeebe:UserTask');
    if (newTemplate && newTemplate.elementType?.value !== 'bpmn:UserTask') {
      return;
    }

    // remove zeebe:UserTask if no binding
    if (userTaskExtension) {
      !hasBinding && commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElements,
        properties: {
          values: without(extensionElements.get('values'), userTaskExtension)
        }
      });
      return;
    }
    if (!hasBinding) {
      return;
    }

    // create new zeebe:UserTask extension element
    const zeebeUserTask = bpmnFactory.create('zeebe:UserTask');
    zeebeUserTask.$parent = extensionElements;
    commandStack.execute('element.updateModdleProperties', {
      element,
      moddleElement: extensionElements,
      properties: {
        values: [...extensionElements.get('values'), zeebeUserTask]
      }
    });
  };

  /**
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateZeebeFormDefinition = function (element, oldTemplate, newTemplate) {
    this._updateSingleExtensionElement(element, oldTemplate, newTemplate, {
      bindingTypes: [ZEEBE_FORM_DEFINITION],
      extensionType: 'zeebe:FormDefinition',
      getPropertyName: binding => binding.property
    });
  };

  /**
   * Generic handler for updating extension elements that are single instances with properties.
   *
   * @example
   * For this template
   * ```json
   * {
   *   properties: [
   *    { binding: { type: 'zeebe:taskDefinition', property: 'type' }, value: 'myTaskType' },
   *    {binding: { type: 'zeebe:taskDefinition', property: 'retries' }, value: 3}
   *   ]
   * }
   *```
   * `getPropertyName` should return `binding.property`.
   *
   * `bindingTypes` should be an array of binding types to consider, e.g. `['zeebe:taskDefinition']`.
   *
   * `extensionType` should be the type of the extension element to update, e.g. `'zeebe:TaskDefinition'`.
   *
   * The resulting XML will look like this:
   *
   * ```xml
   * <bpmn:extensionElements>
   *   <zeebe:taskDefinition type="myTaskType" retries="3" />
   * </bpmn:extensionElements>
   *```
   * @param {Element} element
   * @param {Object} [oldTemplate]
   * @param {Object} [newTemplate]
   * @param {Object} options
   * @param {Array<string>} options.bindingTypes - binding types to consider
   * @param {string} options.extensionType - type of the extension element to update
   * @param {(binding: Object) => string} options.getPropertyName - function to get the property name for the binding
   */
  _updateSingleExtensionElement(element, oldTemplate, newTemplate, options) {
    const {
      bindingTypes,
      extensionType,
      getPropertyName
    } = options;
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    let newProperties = [];
    if (newTemplate) {
      newProperties = newTemplate.properties.filter(newProperty => {
        const newBinding = newProperty.binding,
          newBindingType = newBinding.type;
        return bindingTypes.includes(newBindingType);
      });
    }
    const businessObject = this._getOrCreateExtensionElements(element);
    let extensionElement = findExtension$1(businessObject, extensionType);

    // (1) Remove extension if no new properties

    if (!newProperties.length) {
      if (!extensionElement) {
        return;
      }
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: without(businessObject.get('values'), extensionElement)
        }
      });
      return;
    }

    // If there are new properties:
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty$1(oldTemplate, newProperty),
        newPropertyValue = getDefaultValue(newProperty),
        newBinding = newProperty.binding,
        propertyName = getPropertyName(newBinding);

      // (2) Update old extension with new property values
      if (extensionElement) {
        if (!shouldKeepValue(extensionElement, oldProperty, newProperty)) {
          const properties = {
            [propertyName]: newPropertyValue
          };
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: extensionElement,
            properties
          });
        }
      }

      // (3) Add new extension with properties if it does not exist
      else {
        const properties = {
          [propertyName]: newPropertyValue
        };
        extensionElement = bpmnFactory.create(extensionType, properties);
        extensionElement.$parent = businessObject;
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties: {
            values: [...businessObject.get('values'), extensionElement]
          }
        });
      }
    });

    // (4) Remove properties no longer templated
    const oldProperties = oldTemplate && oldTemplate.properties.filter(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type,
        oldPropertyName = getPropertyName(oldBinding);
      return bindingTypes.includes(oldBindingType) && !newProperties.find(newProperty => oldPropertyName === getPropertyName(newProperty.binding));
    }) || [];
    oldProperties.forEach(oldProperty => {
      const properties = {
        [getPropertyName(oldProperty.binding)]: undefined
      };
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElement,
        properties
      });
    });
  }
};
ChangeElementTemplateHandler$1.$inject = ['bpmnFactory', 'bpmnReplace', 'commandStack', 'modeling', 'moddleCopy', 'injector'];

// helpers //////////

/**
 * Find business object matching specified property.
 *
 * @param {djs.model.Base|ModdleElement} element
 * @param {Object} property
 *
 * @returns {ModdleElement}
 */
function findBusinessObject(element, property) {
  const businessObject = getBusinessObject(element);
  const binding = property.binding,
    bindingType = binding.type;
  if (TASK_DEFINITION_TYPES.includes(bindingType)) {
    return findExtension$1(businessObject, 'zeebe:TaskDefinition');
  }
  if (bindingType === 'zeebe:input' || bindingType === 'zeebe:output') {
    const extensionElements = findExtension$1(businessObject, 'zeebe:IoMapping');
    if (!extensionElements) {
      return;
    }
    if (bindingType === 'zeebe:input') {
      return find(extensionElements.get('zeebe:inputParameters'), function (input) {
        return input.get('zeebe:target') === binding.name;
      });
    } else {
      return find(extensionElements.get('zeebe:outputParameters'), function (output) {
        return output.get('zeebe:source') === binding.source;
      });
    }
  }
  if (bindingType === 'zeebe:taskHeader') {
    const extensionElements = findExtension$1(businessObject, 'zeebe:TaskHeaders');
    if (!extensionElements) {
      return;
    }
    return find(extensionElements.get('zeebe:values'), function (value) {
      return value.get('zeebe:key') === binding.key;
    });
  }
  if (bindingType === 'zeebe:property') {
    const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
    if (!zeebeProperties) {
      return;
    }
    return zeebeProperties.get('properties').find(value => {
      return value.get('name') === binding.name;
    });
  }
  if (bindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
    const linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
    if (!linkedResources) {
      return;
    }
    return linkedResources.get('values').find(value => {
      return value.get('linkName') === binding.linkName;
    });
  }
}

/**
 * Find old property matching specified new property.
 *
 * @param {Object} oldTemplate
 * @param {Object} newProperty
 *
 * @returns {Object}
 */
function findOldProperty$1(oldTemplate, newProperty) {
  if (!oldTemplate) {
    return;
  }
  const oldProperties = oldTemplate.properties,
    newBinding = newProperty.binding,
    newBindingName = newBinding.name,
    newBindingType = newBinding.type;
  if (newBindingType === 'property') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'property' && oldBindingName === newBindingName;
    });
  }
  if (TASK_DEFINITION_TYPES.includes(newBindingType)) {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldPropertyName = getTaskDefinitionPropertyName(oldBinding),
        newPropertyName = getTaskDefinitionPropertyName(newBinding);
      return oldPropertyName === newPropertyName;
    });
  }
  if (newBindingType === 'zeebe:input') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'zeebe:input') {
        return;
      }
      return oldBindingName === newBindingName;
    });
  }
  if (newBindingType === 'zeebe:output') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'zeebe:output') {
        return;
      }
      return oldBinding.source === newBinding.source;
    });
  }
  if (newBindingType === 'zeebe:taskHeader') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'zeebe:taskHeader') {
        return;
      }
      return oldBinding.key === newBinding.key;
    });
  }
  if (newBindingType === 'zeebe:property') {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'zeebe:property') {
        return;
      }
      return oldBinding.name === newBinding.name;
    });
  }
  if (newBindingType === MESSAGE_PROPERTY_TYPE) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== MESSAGE_PROPERTY_TYPE) {
        return;
      }
      return oldBinding.name === newBinding.name;
    });
  }
  if (newBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
        return;
      }
      return oldBinding.name === newBinding.name;
    });
  }
  if (newBindingType === SIGNAL_PROPERTY_TYPE) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== SIGNAL_PROPERTY_TYPE) {
        return;
      }
      return oldBinding.name === newBinding.name;
    });
  }
  if (newBindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_LINKED_RESOURCE_PROPERTY) {
        return;
      }
      return oldBinding.linkName === newBinding.linkName && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_CALLED_DECISION) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_CALLED_DECISION) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_SCRIPT_TASK) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_SCRIPT_TASK) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_FORM_DEFINITION) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_FORM_DEFINITION) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_ASSIGNMENT_DEFINITION) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_ASSIGNMENT_DEFINITION) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_PRIORITY_DEFINITION) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_PRIORITY_DEFINITION) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_AD_HOC) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_AD_HOC) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === ZEEBE_TASK_SCHEDULE) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== ZEEBE_TASK_SCHEDULE) {
        return;
      }
      return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
    });
  }
  if (newBindingType === TIMER_EVENT_DEFINITION_PROPERTY_TYPE) {
    return oldProperties.find(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== TIMER_EVENT_DEFINITION_PROPERTY_TYPE) {
        return;
      }
      return oldBinding.name === newBinding.name;
    });
  }
}

/**
 * Check whether the existing property should be kept. This is the case if
 *  - an old template was set and the value differs from the default
 *  - no template was set but the property was set manually
 *
 * @param {djs.model.Base|ModdleElement} element
 * @param {Object} oldProperty
 * @param {Object} newProperty
 *
 * @returns {boolean}
 */
function shouldKeepValue(element, oldProperty, newProperty) {
  // "Hidden" values are treated as a constant
  if (newProperty.type === 'Hidden') {
    return false;
  }

  // Dropdowns should keep existing configuration
  // cf. https://github.com/bpmn-io/bpmn-js-properties-panel/issues/767
  if (newProperty.type === 'Dropdown') {
    const currentValue = getPropertyValue$1(element, newProperty);

    // only keep value if old value is a valid option
    return newProperty.choices && newProperty.choices.some(choice => choice.value === currentValue);
  }

  // keep existing old property if
  // user changed it from the original
  if (oldProperty) {
    return propertyChanged$1(element, oldProperty);
  }

  // keep existing property value
  return !!getPropertyValue$1(element, newProperty);
}

/**
 * Check whether property was changed after being set by template.
 *
 * @param {djs.model.Base|ModdleElement} element
 * @param {Object} oldProperty
 *
 * @returns {boolean}
 */
function propertyChanged$1(element, oldProperty) {
  const oldPropertyValue = getDefaultFixedValue(oldProperty);
  return getPropertyValue$1(element, oldProperty) !== oldPropertyValue;
}
function getPropertyValue$1(element, property) {
  const businessObject = getBusinessObject(element);
  if (!businessObject) {
    return;
  }
  const binding = property.binding,
    bindingName = binding.name,
    bindingType = binding.type,
    bindingProperty = binding.property;
  if (bindingType === 'property') {
    return businessObject.get(bindingName);
  }
  if (TASK_DEFINITION_TYPES.includes(bindingType)) {
    return businessObject.get(getTaskDefinitionPropertyName(binding));
  }
  if (bindingType === 'zeebe:input') {
    return businessObject.get('zeebe:source');
  }
  if (bindingType === 'zeebe:output') {
    return businessObject.get('zeebe:target');
  }
  if (bindingType === 'zeebe:taskHeader') {
    return businessObject.get('zeebe:value');
  }
  if (bindingType === 'zeebe:property') {
    return businessObject.get('zeebe:value');
  }
  if (bindingType === MESSAGE_PROPERTY_TYPE) {
    return businessObject.get(bindingName);
  }
  if (bindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
    return businessObject.get(bindingName);
  }
  if (bindingType === SIGNAL_PROPERTY_TYPE) {
    return businessObject.get(bindingName);
  }
  if (bindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_CALLED_DECISION) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_CALLED_ELEMENT) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_FORM_DEFINITION) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_SCRIPT_TASK) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_ASSIGNMENT_DEFINITION) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_PRIORITY_DEFINITION) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_AD_HOC) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === ZEEBE_TASK_SCHEDULE) {
    return businessObject.get(bindingProperty);
  }
  if (bindingType === TIMER_EVENT_DEFINITION_PROPERTY_TYPE) {
    // the actual value is nested in an Expression
    return businessObject.get(bindingName)?.get('body');
  }
}
function remove$1(array, item) {
  const index = array.indexOf(item);
  if (index < 0) {
    return array;
  }
  array.splice(index, 1);
  return array;
}
function hasMessageProperties(template) {
  return template.properties.some(p => MESSAGE_BINDING_TYPES.includes(p.binding.type));
}
function hasSignalProperties(template) {
  return template.properties.some(p => p.binding.type === SIGNAL_PROPERTY_TYPE);
}
function shouldUpdateElementType(element, oldTemplate, newType) {
  // Never reuse existing eventDefinition when applying a new template
  if (!oldTemplate && newType.eventDefinition) {
    return true;
  }
  const oldType = oldTemplate && oldTemplate.elementType || {
    value: element.type,
    eventDefinition: getEventDefinitionType(element)
  };

  // Do not update if the element type did not change
  if (oldType && oldType.value === newType.value && oldType.eventDefinition === newType.eventDefinition) {
    return false;
  }
  return true;
}
function getEventDefinitionType(element) {
  const businessObject = getBusinessObject(element);
  if (!businessObject.eventDefinitions) {
    return;
  }
  const eventDefinition = businessObject.eventDefinitions[0];
  if (!eventDefinition) {
    return;
  }
  return eventDefinition.$type;
}

let RemoveElementTemplateHandler$1 = class RemoveElementTemplateHandler {
  constructor(commandStack) {
    this._commandStack = commandStack;
  }
  preExecute(context) {
    const {
      element,
      oldTemplate
    } = context;
    this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', {
      element,
      oldTemplate,
      newTemplate: null,
      removeProperties: true
    });
  }
};
RemoveElementTemplateHandler$1.$inject = ['commandStack'];

let UnlinkElementTemplateHandler$1 = class UnlinkElementTemplateHandler {
  constructor(commandStack) {
    this._commandStack = commandStack;
  }
  preExecute(context) {
    const {
      element,
      oldTemplate
    } = context;
    this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', {
      element,
      oldTemplate,
      newTemplate: null,
      removeProperties: false
    });
  }
};
UnlinkElementTemplateHandler$1.$inject = ['commandStack'];

/**
 * A handler that combines and executes multiple commands.
 *
 * All updates are bundled on the command stack and executed in one step.
 * This also makes it possible to revert the changes in one step.
 *
 * Example use case: remove the camunda:formKey attribute and in addition
 * add all form fields needed for the camunda:formData property.
 */
class MultiCommandHandler {
  constructor(commandStack) {
    this._commandStack = commandStack;
  }
  preExecute(context) {
    const commandStack = this._commandStack;
    forEach(context, function (command) {
      commandStack.execute(command.cmd, command.context);
    });
  }
}
MultiCommandHandler.$inject = ['commandStack'];

let ElementTemplatesCommands$1 = class ElementTemplatesCommands {
  constructor(commandStack, elementTemplates, eventBus) {
    commandStack.registerHandler('element-templates.multi-command-executor', MultiCommandHandler);
    commandStack.registerHandler('propertiesPanel.zeebe.changeTemplate', ChangeElementTemplateHandler$1);
    commandStack.registerHandler('propertiesPanel.removeTemplate', RemoveElementTemplateHandler$1);
    commandStack.registerHandler('propertiesPanel.unlinkTemplate', UnlinkElementTemplateHandler$1);

    // apply default element templates on shape creation
    eventBus.on(['commandStack.shape.create.postExecuted'], function (event) {
      const {
        context: {
          hints = {},
          shape
        }
      } = event;
      if (hints.createElementsBehavior !== false) {
        applyDefaultTemplate$1(shape, elementTemplates, commandStack);
      }
    });

    // apply default element templates on connection creation
    eventBus.on(['commandStack.connection.create.postExecuted'], function (event) {
      const {
        context: {
          hints = {},
          connection
        }
      } = event;
      if (hints.createElementsBehavior !== false) {
        applyDefaultTemplate$1(connection, elementTemplates, commandStack);
      }
    });
  }
};
ElementTemplatesCommands$1.$inject = ['commandStack', 'elementTemplates', 'eventBus'];
function applyDefaultTemplate$1(element, elementTemplates, commandStack) {
  if (!elementTemplates.get(element) && elementTemplates.getDefault(element)) {
    const command = 'propertiesPanel.zeebe.changeTemplate';
    const commandContext = {
      element: element,
      newTemplate: elementTemplates.getDefault(element)
    };
    commandStack.execute(command, commandContext);
  }
}

var commandsModule$1 = {
  __init__: ['elementTemplateCommands'],
  elementTemplateCommands: ['type', ElementTemplatesCommands$1]
};

class PropertyBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory,
      property
    } = options;
    const {
      binding
    } = property;
    const {
      name
    } = binding;
    let value = getDefaultValue(property);
    const businessObject = getBusinessObject(element);
    if (isExpression(businessObject, name)) {
      const expression = createExpression(value, businessObject, bpmnFactory);
      value = expression;
    }
    businessObject[name] = value;
  }
}

class TaskDefinitionTypeBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const value = getDefaultValue(property);
    const propertyName = getTaskDefinitionPropertyName(property.binding);
    const taskDefinition = ensureExtension(element, 'zeebe:TaskDefinition', bpmnFactory);
    taskDefinition.set(propertyName, value);
  }
}

class InputBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const value = getDefaultValue(property);
    const ioMapping = ensureExtension(element, 'zeebe:IoMapping', bpmnFactory);
    if (!shouldUpdate(value, property)) {
      return;
    }
    const input = createInputParameter$1(binding, value, bpmnFactory);
    input.$parent = ioMapping;
    ioMapping.get('inputParameters').push(input);
  }
}

class OutputBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const value = getDefaultValue(property);
    const ioMapping = ensureExtension(element, 'zeebe:IoMapping', bpmnFactory);
    if (!shouldUpdate(value, property)) {
      return;
    }
    const output = createOutputParameter$1(binding, value, bpmnFactory);
    output.$parent = ioMapping;
    ioMapping.get('outputParameters').push(output);
  }
}

class TaskHeaderBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const value = getDefaultValue(property);
    const taskHeaders = ensureExtension(element, 'zeebe:TaskHeaders', bpmnFactory);
    const header = createTaskHeader(binding, value, bpmnFactory);
    header.$parent = taskHeaders;
    taskHeaders.get('values').push(header);
  }
}

/**
 * A provider that sets up the `zeebe:property` binding
 * on a newly created moddle element.
 */
class ZeebePropertiesProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const value = getDefaultValue(property);
    const zeebeProperties = ensureExtension(element, 'zeebe:Properties', bpmnFactory);
    if (!shouldUpdate(value, property)) {
      return;
    }
    const zeebeProperty = createZeebeProperty(binding, value, bpmnFactory);
    zeebeProperty.$parent = zeebeProperties;
    zeebeProperties.get('properties').push(zeebeProperty);
  }
}

class MessagePropertyBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory,
      property
    } = options;
    const {
      binding
    } = property;
    const {
      name
    } = binding;
    const value = getDefaultValue(property);
    let businessObject = getBusinessObject(element);
    if (is(businessObject, 'bpmn:Event')) {
      businessObject = businessObject.get('eventDefinitions')[0];
    }
    let message = businessObject.get('messageRef');
    if (!message) {
      message = bpmnFactory.create('bpmn:Message', {
        'zeebe:modelerTemplate': getTemplateId$1(element)
      });
      businessObject.set('messageRef', message);
    }
    message.set(name, value);
  }
}

class MessageZeebeSubscriptionBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory,
      property
    } = options;
    const {
      binding
    } = property;
    const {
      name
    } = binding;
    const value = getDefaultValue(property);
    let businessObject = getBusinessObject(element);
    if (is(businessObject, 'bpmn:Event')) {
      businessObject = businessObject.get('eventDefinitions')[0];
    }
    let message = businessObject.get('messageRef');
    if (!message) {
      message = bpmnFactory.create('bpmn:Message', {
        'zeebe:modelerTemplate': getTemplateId$1(element)
      });
      businessObject.set('messageRef', message);
    }
    const subscription = ensureExtension(message, 'zeebe:Subscription', bpmnFactory);
    if (!shouldUpdate(value, property)) {
      return;
    }
    subscription.set(name, value);
  }
}

class SignalPropertyBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory,
      property
    } = options;
    const {
      binding
    } = property;
    const {
      name
    } = binding;
    const value = getDefaultValue(property);
    let businessObject = getBusinessObject(element);
    if (is(businessObject, 'bpmn:Event')) {
      businessObject = businessObject.get('eventDefinitions')[0];
    }
    let signal = businessObject.get('signalRef');
    if (!signal) {
      signal = bpmnFactory.create('bpmn:Signal', {
        'zeebe:modelerTemplate': getTemplateId$1(element)
      });
      businessObject.set('signalRef', signal);
    }
    signal.set(name, value);
  }
}

class TimerPropertyBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory,
      property
    } = options;
    const {
      binding
    } = property;
    const {
      name
    } = binding;
    const value = getDefaultValue(property);
    const businessObject = getBusinessObject(element);
    let timerEventDefinition = findTimerEventDefinition(businessObject);
    if (!timerEventDefinition) {
      timerEventDefinition = createElement('bpmn:TimerEventDefinition', {}, businessObject, bpmnFactory);
      businessObject.set('eventDefinitions', [timerEventDefinition]);
    }
    const expression = createElement('bpmn:FormalExpression', {
      body: value
    }, timerEventDefinition, bpmnFactory);
    timerEventDefinition.set(name, expression);
    if (name === 'timeCycle' && is(businessObject, 'bpmn:BoundaryEvent')) {
      businessObject.set('cancelActivity', false);
    }
  }
}

class CalledElementBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const calledElement = ensureExtension(element, 'zeebe:CalledElement', bpmnFactory);

    // TODO(@barmac): remove if we decide to support propagation in templates
    ensureNoPropagation(calledElement);
    calledElement.set(propertyName, value);
  }
}
function ensureNoPropagation(calledElement) {
  calledElement.set('propagateAllChildVariables', false);
  calledElement.set('propagateAllParentVariables', false);
}

class LinkedResourcePropertyBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding: {
        property: bindingProperty,
        linkName
      }
    } = property;
    const value = getDefaultValue(property);
    const bo = getBusinessObject(element);
    const linkedResources = ensureExtension(element, 'zeebe:LinkedResources', bpmnFactory);
    let linkedResource = linkedResources.get('values').find(linkedResource => linkedResource.get('linkName') === linkName);
    if (!linkedResource) {
      linkedResource = createElement('zeebe:LinkedResource', {
        linkName
      }, bo, bpmnFactory);
      linkedResources.get('values').push(linkedResource);
    }
    linkedResource.set(bindingProperty, value);
  }
}

class ZeebeUserTaskBindingProvider {
  static create(element, options) {
    const {
      bpmnFactory
    } = options;
    ensureExtension(element, 'zeebe:UserTask', bpmnFactory);
  }
}

class CalledDecisionBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const calledDecision = ensureExtension(element, 'zeebe:CalledDecision', bpmnFactory);
    calledDecision.set(propertyName, value);
  }
}

class ScriptTaskBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const scriptTask = ensureExtension(element, 'zeebe:Script', bpmnFactory);
    scriptTask.set(propertyName, value);
  }
}

class ZeebeFormDefinitionBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const formDefinition = ensureExtension(element, 'zeebe:FormDefinition', bpmnFactory);
    formDefinition.set(propertyName, value);
  }
}

class AssignmentDefinitionBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const assignmentDefinition = ensureExtension(element, 'zeebe:AssignmentDefinition', bpmnFactory);
    assignmentDefinition.set(propertyName, value);
  }
}

class PriorityDefinitionBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const priorityDefinition = ensureExtension(element, 'zeebe:PriorityDefinition', bpmnFactory);
    priorityDefinition.set(propertyName, value);
  }
}

/**
 * Provider for the `zeebe:adHoc` binding. Ensures a single zeebe:AdHoc extension
 * element exists and sets the configured property on it.
 */
class AdHocBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const adHoc = ensureExtension(element, 'zeebe:AdHoc', bpmnFactory);
    adHoc.set(propertyName, value);
  }
}

class TaskScheduleBindingProvider {
  static create(element, options) {
    const {
      property,
      bpmnFactory
    } = options;
    const {
      binding
    } = property;
    const {
      property: propertyName
    } = binding;
    const value = getDefaultValue(property);
    const taskSchedule = ensureExtension(element, 'zeebe:TaskSchedule', bpmnFactory);
    taskSchedule.set(propertyName, value);
  }
}

const EXPRESSION_TYPES = ['bpmn:Expression', 'bpmn:FormalExpression'];
function getPropertyValue(element, property) {
  const rawValue = getRawPropertyValue(element, property);
  const {
    type
  } = property;
  if (type === 'Boolean') {
    return getBooleanPropertyValue(rawValue);
  }
  return rawValue;
}
function getRawPropertyValue(element, property) {
  let businessObject = getBusinessObject(element);
  const defaultValue = '';
  const {
    binding
  } = property;
  const {
    name,
    property: bindingProperty,
    type,
    linkName
  } = binding;

  // property
  if (type === 'property') {
    const value = isExpression(element, name) ? getExpressionValue(element, name) : businessObject.get(name);
    if (!isUndefined$1(value)) {
      return value;
    }
    return defaultValue;
  }

  // zeebe:taskDefinition
  if (TASK_DEFINITION_TYPES.includes(type)) {
    const taskDefinition = findExtension$1(businessObject, 'zeebe:TaskDefinition');
    if (taskDefinition) {
      if (type === ZEEBE_TASK_DEFINITION_TYPE_TYPE) {
        return taskDefinition.get('type');
      } else if (type === ZEEBE_TASK_DEFINITION) {
        return taskDefinition.get(bindingProperty);
      }
    }
    return defaultValue;
  }
  if (IO_BINDING_TYPES$1.includes(type)) {
    const ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');
    if (!ioMapping) {
      return defaultValue;
    }

    // zeebe:Input
    if (type === ZEBBE_INPUT_TYPE) {
      const inputParameter = findInputParameter$1(ioMapping, binding);
      if (inputParameter) {
        return inputParameter.get('source');
      }
      return defaultValue;
    }

    // zeebe:Output
    if (type === ZEEBE_OUTPUT_TYPE) {
      const outputParameter = findOutputParameter$1(ioMapping, binding);
      if (outputParameter) {
        return outputParameter.get('target');
      }
      return defaultValue;
    }
  }

  // zeebe:taskHeaders
  if (type === ZEEBE_TASK_HEADER_TYPE) {
    const taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');
    if (!taskHeaders) {
      return defaultValue;
    }
    const header = findTaskHeader(taskHeaders, binding);
    if (header) {
      return header.get('value');
    }
    return defaultValue;
  }

  // zeebe:Property
  if (type === ZEEBE_PROPERTY_TYPE) {
    const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
    if (zeebeProperties) {
      const zeebeProperty = findZeebeProperty(zeebeProperties, binding);
      if (zeebeProperty) {
        return zeebeProperty.get('value');
      }
    }
    return defaultValue;
  }

  // bpmn:Message#property
  if (type === MESSAGE_PROPERTY_TYPE) {
    const message = findMessage(businessObject);
    const value = message ? message.get(name) : undefined;
    if (!isUndefined$1(value)) {
      return value;
    }
    return defaultValue;
  }

  // bpmn:Message#zeebe:subscription#property
  if (type === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
    const message = findMessage(businessObject);
    if (message) {
      const subscription = findExtension$1(message, 'zeebe:Subscription');
      const value = subscription ? subscription.get(name) : undefined;
      if (!isUndefined$1(value)) {
        return subscription.get(name);
      }
    }
    return defaultValue;
  }

  // bpmn:Signal#property
  if (type === SIGNAL_PROPERTY_TYPE) {
    const signal = findSignal(businessObject);
    const value = signal ? signal.get(name) : undefined;
    if (!isUndefined$1(value)) {
      return value;
    }
    return defaultValue;
  }

  // bpmn:TimerEventDefinition#property
  if (type === TIMER_EVENT_DEFINITION_PROPERTY_TYPE) {
    const timerEventDefinition = findTimerEventDefinition(businessObject);
    if (!timerEventDefinition) {
      return defaultValue;
    }
    const expression = timerEventDefinition.get(name);
    if (expression) {
      return expression.get('body');
    }
    return defaultValue;
  }

  // zeebe:calledElement
  if (type === ZEEBE_CALLED_ELEMENT) {
    const calledElement = findExtension$1(businessObject, 'zeebe:CalledElement');
    return calledElement ? calledElement.get(bindingProperty) : defaultValue;
  }
  if (type === ZEEBE_LINKED_RESOURCE_PROPERTY) {
    const linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
    if (!linkedResources) {
      return defaultValue;
    }
    const linkedResource = linkedResources.get('values').find(value => value.get('linkName') === linkName);
    return linkedResource ? linkedResource.get(bindingProperty) : defaultValue;
  }

  // zeebe:userTask
  if (type === ZEEBE_USER_TASK) {
    const userTask = findExtension$1(businessObject, 'zeebe:userTask');
    return userTask ? userTask.get(bindingProperty) : defaultValue;
  }

  // zeebe:calledDecision
  if (type === ZEEBE_CALLED_DECISION) {
    const calledDecision = findExtension$1(businessObject, 'zeebe:CalledDecision');
    return calledDecision ? calledDecision.get(bindingProperty) : defaultValue;
  }

  // zeebe:formDefinition
  if (type === ZEEBE_FORM_DEFINITION) {
    const formDefinition = findExtension$1(businessObject, 'zeebe:FormDefinition');
    return formDefinition ? formDefinition.get(bindingProperty) : defaultValue;
  }

  // zeebe:script
  if (type === ZEEBE_SCRIPT_TASK) {
    const scriptTask = findExtension$1(businessObject, 'zeebe:Script');
    return scriptTask ? scriptTask.get(bindingProperty) : defaultValue;
  }
  if (type === ZEEBE_ASSIGNMENT_DEFINITION) {
    const assignmentDefinition = findExtension$1(businessObject, 'zeebe:AssignmentDefinition');
    return assignmentDefinition ? assignmentDefinition.get(bindingProperty) : defaultValue;
  }
  if (type === ZEEBE_TASK_SCHEDULE) {
    const taskSchedule = findExtension$1(businessObject, 'zeebe:TaskSchedule');
    return taskSchedule ? taskSchedule.get(bindingProperty) : defaultValue;
  }
  if (type === ZEEBE_PRIORITY_DEFINITION) {
    const priorityDefinition = findExtension$1(businessObject, 'zeebe:PriorityDefinition');
    return priorityDefinition ? priorityDefinition.get(bindingProperty) : defaultValue;
  }
  if (type === ZEEBE_AD_HOC) {
    const adHoc = findExtension$1(businessObject, 'zeebe:AdHoc');
    return adHoc ? adHoc.get(bindingProperty) : defaultValue;
  }

  // should never throw as templates are validated beforehand
  throw unknownBindingError$1(element, property);
}

/**
 * Cast a string value to a boolean if possible. Otherwise return the value.
 * Cannot always cast due to FEEL expressions.
 *
 * @param {string|boolean} value
 */
function getBooleanPropertyValue(value) {
  switch (value) {
    case 'true':
      return true;
    case 'false':
      return false;
  }
  return value;
}
const NO_OP = null;
function setPropertyValue(bpmnFactory, commandStack, element, property, value) {
  let businessObject = getBusinessObject(element);
  const {
    binding
  } = property;
  const {
    name,
    type,
    property: bindingProperty,
    linkName
  } = binding;
  let extensionElements;
  let propertyValue;
  const commands = [];
  const context = {
    element,
    property
  };

  // ensure message exists
  if (MESSAGE_BINDING_TYPES.includes(type)) {
    if (is(businessObject, 'bpmn:Event')) {
      businessObject = businessObject.get('eventDefinitions')[0];
    }
    let message = findMessage(businessObject);
    if (!message) {
      message = bpmnFactory.create('bpmn:Message', {
        'zeebe:modelerTemplate': getTemplateId$1(element)
      });
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: businessObject,
          properties: {
            messageRef: message
          }
        }
      });
    }
    businessObject = message;
  }

  // ensure signal exists
  if (type === SIGNAL_PROPERTY_TYPE) {
    if (is(businessObject, 'bpmn:Event')) {
      businessObject = businessObject.get('eventDefinitions')[0];
    }
    let signal = findSignal(businessObject);
    if (!signal) {
      signal = bpmnFactory.create('bpmn:Signal', {
        'zeebe:modelerTemplate': getTemplateId$1(element)
      });
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: businessObject,
          properties: {
            signalRef: signal
          }
        }
      });
    }
    businessObject = signal;
  }

  // handle timer event definition property
  if (type === TIMER_EVENT_DEFINITION_PROPERTY_TYPE) {
    const timerEventDefinition = findTimerEventDefinition(businessObject);
    if (!timerEventDefinition) {
      throw new Error('cannot set timer property on element without TimerEventDefinition');
    }
    let expression = timerEventDefinition.get(name);
    if (!expression) {
      expression = bpmnFactory.create('bpmn:FormalExpression', {
        body: value || ''
      });
      expression.$parent = timerEventDefinition;
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: timerEventDefinition,
          properties: {
            [name]: expression
          }
        }
      });
    } else {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: expression,
          properties: {
            body: value || ''
          }
        }
      });
    }
  }

  // ensure extension elements
  if (EXTENSION_BINDING_TYPES$1.includes(type)) {
    extensionElements = businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = createElement('bpmn:ExtensionElements', null, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: businessObject,
          properties: {
            extensionElements
          }
        }
      });
    } else {
      commands.push(NO_OP);
    }
  }

  // property
  if (PROPERTY_BINDING_TYPES.includes(type)) {
    const propertyType = businessObject.$descriptor.propertiesByName[name]?.type;
    let propertyName = name;
    if (!propertyType || propertyType === 'String') {
      // make sure we create and don't remove the property
      propertyValue = value || '';
    } else if (propertyType === 'Boolean') {
      propertyValue = !!value;
    } else if (propertyType === 'Integer') {
      propertyValue = parseInt(value, 10);
      if (isNaN(propertyValue)) {
        // do not set NaN value
        propertyValue = undefined;
      }
    } else if (EXPRESSION_TYPES.includes(propertyType)) {
      const existingExpression = businessObject.get(name);
      if (existingExpression && is(existingExpression, 'bpmn:FormalExpression')) {
        // re-use existing expression
        businessObject = existingExpression;
        propertyName = 'body';
        propertyValue = value || '';
      } else {
        propertyValue = createExpression(value, businessObject, bpmnFactory);
      }
    } else {
      // unsupported non-primitive types cannot be set
      throw new Error(`cannot set property of type <${propertyType}>`);
    }
    if (!isUndefined$1(propertyValue)) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: businessObject,
          properties: {
            [propertyName]: propertyValue
          }
        }
      });
    } else {
      commands.push(NO_OP);
    }
  }

  // zeebe:taskDefinition
  if (TASK_DEFINITION_TYPES.includes(type)) {
    const oldTaskDefinition = findExtension$1(extensionElements, 'zeebe:TaskDefinition'),
      propertyName = getTaskDefinitionPropertyName(binding),
      properties = {
        [propertyName]: value || ''
      };
    if (oldTaskDefinition) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          properties,
          moddleElement: oldTaskDefinition
        }
      });
    } else {
      const newTaskDefinition = createTaskDefinition(properties, bpmnFactory);
      newTaskDefinition.$parent = businessObject;
      const values = extensionElements.get('values');
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...values, newTaskDefinition]
          }
        }
      });
    }
  }
  if (IO_BINDING_TYPES$1.includes(type)) {
    let ioMapping = findExtension$1(extensionElements, 'zeebe:IoMapping');
    if (!ioMapping) {
      ioMapping = createElement('zeebe:IoMapping', null, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), ioMapping]
          }
        }
      });
    }

    // zeebe:Input
    if (type === ZEBBE_INPUT_TYPE) {
      const oldZeebeInputParameter = findInputParameter$1(ioMapping, binding);
      const values = ioMapping.get('inputParameters').filter(value => value !== oldZeebeInputParameter);

      // do not persist empty parameters when configured as <optional>
      if (shouldUpdate(value, property)) {
        const newZeebeInputParameter = createInputParameter$1(binding, value, bpmnFactory);
        values.push(newZeebeInputParameter);
      }
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: ioMapping,
          properties: {
            inputParameters: [...values]
          }
        }
      });
    }

    // zeebe:Output
    if (type === ZEEBE_OUTPUT_TYPE) {
      const oldZeebeOutputParameter = findOutputParameter$1(ioMapping, binding);
      const values = ioMapping.get('outputParameters').filter(value => value !== oldZeebeOutputParameter);

      // do not persist empty parameters when configured as <optional>
      if (shouldUpdate(value, property)) {
        const newZeebeOutputParameter = createOutputParameter$1(binding, value, bpmnFactory);
        values.push(newZeebeOutputParameter);
      }
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: ioMapping,
          properties: {
            'outputParameters': [...values]
          }
        }
      });
    }
  }

  // zeebe:taskHeaders
  if (type === ZEEBE_TASK_HEADER_TYPE) {
    let taskHeaders = findExtension$1(extensionElements, 'zeebe:TaskHeaders');
    if (!taskHeaders) {
      taskHeaders = createElement('zeebe:TaskHeaders', null, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), taskHeaders]
          }
        }
      });
    }
    const oldTaskHeader = findTaskHeader(taskHeaders, binding);
    const values = taskHeaders.get('values').filter(value => value !== oldTaskHeader);

    // do not persist task headers with empty value
    if (!value) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: taskHeaders,
          properties: {
            values
          }
        }
      });
    } else {
      const newTaskHeader = createTaskHeader(binding, value, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: taskHeaders,
          properties: {
            values: [...values, newTaskHeader]
          }
        }
      });
    }
  }

  // zeebe:Property
  if (type === ZEEBE_PROPERTY_TYPE) {
    let zeebeProperties = findExtension$1(extensionElements, 'zeebe:Properties');
    if (!zeebeProperties) {
      zeebeProperties = createElement('zeebe:Properties', null, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), zeebeProperties]
          }
        }
      });
    }
    const oldZeebeProperty = findZeebeProperty(zeebeProperties, binding);
    const properties = zeebeProperties.get('properties').filter(property => property !== oldZeebeProperty);
    if (shouldUpdate(value, property)) {
      const newZeebeProperty = createZeebeProperty(binding, value, bpmnFactory);
      properties.push(newZeebeProperty);
    }
    commands.push({
      cmd: 'element.updateModdleProperties',
      context: {
        element,
        moddleElement: zeebeProperties,
        properties: {
          properties
        }
      }
    });
  }

  // bpmn:Message#zeebe:subscription#property
  if (type === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
    let subscription = findExtension$1(extensionElements, 'zeebe:Subscription');
    const properties = {
      [name]: value || ''
    };
    if (subscription) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: subscription
        }
      });
    } else {
      subscription = createElement('zeebe:Subscription', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), subscription]
          }
        }
      });
    }
  }

  // zeebe:calledElement
  if (type === ZEEBE_CALLED_ELEMENT) {
    let calledElement = findExtension$1(element, 'zeebe:CalledElement');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (calledElement) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: calledElement
        }
      });
    } else {
      calledElement = createElement('zeebe:CalledElement', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), calledElement]
          }
        }
      });
    }
  }

  // zeebe:linkedResource
  if (type === ZEEBE_LINKED_RESOURCE_PROPERTY) {
    let linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
    if (!linkedResources) {
      linkedResources = createElement('zeebe:LinkedResources', null, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), linkedResources]
          }
        }
      });
    }
    let linkedResource = linkedResources.get('values').find(value => value.get('linkName') === linkName);
    if (!linkedResource) {
      linkedResource = createElement('zeebe:LinkedResource', {
        linkName
      }, businessObject, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: linkedResources,
          properties: {
            values: [...linkedResources.get('values'), linkedResource]
          }
        }
      });
    }
    commands.push({
      cmd: 'element.updateModdleProperties',
      context: {
        ...context,
        moddleElement: linkedResource,
        properties: {
          [bindingProperty]: value
        }
      }
    });
  }

  // zeebe:calledDecision
  if (type === ZEEBE_CALLED_DECISION) {
    let calledDecision = findExtension$1(element, 'zeebe:CalledDecision');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (calledDecision) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: calledDecision
        }
      });
    } else {
      calledDecision = createElement('zeebe:CalledDecision', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), calledDecision]
          }
        }
      });
    }
  }

  // zeebe:script
  if (type === ZEEBE_SCRIPT_TASK) {
    let scriptTask = findExtension$1(element, 'zeebe:Script');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (scriptTask) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: scriptTask
        }
      });
    } else {
      scriptTask = createElement('zeebe:Script', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), scriptTask]
          }
        }
      });
    }
  }

  // zeebe:formDefinition
  if (type === ZEEBE_FORM_DEFINITION) {
    let formDefinition = findExtension$1(element, 'zeebe:FormDefinition');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (formDefinition) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: formDefinition
        }
      });
    } else {
      formDefinition = createElement('zeebe:FormDefinition', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), formDefinition]
          }
        }
      });
    }
  }

  // zeebe:assignmentDefinition
  if (type === ZEEBE_ASSIGNMENT_DEFINITION) {
    let assignmentDefinition = findExtension$1(element, 'zeebe:AssignmentDefinition');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (assignmentDefinition) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: assignmentDefinition
        }
      });
    } else {
      assignmentDefinition = createElement('zeebe:AssignmentDefinition', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), assignmentDefinition]
          }
        }
      });
    }
  }

  // zeebe:taskSchedule
  if (type === ZEEBE_TASK_SCHEDULE) {
    let taskSchedule = findExtension$1(element, 'zeebe:TaskSchedule');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (taskSchedule) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: taskSchedule
        }
      });
    } else {
      taskSchedule = createElement('zeebe:TaskSchedule', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), taskSchedule]
          }
        }
      });
    }
  }

  // zeebe:priorityDefinition
  if (type === ZEEBE_PRIORITY_DEFINITION) {
    let priorityDefinition = findExtension$1(element, 'zeebe:PriorityDefinition');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (priorityDefinition) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: priorityDefinition
        }
      });
    } else {
      priorityDefinition = createElement('zeebe:PriorityDefinition', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), priorityDefinition]
          }
        }
      });
    }
  }

  // zeebe:adHoc
  if (type === ZEEBE_AD_HOC) {
    let adHoc = findExtension$1(element, 'zeebe:AdHoc');
    const propertyName = binding.property;
    const properties = {
      [propertyName]: value || ''
    };
    if (adHoc) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          properties,
          moddleElement: adHoc
        }
      });
    } else {
      adHoc = createElement('zeebe:AdHoc', properties, extensionElements, bpmnFactory);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), adHoc]
          }
        }
      });
    }
  }
  if (commands.length) {
    const commandsToExecute = commands.filter(command => command !== NO_OP);
    commandsToExecute.length && commandStack.execute('element-templates.multi-command-executor', commandsToExecute);
    return;
  }

  // should never throw as templates are validated beforehand
  throw unknownBindingError$1(element, property);
}

// TODO(@barmac): fix translate usage (https://github.com/bpmn-io/bpmn-js-element-templates/pull/53#issuecomment-1906203270)
function validateProperty(value, property, translate = defaultTranslate) {
  const {
    constraints = {},
    label
  } = property;
  const {
    maxLength,
    minLength,
    notEmpty
  } = constraints;
  if (notEmpty && isEmpty$1(value)) {
    return `${label} ${translate('must not be empty.')}`;
  }
  if (property.feel && isFeel$1(value)) {
    return;
  }
  if (maxLength && (value || '').length > maxLength) {
    return `${label} ${translate('must have max length {maxLength}.', {
      maxLength
    })}`;
  }
  if (minLength && (value || '').length < minLength) {
    return `${label} ${translate('must have min length {minLength}.', {
      minLength
    })}`;
  }
  let {
    pattern
  } = constraints;
  if (pattern) {
    let message;
    if (!isString(pattern)) {
      message = pattern.message;
      pattern = pattern.value;
    }
    if (!matchesPattern$1(value, pattern)) {
      if (message) {
        return `${label} ${translate(message)}`;
      }
      return `${label} ${translate('must match pattern {pattern}.', {
        pattern
      })}`;
    }
  }
}

// helpers
function unknownBindingError$1(element, property) {
  const businessObject = getBusinessObject(element);
  const id = businessObject.get('id');
  const {
    binding
  } = property;
  const {
    type
  } = binding;
  return new Error(`unknown binding <${type}> for element <${id}>, this should never happen`);
}
function isEmpty$1(value) {
  if (typeof value === 'string') {
    return !value.trim().length;
  }
  return value === undefined;
}
function matchesPattern$1(string, pattern) {
  return new RegExp(pattern).test(string);
}
function defaultTranslate(template, replacements) {
  replacements = replacements || {};
  return template.replace(/{([^}]+)}/g, function (_, key) {
    return replacements[key] || '{' + key + '}';
  });
}
function isFeel$1(value) {
  return isString(value) && value.trim().startsWith('=');
}

/**
 * Based on conditions, remove properties from the template.
 */
function applyConditions(element, elementTemplate) {
  const {
    properties
  } = elementTemplate;
  const filteredProperties = properties.filter(property => {
    return isPropertyAllowed(element, property) && isConditionMet(element, properties, property);
  });
  return {
    ...elementTemplate,
    properties: filteredProperties
  };
}
function isConditionMet(element, properties, property) {
  const {
    condition
  } = property;

  // If no condition is defined, return true.
  if (!condition) {
    return true;
  }

  // multiple ("and") conditions
  if (condition.allMatch) {
    const conditions = condition.allMatch;
    return conditions.every(condition => isSimpleConditionMet(element, properties, condition));
  }

  // single condition
  return isSimpleConditionMet(element, properties, condition);
}
function isSimpleConditionMet(element, properties, condition) {
  const {
    property,
    equals,
    oneOf,
    isActive
  } = condition;
  if (typeof isActive !== 'undefined') {
    const relatedCondition = properties.find(p => p.id === property);
    if (!relatedCondition) {
      return !isActive;
    }
    return isActive ? isConditionMet(element, properties, relatedCondition) : !isConditionMet(element, properties, relatedCondition);
  }
  const propertyObject = getProperty(properties, property);
  if (propertyObject) {
    // if the property is a FEEL [optional, static, required] property,
    // we need to get the value removing the '='

    const propertyValue = shouldCastToFeel(propertyObject) || propertyObject.feel === 'required' ? getPropertyValue(element, propertyObject).slice(1) : getPropertyValue(element, propertyObject);
    if (hasProperty(condition, 'equals')) {
      return compareProperty(propertyObject, propertyValue, equals);
    }
    if (oneOf) {
      return oneOf.includes(propertyValue);
    }
  }
  return false;
}
function getProperty(properties, propertyId) {
  const property = properties.find(p => p.id === propertyId);
  if (!property) {
    return;
  }
  return property;
}
function isPropertyAllowed(element, property) {
  const {
    binding
  } = property;
  const {
    type
  } = binding;
  if (type === 'bpmn:Message#zeebe:subscription#property' && binding.name === 'correlationKey' && is(element, 'bpmn:StartEvent') && !isEventSubProcess(element.parent)) {
    return false;
  }
  return true;
}
function compareProperty(propertyObj, propertyValue, value) {
  // Number
  if (propertyObj.type === 'Number') {
    return Number(propertyValue) === value;
  }

  // Boolean
  else if (propertyObj.type === 'Boolean') {
    return Boolean(propertyValue) === value;
  }
  return propertyValue === value;
}

// helpers //////////////////////

function hasProperty(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

class TemplateElementFactory {
  constructor(bpmnFactory, elementFactory) {
    this._bpmnFactory = bpmnFactory;
    this._elementFactory = elementFactory;
    this._providers = {
      [PROPERTY_TYPE$1]: PropertyBindingProvider,
      [ZEEBE_TASK_DEFINITION_TYPE_TYPE]: TaskDefinitionTypeBindingProvider,
      [ZEEBE_TASK_DEFINITION]: TaskDefinitionTypeBindingProvider,
      [ZEBBE_PROPERTY_TYPE]: ZeebePropertiesProvider,
      [ZEBBE_INPUT_TYPE]: InputBindingProvider,
      [ZEEBE_OUTPUT_TYPE]: OutputBindingProvider,
      [ZEEBE_TASK_HEADER_TYPE]: TaskHeaderBindingProvider,
      [MESSAGE_PROPERTY_TYPE]: MessagePropertyBindingProvider,
      [MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE]: MessageZeebeSubscriptionBindingProvider,
      [SIGNAL_PROPERTY_TYPE]: SignalPropertyBindingProvider,
      [TIMER_EVENT_DEFINITION_PROPERTY_TYPE]: TimerPropertyBindingProvider,
      [ZEEBE_CALLED_ELEMENT]: CalledElementBindingProvider,
      [ZEEBE_LINKED_RESOURCE_PROPERTY]: LinkedResourcePropertyBindingProvider,
      [ZEEBE_USER_TASK]: ZeebeUserTaskBindingProvider,
      [ZEEBE_CALLED_DECISION]: CalledDecisionBindingProvider,
      [ZEEBE_FORM_DEFINITION]: ZeebeFormDefinitionBindingProvider,
      [ZEEBE_SCRIPT_TASK]: ScriptTaskBindingProvider,
      [ZEEBE_ASSIGNMENT_DEFINITION]: AssignmentDefinitionBindingProvider,
      [ZEEBE_PRIORITY_DEFINITION]: PriorityDefinitionBindingProvider,
      [ZEEBE_AD_HOC]: AdHocBindingProvider,
      [ZEEBE_TASK_SCHEDULE]: TaskScheduleBindingProvider
    };
  }

  /**
   * Create an element based on an element template.
   *
   * @param {ElementTemplate} template
   * @returns {djs.model.Base}
   */
  create(template) {
    const {
      properties
    } = template;

    // (1) base shape
    const element = this._createShape(template);

    // (2) apply template
    this._setModelerTemplate(element, template);

    // (3) apply icon
    if (hasIcon(template)) {
      this._setModelerTemplateIcon(element, template);
    }

    // (4) apply properties
    this._applyProperties(element, properties);
    return element;
  }
  _createShape(template) {
    const {
      appliesTo,
      elementType = {}
    } = template;
    const elementFactory = this._elementFactory;
    const attrs = {
      type: elementType.value || appliesTo[0]
    };
    if (isSubprocess(attrs.type)) {
      attrs.isExpanded = true;
    }

    // apply eventDefinition
    if (elementType.eventDefinition) {
      attrs.eventDefinitionType = elementType.eventDefinition;
    }
    const element = elementFactory.createShape(attrs);
    return element;
  }
  _ensureExtensionElements(element) {
    const bpmnFactory = this._bpmnFactory;
    const businessObject = getBusinessObject(element);
    let extensionElements = businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
        values: []
      });
      extensionElements.$parent = businessObject;
      businessObject.set('extensionElements', extensionElements);
    }
    return extensionElements;
  }
  _setModelerTemplate(element, template) {
    const {
      id,
      version
    } = template;
    const businessObject = getBusinessObject(element);
    businessObject.set('zeebe:modelerTemplate', id);
    businessObject.set('zeebe:modelerTemplateVersion', version);
  }
  _setModelerTemplateIcon(element, template) {
    const {
      icon
    } = template;
    const {
      contents
    } = icon;
    const businessObject = getBusinessObject(element);
    businessObject.set('zeebe:modelerTemplateIcon', contents);
  }

  /**
   * Apply properties to a given element.
   *
   * @param {djs.model.Base} element
   * @param {Array<Object>} properties
   */
  _applyProperties(element, properties) {
    const processedProperties = [];
    properties.forEach(property => this._applyProperty(element, property, properties, processedProperties));
  }

  /**
   * Apply a property and its parent properties to an element based on conditions.
   *
   * @param {djs.model.Base} element
   * @param {Object} property
   * @param {Array<Object>} properties
   * @param {Array<Object>} processedProperties
   */
  _applyProperty(element, property, properties, processedProperties) {
    // skip if already processed
    if (processedProperties.includes(property)) {
      return;
    }

    // apply dependant property first if not already applied
    const dependentProperties = findDependentProperties(property, properties);
    dependentProperties.forEach(property => this._applyProperty(element, property, properties, processedProperties));

    // check condition and apply property if condition is met
    if (isConditionMet(element, properties, property)) {
      this._bindProperty(property, element);
    }
    processedProperties.push(property);
  }

  /**
   * Bind property to element.
   * @param {Object} property
   * @param {djs.Model.Base} element
   */
  _bindProperty(property, element) {
    const {
      binding
    } = property;
    const {
      type: bindingType
    } = binding;
    const bindingProvider = this._providers[bindingType];
    bindingProvider.create(element, {
      property,
      bpmnFactory: this._bpmnFactory
    });
  }
}
TemplateElementFactory.$inject = ['bpmnFactory', 'elementFactory'];

// helper ////////////////

function hasIcon(template) {
  const {
    icon
  } = template;
  return !!(icon && icon.contents);
}
function findDependentProperties(property, properties) {
  const {
    condition
  } = property;
  if (!condition) {
    return [];
  }
  const dependentProperty = findPropertyById(properties, condition.property);
  if (dependentProperty) {
    return [dependentProperty];
  }
  return [];
}
function findPropertyById(properties, id) {
  return find(properties, function (property) {
    return property.id === id;
  });
}

var createModule = {
  __init__: ['templateElementFactory'],
  templateElementFactory: ['type', TemplateElementFactory]
};

/**
 * Checks the conditions of an element template and sets/resets the
 * corresponding properties on the element.
 */
class ConditionalBehavior extends CommandInterceptor {
  /**
   * @param {import('diagram-js/lib/core/EventBus').default} eventBus
   * @param {import('../ElementTemplates').default} elementTemplates
   * @param {import('diagram-js/lib/command/CommandStack').default} commandStack
   * @param {import('bpmn-js/lib/features/modeling/BpmnFactory').default} bpmnFactory
   * @param {import('didi').Injector} injector
   */
  constructor(eventBus, elementTemplates, commandStack, bpmnFactory, injector) {
    super(eventBus);
    this._eventBus = eventBus;
    this._elementTemplates = elementTemplates;
    this._commandStack = commandStack;
    this._bpmnFactory = bpmnFactory;
    this._injector = injector;

    // (1) save pre-conditional state before updating a property
    this.preExecute(['element.updateProperties', 'element.updateModdleProperties', 'element.move'], this._saveConditionalState, true, this);

    // (2) check if we need to apply post-conditional updates
    //
    //   if [additional bindings activate] then
    //     (re-)trigger setting the template
    //   else
    //     else we're done
    //
    this.postExecute(['element.updateProperties', 'element.updateModdleProperties', 'propertiesPanel.zeebe.changeTemplate', 'element.move'], this._applyConditions, true, this);

    // (3) set conditions before changing the template
    this.preExecute(['propertiesPanel.zeebe.changeTemplate'], this._ensureConditional, true, this);
  }
  _saveConditionalState(context) {
    const {
      element
    } = context;
    const template = this._elementTemplates.get(element);
    if (!template) {
      return;
    }
    context.oldTemplateWithConditions = applyConditions(element, template);
  }
  _applyConditions(context, event) {
    const {
      element,
      newTemplate,
      oldTemplateWithConditions
    } = context;

    // do not apply conditions if we only update meta information
    if (isMetaUpdate(event, context)) {
      return;
    }
    const template = this._elementTemplates.get(element);

    // new Template is persisted before applying default values,
    // new conditions might apply after the defaults are present.
    const oldTemplate = oldTemplateWithConditions || newTemplate;
    if (!template || !oldTemplate) {
      return;
    }
    const newTemplateWithConditions = applyConditions(element, template);

    // verify that new bindings were activated
    if (!hasDifferentPropertyBindings(newTemplateWithConditions, oldTemplate)) {
      return;
    }

    // do another pass to apply further conditional bindings
    // newTemplate will always be the original template; it is filtered
    // at a later step (3)
    const changeContext = {
      element,
      newTemplate: template,
      oldTemplate
    };
    this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', changeContext);
  }
  _ensureConditional(context) {
    const {
      element,
      newTemplate
    } = context;
    if (!newTemplate) {
      return;
    }

    // ensure conditions are applied before changing the template.
    // `newTemplate` will always be the original template.
    context.newTemplate = applyConditions(element, newTemplate);
  }
}
ConditionalBehavior.$inject = ['eventBus', 'elementTemplates', 'commandStack', 'bpmnFactory', 'injector'];

// helpers

function hasDifferentPropertyBindings(sourceTemplate, targetTemplate) {
  return hasNewProperties(sourceTemplate, targetTemplate) || hasRemovedProperties(sourceTemplate, targetTemplate);
}
function hasNewProperties(sourceTemplate, targetTemplate) {
  let properties = targetTemplate.properties;
  return properties.some(targetProp => !sourceTemplate.properties.find(sourceProp => compareProps(sourceProp, targetProp)));
}
function hasRemovedProperties(oldTemplate, newTemplate) {
  const oldProperties = getMissingProperties(newTemplate, oldTemplate);

  // ensure XML properties are mantained for properties with
  // different conditions but same bindings
  return oldProperties.some(property => !findPropertyWithBinding(newTemplate, property));
}
function getMissingProperties(sourceTemplate, targetTemplate) {
  let properties = targetTemplate.properties;
  return properties.filter(targetProp => !sourceTemplate.properties.find(sourceProp => compareProps(sourceProp, targetProp)));
}
function compareProps(sourceProp, targetProp) {
  return areBindingsEqual(sourceProp.binding, targetProp.binding) && equals(sourceProp.condition, targetProp.condition);
}
function findPropertyWithBinding(template, prop1) {
  return template.properties.some(prop2 => areBindingsEqual(prop1.binding, prop2.binding));
}
function normalizeReplacer$1(key, value) {
  if (isObject(value)) {
    const keys = Object.keys(value).sort();
    return keys.reduce((obj, key) => {
      obj[key] = value[key];
      return obj;
    }, {});
  }
  return value;
}
function areBindingsEqual(binding1, binding2) {
  binding1 = normalizeBinding(binding1);
  binding2 = normalizeBinding(binding2);
  return equals(binding1, binding2);
}

/**
 * Convert deprecated binding type to new type.
 */
function normalizeBinding(binding) {
  if (binding.type === ZEEBE_TASK_DEFINITION_TYPE_TYPE) {
    return {
      ...binding,
      type: ZEEBE_TASK_DEFINITION,
      property: 'type'
    };
  }
  return binding;
}
function equals(a, b) {
  return JSON.stringify(a, normalizeReplacer$1) === JSON.stringify(b, normalizeReplacer$1);
}

/**
 * Checks if the event only updates "meta" properties:
 *
 *   * `zeebe:modelerTemplate`
 *   * `zeebe:modelerTemplateVersion`
 *
 * @param {string} event
 * @param {any} context
 *
 * @return {boolean}
 */
function isMetaUpdate(event, context) {
  return event === 'element.updateProperties' && Object.keys(context.properties).every(key => ['zeebe:modelerTemplate', 'zeebe:modelerTemplateVersion'].includes(key));
}

/**
 * This Behavior checks if the new element's type is in
 * the list of elements the template applies to and unlinks
 * it if not.
 */
let ReplaceBehavior$1 = class ReplaceBehavior extends CommandInterceptor {
  constructor(elementTemplates, injector) {
    super(injector.get('eventBus'));
    this.postExecuted('shape.replace', function (e) {
      var context = e.context,
        oldShape = context.oldShape,
        oldBo = getBusinessObject(oldShape),
        newShape = context.newShape,
        newBo = getBusinessObject(newShape);
      if (!oldBo.modelerTemplate) {
        return;
      }
      const template = newBo.modelerTemplate;
      const version = newBo.modelerTemplateVersion;
      const elementTemplate = elementTemplates.get(template, version);
      if (!elementTemplate) {
        elementTemplates.unlinkTemplate(newShape);
        return;
      }
      const {
        appliesTo,
        elementType
      } = elementTemplate;
      if (elementType) {
        if (!is(newShape, elementType.value) || shouldUnlinkEvent(newShape, elementType)) {
          elementTemplates.unlinkTemplate(newShape);
        }
        return;
      }
      const allowed = appliesTo.reduce((allowed, type) => {
        return allowed || is(newBo, type);
      }, false);
      if (!allowed) {
        elementTemplates.unlinkTemplate(newShape);
      }
    });
  }
};
ReplaceBehavior$1.$inject = ['elementTemplates', 'injector'];
function shouldUnlinkEvent(newShape, elementType) {
  if (!is(newShape, 'bpmn:Event')) {
    return false;
  }
  const {
      eventDefinition
    } = elementType,
    bo = getBusinessObject(newShape),
    eventDefinitions = bo.get('eventDefinitions');
  if (!eventDefinition) {
    return eventDefinitions.length !== 0;
  }
  return !is(eventDefinitions[0], eventDefinition);
}

/**
 * This Behavior monitors changes to timer events and unlinks templates
 * when constraints are violated:
 * - timeCycle requires non-interrupting for boundary events (cancelActivity: false)
 * - timeCycle requires non-interrupting for event subprocess starts (isInterrupting: false)
 * - timeDuration is not valid for process-level start events
 */
class TimerTemplateBehavior extends CommandInterceptor {
  constructor(eventBus, elementTemplates) {
    super(eventBus);
    this._elementTemplates = elementTemplates;

    // Handle property changes - only react to cancelActivity/isInterrupting changes
    this.postExecuted(['element.updateProperties', 'element.updateModdleProperties'], this._handlePropertiesChange, true, this);

    // Handle shape move - element may have moved to a different context
    this.postExecuted('shape.move', context => {
      const element = context.shape;
      this._unlinkIfInvalidTimerTemplate(element);
    }, true);

    // Handle copy-paste: unlink templates that become invalid in the new context
    this.postExecuted('elements.create', context => {
      if (context.elements) {
        context.elements.forEach(el => this._unlinkIfInvalidTimerTemplate(el));
      }
    }, true);
  }
  _handlePropertiesChange(context) {
    const {
      element,
      properties
    } = context;
    const isRelevantPropertyChange = properties && ('cancelActivity' in properties || 'isInterrupting' in properties);
    if (!isRelevantPropertyChange) {
      return;
    }
    this._unlinkIfInvalidTimerTemplate(element);
  }
  _unlinkIfInvalidTimerTemplate(element) {
    if (!is(element, 'bpmn:Event')) {
      return;
    }
    const timerEventDefinition = findTimerEventDefinition(element);
    if (!timerEventDefinition) {
      return;
    }
    const elementTemplates = this._elementTemplates;
    const template = elementTemplates.get(element);
    if (!template) {
      return;
    }
    const timerBinding = template.properties.find(property => {
      return property.binding?.type === TIMER_EVENT_DEFINITION_PROPERTY_TYPE;
    });
    if (!timerBinding) {
      return;
    }
    const timerType = timerBinding.binding.name;
    if (!isTimerExpressionTypeSupported(timerType, element)) {
      elementTemplates.unlinkTemplate(element);
    }
  }
}
TimerTemplateBehavior.$inject = ['eventBus', 'elementTemplates'];

/**
 * Restores the original order of the template properties
 * on the moddle element.
 */
class UpdateTemplatePropertiesOrder extends CommandInterceptor {
  constructor(eventBus, elementTemplates, commandStack, bpmnFactory) {
    super(eventBus);
    this._eventBus = eventBus;
    this._elementTemplates = elementTemplates;
    this._commandStack = commandStack;
    this._bpmnFactory = bpmnFactory;
    this.postExecute(['element.updateProperties', 'element.updateModdleProperties'], this._updatePropertiesOrder, true, this);
  }
  _updatePropertiesOrder(context) {
    const {
      element
    } = context;
    const template = this._elementTemplates.get(element);
    const businessObject = getBusinessObject(element);
    const commands = [];
    if (!template) {
      return;
    }
    const templateProperties = applyConditions(element, template).properties;

    // zeebe:Property
    const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
    if (zeebeProperties) {
      this._updateZeebePropertiesOrder(zeebeProperties, templateProperties, commands, context);
    }

    // zeebe:IoMapping
    const ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');
    if (ioMapping) {
      // zeebe:Input
      this._updateInputOrder(ioMapping, templateProperties, commands, context);

      // zeebe:Output
      this._updateOutputOrder(ioMapping, templateProperties, commands, context);
    }

    // zeebe:TaskHeaders
    const taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');
    if (taskHeaders) {
      this._updateTaskHeadersOrder(taskHeaders, templateProperties, commands, context);
    }
    if (commands.length) {
      const commandsToExecute = commands.filter(command => command !== null);
      commandsToExecute.length && this._commandStack.execute('element-templates.multi-command-executor', commandsToExecute);
      return;
    }
  }
  _updateZeebePropertiesOrder(zeebeProperties, templateProperties, commands, context) {
    const findIndex = (properties, propertyToFind) => properties.findIndex(prop => prop.binding.type == 'zeebe:property' && prop.binding.name === propertyToFind.get('name'));
    const properties = zeebeProperties.get('properties');
    if (properties.length < 1) return;
    let newPropertiesOrder = [...properties];
    sortProperties(newPropertiesOrder, findIndex, templateProperties);
    if (!arrayEquals(newPropertiesOrder, properties)) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: zeebeProperties,
          properties: {
            properties: newPropertiesOrder
          }
        }
      });
    }
  }
  _updateInputOrder(ioMapping, templateProperties, commands, context) {
    const findIndex = (properties, propertyToFind) => properties.findIndex(prop => prop.binding.type == 'zeebe:input' && prop.binding.name === propertyToFind.get('target'));
    const inputParameters = ioMapping.get('inputParameters');
    if (inputParameters.length < 1) return;
    let newInputOrder = [...inputParameters];
    sortProperties(newInputOrder, findIndex, templateProperties);
    if (!arrayEquals(newInputOrder, inputParameters)) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: ioMapping,
          properties: {
            inputParameters: newInputOrder
          }
        }
      });
    }
  }
  _updateOutputOrder(ioMapping, templateProperties, commands, context) {
    const findIndex = (properties, propertyToFind) => properties.findIndex(prop => prop.binding.type == 'zeebe:output' && prop.binding.source === propertyToFind.get('source'));
    const outputParameters = ioMapping.get('outputParameters');
    if (outputParameters.length < 1) return;
    let newOutputOrder = [...outputParameters];
    sortProperties(newOutputOrder, findIndex, templateProperties);
    if (!arrayEquals(newOutputOrder, outputParameters)) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: ioMapping,
          properties: {
            outputParameters: newOutputOrder
          }
        }
      });
    }
  }
  _updateTaskHeadersOrder(taskHeaders, templateProperties, commands, context) {
    const findIndex = (properties, propertyToFind) => properties.findIndex(prop => prop.binding.type == 'zeebe:taskHeader' && prop.binding.key === propertyToFind.get('key'));
    const headers = taskHeaders.get('zeebe:values');
    if (headers.length < 1) return;
    let newHeadersOrder = [...headers];
    sortProperties(newHeadersOrder, findIndex, templateProperties);
    if (!arrayEquals(newHeadersOrder, headers)) {
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          ...context,
          moddleElement: taskHeaders,
          properties: {
            values: newHeadersOrder
          }
        }
      });
    }
  }
}
UpdateTemplatePropertiesOrder.$inject = ['eventBus', 'elementTemplates', 'commandStack', 'bpmnFactory'];

// helpers

function normalizeReplacer(key, value) {
  if (isObject(value)) {
    const keys = Object.keys(value).sort();
    return keys.reduce((obj, key) => {
      obj[key] = value[key];
      return obj;
    }, {});
  }
  return value;
}
function objectEquals(a, b) {
  return JSON.stringify(a, normalizeReplacer) === JSON.stringify(b, normalizeReplacer);
}
function arrayEquals(a, b) {
  return a.every((element, idx) => objectEquals(element, b[idx]));
}
function sortProperties(array, findIndex, templateProperties) {
  return array.sort((a, b) => {
    const aIndex = findIndex(templateProperties, a);
    const bIndex = findIndex(templateProperties, b);
    return aIndex - bIndex;
  });
}

/**
 * Handles referenced elements.
 */
class ReferencedElementBehavior extends CommandInterceptor {
  constructor(eventBus, elementTemplates, modeling, injector, moddleCopy, bpmnFactory) {
    super(eventBus);
    this._eventBus = eventBus;
    this._elementTemplates = elementTemplates;
    this._modeling = modeling;
    this._injector = injector;
    this.postExecuted(['element.updateProperties', 'element.updateModdleProperties'], this._handlePropertiesUpdate, true, this);
    this.postExecuted('shape.replace', this._handleReplacement, true, this);
    this.postExecuted('shape.delete', this._handleRemoval, true, this);

    // copy templated root element when pasting
    eventBus.on('copyPaste.pasteElement', function (context) {
      const {
        referencedRootElement
      } = context.descriptor;
      if (!referencedRootElement) {
        return;
      }
      if (!getTemplateId$1(referencedRootElement)) {
        return;
      }
      context.descriptor.referencedRootElement = moddleCopy.copyElement(referencedRootElement, bpmnFactory.create(referencedRootElement.$type));
    });
  }

  /**
   * Unlink referenced element when template is unlinked.
   */
  _handlePropertiesUpdate(context) {
    const {
      element,
      properties
    } = context;
    if (!canHaveReferencedElement(element)) {
      return;
    }
    if (!(TEMPLATE_ID_ATTR$1 in properties) || isString(properties[TEMPLATE_ID_ATTR$1])) {
      return;
    }
    const bo = getBusinessObject(element);
    const referencedElement = findMessage(bo) || findSignal(bo);
    if (referencedElement && getTemplateId$1(referencedElement)) {
      this._modeling.updateModdleProperties(element, referencedElement, {
        [TEMPLATE_ID_ATTR$1]: null
      });
    }
  }

  /**
   * Remove referenced element when template is removed.
   * Keep referenced element when template is replaced.
   */
  _handleReplacement(context) {
    const {
      oldShape,
      newShape
    } = context;
    const oldTemplate = getTemplateId$1(oldShape),
      newTemplate = getTemplateId$1(newShape);
    if (!canHaveReferencedElement(oldShape) || !oldTemplate) {
      return;
    }
    const bo = getBusinessObject(oldShape);
    const message = findMessage(bo);
    const signal = findSignal(bo);
    if (message && getTemplateId$1(message)) {
      if (!newTemplate || !canHaveMessage(newShape)) {
        removeRootElement(message, this._injector);
      } else {
        this._addMessage(newShape, message);
      }
    }
    if (signal && getTemplateId$1(signal)) {
      if (!newTemplate || !canHaveSignal(newShape)) {
        removeRootElement(signal, this._injector);
      } else {
        this._addSignal(newShape, signal);
      }
    }
  }
  _handleRemoval(context) {
    const {
      shape
    } = context;
    if (isLabel(shape)) {
      return;
    }
    if (!canHaveReferencedElement(shape)) {
      return;
    }
    if (!getTemplateId$1(shape)) {
      return;
    }
    const bo = getBusinessObject(shape);
    const referencedElement = findMessage(bo) || findSignal(bo);
    if (referencedElement && getTemplateId$1(referencedElement)) {
      removeRootElement(referencedElement, this._injector);
    }
  }
  _addMessage(element, message) {
    const bo = getReferringElement(element);
    this._modeling.updateModdleProperties(element, bo, {
      'messageRef': message
    });
  }
  _addSignal(element, signal) {
    const bo = getReferringElement(element);
    this._modeling.updateModdleProperties(element, bo, {
      'signalRef': signal
    });
  }
}
ReferencedElementBehavior.$inject = ['eventBus', 'elementTemplates', 'modeling', 'injector', 'moddleCopy', 'bpmnFactory'];
function canHaveReferencedElement(element) {
  // Blank-Events can't have referenced elements
  if (is(element, 'bpmn:Event')) {
    const bo = getBusinessObject(element);
    return bo.get('eventDefinitions') && bo.get('eventDefinitions')[0];
  }
  return isAny(element, ['bpmn:ReceiveTask', 'bpmn:SendTask']);
}
function canHaveMessage(element) {
  if (is(element, 'bpmn:ReceiveTask') || is(element, 'bpmn:SendTask')) {
    return true;
  }
  if (is(element, 'bpmn:Event')) {
    const bo = getBusinessObject(element);
    const eventDefinitions = bo.get('eventDefinitions');
    if (!eventDefinitions || !eventDefinitions.length) {
      return false;
    }
    return is(eventDefinitions[0], 'bpmn:MessageEventDefinition');
  }
  return false;
}
function canHaveSignal(element) {
  if (is(element, 'bpmn:Event')) {
    const bo = getBusinessObject(element);
    const eventDefinitions = bo.get('eventDefinitions');
    if (!eventDefinitions || !eventDefinitions.length) {
      return false;
    }
    return is(eventDefinitions[0], 'bpmn:SignalEventDefinition');
  }
  return false;
}
function isLabel(element) {
  return element.type === 'label';
}

/**
 * Handles generated value properties.
 */
class GeneratedValueBehavior extends CommandInterceptor {
  constructor(eventBus, elementTemplates, modeling, commandStack, bpmnFactory) {
    super(eventBus);
    this._eventBus = eventBus;
    this._elementTemplates = elementTemplates;
    this._modeling = modeling;
    this.preExecute('shape.create', context => {
      const element = context.shape;
      const template = elementTemplates.get(element);
      if (!template) {
        return;
      }
      const generatedProps = template.properties.filter(p => p.generatedValue);
      generatedProps.forEach(p => {
        if (!getPropertyValue(element, p)) {
          return;
        }
        const value = getDefaultValue(p);
        setPropertyValue(bpmnFactory, commandStack, element, p, value);
      });
    }, true);
  }
}
GeneratedValueBehavior.$inject = ['eventBus', 'elementTemplates', 'modeling', 'commandStack', 'bpmnFactory'];

/**
 * Enforces no variable propagation for templated call activities.
 */
class CalledElementBehavior extends CommandInterceptor {
  /**
   * @param {*} eventBus
   * @param {*} modeling
   * @param {import('../ElementTemplates').default} elementTemplates
   */
  constructor(eventBus, modeling, elementTemplates) {
    super(eventBus);
    this._modeling = modeling;
    this._elementTemplates = elementTemplates;
    this.postExecuted(['element.updateProperties', 'element.updateModdleProperties'], this._ensureNoPropagation, true, this);
  }
  _ensureNoPropagation(context) {
    const {
      element
    } = context;
    if (!this._elementTemplates.get(element)) {
      return;
    }
    if (!is(element, 'bpmn:CallActivity')) {
      return;
    }
    const calledElement = findExtension$1(element, 'zeebe:CalledElement');
    if (!calledElement) {
      return;
    }
    for (const property of ['propagateAllChildVariables', 'propagateAllParentVariables']) {
      if (calledElement.get(property) !== false) {
        this._modeling.updateModdleProperties(element, calledElement, {
          [property]: false
        });
      }
    }
  }
}
CalledElementBehavior.$inject = ['eventBus', 'modeling', 'elementTemplates'];

/**
 * Disable the default Camunda behavior for a newly created element
 * if it is a templated `<bpmn:UserTask>`.
 */
class UserTaskBehavior extends CommandInterceptor {
  constructor(eventBus, elementTemplates) {
    super(eventBus);
    this.preExecute('shape.create', function (event) {
      const {
        context: {
          shape,
          hints = {}
        }
      } = event;
      if (is(shape, 'bpmn:UserTask') && elementTemplates.get(shape)) {
        assign(hints, {
          createElementsBehavior: false
        });
      }
    });
  }
}
UserTaskBehavior.$inject = ['eventBus', 'elementTemplates'];

var behaviorModule$1 = {
  __init__: ['elementTemplatesReplaceBehavior', 'elementTemplatesConditionalBehavior', 'elementTemplatesGeneratedValueBehavior', 'elementTemplatesReferencedElementBehavior', 'elementTemplatesUpdatePropertiesOrderBehavior', 'elementTemplatesCalledElementBehavior', 'elementTemplatesUserTaskBehavior', 'elementTemplatesTimerTemplateBehavior'],
  elementTemplatesReplaceBehavior: ['type', ReplaceBehavior$1],
  elementTemplatesConditionalBehavior: ['type', ConditionalBehavior],
  elementTemplatesGeneratedValueBehavior: ['type', GeneratedValueBehavior],
  elementTemplatesReferencedElementBehavior: ['type', ReferencedElementBehavior],
  elementTemplatesUpdatePropertiesOrderBehavior: ['type', UpdateTemplatePropertiesOrder],
  elementTemplatesCalledElementBehavior: ['type', CalledElementBehavior],
  elementTemplatesUserTaskBehavior: ['type', UserTaskBehavior],
  elementTemplatesTimerTemplateBehavior: ['type', TimerTemplateBehavior]
};

var coreModule$1 = {
  __depends__: [commandsModule$1, behaviorModule$1, createModule],
  __init__: ['elementTemplatesLoader'],
  elementTemplates: ['type', ElementTemplates],
  elementTemplatesLoader: ['type', ElementTemplatesLoader]
};

function getVersionOrDateFromTemplate(template) {
  const metadata = template.metadata,
    version = template.version;
  if (metadata) {
    if (!isUndefined$1(metadata.created)) {
      return toDateString(metadata.created);
    } else if (!isUndefined$1(metadata.updated)) {
      return toDateString(metadata.updated);
    }
  }
  if (isUndefined$1(version)) {
    return null;
  }
  return version;
}

// helper ///////////

/**
 * Example: 01.01.1900 01:01
 *
 * @param {number} timestamp
 * @returns {string}
 */
function toDateString(timestamp) {
  const date = new Date(timestamp);
  const year = date.getFullYear();
  const month = withLeadingZeros(String(date.getMonth() + 1));
  const day = withLeadingZeros(String(date.getDate()));
  const hours = withLeadingZeros(String(date.getHours()));
  const minutes = withLeadingZeros(String(date.getMinutes()));
  return day + '.' + month + '.' + year + ' ' + hours + ':' + minutes;
}
function withLeadingZeros(string) {
  return leftPad(string, 2, '0');
}
function leftPad(string, length, character) {
  while (string.length < length) {
    string = character + string;
  }
  return string;
}

/**
 * @typedef {NoTemplate|KnownTemplate|UnknownTemplate|OutdatedTemplate} TemplateState
 */

/**
 * @typedef NoTemplate
 * @property {'NO_TEMPLATE'} type
 *
 * @typedef KnownTemplate
 * @property {'KNOWN_TEMPLATE'} type
 * @property {object} template
 *
 * @typedef UnknownTemplate
 * @property {'UNKNOWN_TEMPLATE'} type
 * @property {string} templateId
 *
 * @typedef OutdatedTemplate
 * @property {'OUTDATED_TEMPLATE'} type
 * @property {object} template
 * @property {object} newerTemplate
 */

/**
 * Factory to create an element templates group.
 *
 * @param {object} [props]
 * @param {function} props.getTemplateId
 * @param {function} [props.unlinkTemplate]
 * @param {function} [props.updateTemplate]
 */
function createElementTemplatesGroup(props = {}) {
  const {
    getTemplateId
  } = props;
  return function ElementTemplatesGroup(props) {
    const {
      id,
      label,
      element,
      entries = []
    } = props;
    const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
    const empty = !entries.length;
    const toggleOpen = () => !empty && setOpen(!open);
    return jsxs("div", {
      class: "bio-properties-panel-group bio-properties-panel-templates-group",
      "data-group-id": 'group-' + id,
      children: [jsxs("div", {
        class: classnames('bio-properties-panel-group-header', {
          empty,
          open: open && !empty
        }),
        onClick: toggleOpen,
        children: [jsx("div", {
          class: "bio-properties-panel-group-header-title",
          children: label
        }), jsxs("div", {
          class: "bio-properties-panel-group-header-buttons",
          children: [jsx(TemplateGroupButtons, {
            element: element,
            getTemplateId: getTemplateId
          }), !empty && jsx(SectionToggle, {
            open: open
          })]
        })]
      }), jsx("div", {
        class: classnames('bio-properties-panel-group-entries', {
          open: open && !empty
        }),
        children: entries.map(entry => {
          const {
            component: Component,
            id
          } = entry;
          return createElement$1(Component, {
            ...entry,
            key: id,
            element: element
          });
        })
      })]
    });
  };
}
function SectionToggle({
  open
}) {
  return jsx(HeaderButton, {
    title: "Toggle section",
    class: "bio-properties-panel-arrow",
    children: jsx(ArrowIcon, {
      class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
    })
  });
}

/**
 *
 * @param {object} props
 * @param {object} props.element
 * @param {function} props.getTemplateId
 * @param {function} props.unlinkTemplate
 * @param {function} props.updateTemplate
 */
function TemplateGroupButtons({
  element,
  getTemplateId
}) {
  const elementTemplates = useService('elementTemplates');
  const templateState = getTemplateState(elementTemplates, element, getTemplateId);
  if (templateState.type === 'NO_TEMPLATE') {
    return jsx(SelectEntryTemplate, {
      element: element
    });
  } else if (templateState.type === 'KNOWN_TEMPLATE') {
    return jsx(AppliedTemplate, {
      element: element
    });
  } else if (templateState.type === 'UNKNOWN_TEMPLATE') {
    return jsx(UnknownTemplate, {
      element: element
    });
  } else if (templateState.type === 'DEPRECATED_TEMPLATE') {
    return jsx(DeprecatedTemplate, {
      element: element,
      templateState: templateState
    });
  } else if (templateState.type === 'INCOMPATIBLE_TEMPLATE') {
    return jsx(IncompatibleTemplate, {
      element: element
    });
  } else if (templateState.type === 'OUTDATED_TEMPLATE') {
    return jsx(OutdatedTemplate, {
      element: element,
      templateState: templateState
    });
  }
}
function SelectEntryTemplate({
  element
}) {
  const translate = useService('translate');
  const eventBus = useService('eventBus');
  const selectTemplate = () => eventBus.fire('elementTemplates.select', {
    element
  });
  return jsxs(HeaderButton, {
    title: "Select a template",
    class: "bio-properties-panel-select-template-button",
    onClick: selectTemplate,
    children: [jsx(CreateIcon, {}), jsx("span", {
      children: translate('Select')
    })]
  });
}
function AppliedTemplate({
  element
}) {
  const translate = useService('translate'),
    elementTemplates = useService('elementTemplates');
  const menuItems = [{
    entry: translate('Unlink'),
    action: () => elementTemplates.unlinkTemplate(element)
  }, {
    entry: jsx(RemoveTemplate, {}),
    action: () => elementTemplates.removeTemplate(element)
  }];
  return jsx(DropdownButton, {
    menuItems: menuItems,
    class: "bio-properties-panel-applied-template-button",
    children: jsxs(HeaderButton, {
      children: [jsx("span", {
        children: translate('Applied')
      }), jsx(ArrowIcon, {
        class: "bio-properties-panel-arrow-down"
      })]
    })
  });
}
function RemoveTemplate() {
  const translate = useService('translate');
  return jsx("span", {
    class: "bio-properties-panel-remove-template",
    children: translate('Remove')
  });
}
function UnknownTemplate({
  element
}) {
  const translate = useService('translate'),
    elementTemplates = useService('elementTemplates');
  const menuItems = [{
    entry: jsx(NotFoundText, {})
  }, {
    separator: true
  }, {
    entry: translate('Unlink'),
    action: () => elementTemplates.unlinkTemplate(element)
  }, {
    entry: jsx(RemoveTemplate, {}),
    action: () => elementTemplates.removeTemplate(element)
  }];
  return jsx(DropdownButton, {
    menuItems: menuItems,
    class: "bio-properties-panel-template-not-found",
    children: jsxs(HeaderButton, {
      children: [jsx("span", {
        children: translate('Not found')
      }), jsx(ArrowIcon, {
        class: "bio-properties-panel-arrow-down"
      })]
    })
  });
}
function NotFoundText() {
  const translate = useService('translate');
  return jsx("div", {
    class: "bio-properties-panel-template-not-found-text",
    children: translate('The template applied was not found. Therefore, its properties cannot be shown. Unlink to access the data.')
  });
}

/**
 *
 * @param {object} props
 * @param {object} element
 * @param {UnknownTemplate} templateState
 * @param {function} unlinkTemplate
 * @param {function} updateTemplate
 */
function OutdatedTemplate({
  element,
  templateState
}) {
  const {
    newerTemplate,
    compatible
  } = templateState;
  const translate = useService('translate'),
    elementTemplates = useService('elementTemplates');
  const menuItems = [{
    entry: jsx(UpdateAvailableText, {
      newerTemplate: newerTemplate,
      compatible: compatible
    })
  }, {
    separator: true
  }, {
    entry: translate('Update'),
    action: () => elementTemplates.applyTemplate(element, newerTemplate)
  }, {
    entry: translate('Unlink'),
    action: () => elementTemplates.unlinkTemplate(element)
  }, {
    entry: jsx(RemoveTemplate, {}),
    action: () => elementTemplates.removeTemplate(element)
  }];
  const cls = compatible ? 'bio-properties-panel-template-update-available' : 'bio-properties-panel-template-incompatible';
  const text = compatible ? translate('Update available') : translate('Incompatible');
  return jsx(DropdownButton, {
    menuItems: menuItems,
    class: cls,
    children: jsxs(HeaderButton, {
      children: [jsx("span", {
        children: text
      }), jsx(ArrowIcon, {
        class: "bio-properties-panel-arrow-down"
      })]
    })
  });
}
function UpdateAvailableText({
  newerTemplate,
  compatible
}) {
  const translate = useService('translate');
  const text = compatible ? translate('A new version of the template is available: {templateVersion}', {
    templateVersion: getVersionOrDateFromTemplate(newerTemplate)
  }) : translate('A version of this template is available that supports your environment: {templateVersion}', {
    templateVersion: getVersionOrDateFromTemplate(newerTemplate)
  });
  return jsx("div", {
    class: "bio-properties-panel-template-update-available-text",
    children: text
  });
}
function DeprecatedTemplate({
  element,
  templateState
}) {
  const {
    template
  } = templateState;
  const translate = useService('translate'),
    elementTemplates = useService('elementTemplates');
  const menuItems = [{
    entry: jsx(DeprecationWarning, {
      template: template
    })
  }, {
    separator: true
  }, {
    entry: translate('Unlink'),
    action: () => elementTemplates.unlinkTemplate(element)
  }, {
    entry: jsx(RemoveTemplate, {}),
    action: () => elementTemplates.removeTemplate(element)
  }];
  return jsx(DropdownButton, {
    menuItems: menuItems,
    class: "bio-properties-panel-deprecated-template-button",
    children: jsxs(HeaderButton, {
      children: [jsx("span", {
        children: translate('Deprecated')
      }), jsx(ArrowIcon, {
        class: "bio-properties-panel-arrow-down"
      })]
    })
  });
}
function DeprecationWarning({
  template
}) {
  const translate = useService('translate');
  const {
    message = translate('This template is deprecated.'),
    documentationRef
  } = template.deprecated;
  return jsxs("div", {
    class: "bio-properties-panel-deprecated-template-text",
    children: [message, documentationRef && jsxs(Fragment, {
      children: ["\xA0", jsx("a", {
        href: documentationRef,
        children: jsx(DocumentationIcon, {})
      })]
    })]
  });
}
function DocumentationIcon() {
  return jsx("svg", {
    width: "12",
    height: "12",
    viewBox: "0 0 12 12",
    fill: "none",
    xmlns: "http://www.w3.org/2000/svg",
    children: jsx("path", {
      fillRule: "evenodd",
      clipRule: "evenodd",
      d: "M10.6368 10.6375V5.91761H11.9995V10.6382C11.9995 10.9973 11.8623 11.3141 11.5878 11.5885C11.3134 11.863 10.9966 12.0002 10.6375 12.0002H1.36266C0.982345 12.0002 0.660159 11.8681 0.396102 11.6041C0.132044 11.34 1.52588e-05 11.0178 1.52588e-05 10.6375V1.36267C1.52588e-05 0.98236 0.132044 0.660173 0.396102 0.396116C0.660159 0.132058 0.982345 2.95639e-05 1.36266 2.95639e-05H5.91624V1.36267H1.36266V10.6375H10.6368ZM12 0H7.2794L7.27873 1.36197H9.68701L3.06507 7.98391L4.01541 8.93425L10.6373 2.31231V4.72059H12V0Z",
      fill: "#818798"
    })
  });
}
function IncompatibleTemplate({
  element
}) {
  const translate = useService('translate'),
    elementTemplates = useService('elementTemplates');
  const menuItems = [{
    entry: jsx(IncompatibleText, {})
  }, {
    separator: true
  }, {
    entry: translate('Unlink'),
    action: () => elementTemplates.unlinkTemplate(element)
  }, {
    entry: jsx(RemoveTemplate, {}),
    action: () => elementTemplates.removeTemplate(element)
  }];
  return jsx(DropdownButton, {
    menuItems: menuItems,
    class: "bio-properties-panel-template-incompatible",
    children: jsxs(HeaderButton, {
      children: [jsx("span", {
        children: translate('Incompatible')
      }), jsx(ArrowIcon, {
        class: "bio-properties-panel-arrow-down"
      })]
    })
  });
}
function IncompatibleText() {
  const translate = useService('translate');
  return jsx("div", {
    class: "bio-properties-panel-template-incompatible-text",
    children: translate('No compatible version of this template was found for your environment. Unlink to access the template’s data.')
  });
}

// helper //////

/**
 * Determine template state in the current element.
 *
 * @param {object} elementTemplates
 * @param {object} element
 * @param {function} getTemplateId
 * @returns {TemplateState}
 */
function getTemplateState(elementTemplates, element, getTemplateId) {
  const templateId = getTemplateId(element),
    template = elementTemplates.get(element);
  if (!templateId) {
    return {
      type: 'NO_TEMPLATE'
    };
  }
  if (!template) {
    return {
      type: 'UNKNOWN_TEMPLATE',
      templateId
    };
  }
  if (template.deprecated) {
    return {
      type: 'DEPRECATED_TEMPLATE',
      template
    };
  }
  const compatible = elementTemplates.isCompatible(template);
  const latestTemplate = elementTemplates.getLatest(templateId, {
    deprecated: true
  })[0];
  if (latestTemplate && latestTemplate !== template) {
    return {
      type: 'OUTDATED_TEMPLATE',
      template,
      newerTemplate: latestTemplate,
      compatible
    };
  }
  if (!compatible) {
    return {
      type: 'INCOMPATIBLE_TEMPLATE',
      template
    };
  }
  return {
    type: 'KNOWN_TEMPLATE',
    template
  };
}

function TemplateProps({
  element,
  elementTemplates,
  getTemplateId,
  getTemplateVersion
}) {
  const localTemplate = elementTemplates.get(element);
  const template = localTemplate || {
    id: getTemplateId(element),
    version: getTemplateVersion(element)
  };

  // no template configured
  if (!template.id) {
    return [];
  }
  return [!localTemplate && {
    id: 'template-id',
    component: TemplateId,
    template
  }, {
    id: 'template-name',
    component: TemplateName,
    template
  }, {
    id: 'template-version',
    component: TemplateVersion,
    template
  }, {
    id: 'template-description',
    component: TemplateDescription,
    template
  }];
}
function TemplateName({
  id,
  template
}) {
  const translate = useService('translate');
  const {
    name
  } = template;
  return name ? jsx(TextEntry, {
    id: id,
    label: translate('Name'),
    content: name
  }) : null;
}
function TemplateId({
  id,
  template
}) {
  const translate = useService('translate');
  return jsx(TextEntry, {
    id: id,
    label: translate('ID'),
    content: template.id
  });
}
function TemplateVersion({
  id,
  template
}) {
  const translate = useService('translate');
  const version = getVersionOrDateFromTemplate(template);
  return version ? jsx(TextEntry, {
    id: id,
    label: translate('Version'),
    content: version
  }) : null;
}
function TemplateDescription({
  id,
  template
}) {
  const translate = useService('translate');
  const {
    description
  } = template;
  return description ? jsx(TextEntry, {
    id: id,
    label: translate('Description'),
    content: template.description
  }) : null;
}
function TextEntry({
  id,
  label,
  content
}) {
  return jsxs("div", {
    "data-entry-id": id,
    class: "bio-properties-panel-entry bio-properties-panel-text-entry",
    children: [jsx("span", {
      class: "bio-properties-panel-label",
      children: label
    }), jsx("span", {
      class: "bio-properties-panel-text-entry__content",
      children: content
    })]
  });
}

var e,
  o = {};
function n(r, t, e) {
  if (3 === r.nodeType) {
    var o = "textContent" in r ? r.textContent : r.nodeValue || "";
    if (false !== n.options.trim) {
      var a = 0 === t || t === e.length - 1;
      if ((!(o = o.match(/^[\s\n]+$/g) && "all" !== n.options.trim ? " " : o.replace(/(^[\s\n]+|[\s\n]+$)/g, "all" === n.options.trim || a ? "" : " ")) || " " === o) && e.length > 1 && a) return null;
    }
    return o;
  }
  if (1 !== r.nodeType) return null;
  var p = String(r.nodeName).toLowerCase();
  if ("script" === p && !n.options.allowScripts) return null;
  var l,
    s,
    u = n.h(p, function (r) {
      var t = r && r.length;
      if (!t) return null;
      for (var e = {}, o = 0; o < t; o++) {
        var a = r[o],
          i = a.name,
          p = a.value;
        "on" === i.substring(0, 2) && n.options.allowEvents && (p = new Function(p)), e[i] = p;
      }
      return e;
    }(r.attributes), (s = (l = r.childNodes) && Array.prototype.map.call(l, n).filter(i)) && s.length ? s : null);
  return n.visitor && n.visitor(u), u;
}
var a,
  i = function (r) {
    return r;
  },
  p = {};
function l(r) {
  var t = (r.type || "").toLowerCase(),
    e = l.map;
  e && e.hasOwnProperty(t) ? (r.type = e[t], r.props = Object.keys(r.props || {}).reduce(function (t, e) {
    var o;
    return t[o = e, o.replace(/-(.)/g, function (r, t) {
      return t.toUpperCase();
    })] = r.props[e], t;
  }, {})) : r.type = t.replace(/[^a-z0-9-]/i, "");
}
var Markup = (function (t) {
  function i() {
    t.apply(this, arguments);
  }
  return t && (i.__proto__ = t), (i.prototype = Object.create(t && t.prototype)).constructor = i, i.setReviver = function (r) {
    a = r;
  }, i.prototype.shouldComponentUpdate = function (r) {
    var t = this.props;
    return r.wrap !== t.wrap || r.type !== t.type || r.markup !== t.markup;
  }, i.prototype.setComponents = function (r) {
    if (this.map = {}, r) for (var t in r) if (r.hasOwnProperty(t)) {
      var e = t.replace(/([A-Z]+)([A-Z][a-z0-9])|([a-z0-9]+)([A-Z])/g, "$1$3-$2$4").toLowerCase();
      this.map[e] = r[t];
    }
  }, i.prototype.render = function (t) {
    var i = t.wrap;
    void 0 === i && (i = true);
    var s,
      u = t.type,
      c = t.markup,
      m = t.components,
      v = t.reviver,
      f = t.onError,
      d = t["allow-scripts"],
      h$1 = t["allow-events"],
      y = t.trim,
      w = function (r, t) {
        var e = {};
        for (var o in r) Object.prototype.hasOwnProperty.call(r, o) && -1 === t.indexOf(o) && (e[o] = r[o]);
        return e;
      }(t, ["wrap", "type", "markup", "components", "reviver", "onError", "allow-scripts", "allow-events", "trim"]),
      C = v || this.reviver || this.constructor.prototype.reviver || a || h;
    this.setComponents(m);
    var g = {
      allowScripts: d,
      allowEvents: h$1,
      trim: y
    };
    try {
      s = function (r, t, a, i, s) {
        var u = function (r, t) {
          var o,
            n,
            a,
            i,
            p = "html" === t ? "text/html" : "application/xml";
          "html" === t ? (i = "body", a = "<!DOCTYPE html>\n<html><body>" + r + "</body></html>") : (i = "xml", a = '<?xml version="1.0" encoding="UTF-8"?>\n<xml>' + r + "</xml>");
          try {
            o = new DOMParser().parseFromString(a, p);
          } catch (r) {
            n = r;
          }
          if (o || "html" !== t || ((o = e || (e = function () {
            if (document.implementation && document.implementation.createHTMLDocument) return document.implementation.createHTMLDocument("");
            var r = document.createElement("iframe");
            return r.style.cssText = "position:absolute; left:0; top:-999em; width:1px; height:1px; overflow:hidden;", r.setAttribute("sandbox", "allow-forms"), document.body.appendChild(r), r.contentWindow.document;
          }())).open(), o.write(a), o.close()), o) {
            var l = o.getElementsByTagName(i)[0],
              s = l.firstChild;
            return r && !s && (l.error = "Document parse failed."), s && "parsererror" === String(s.nodeName).toLowerCase() && (s.removeChild(s.firstChild), s.removeChild(s.lastChild), l.error = s.textContent || s.nodeValue || n || "Unknown error", l.removeChild(s)), l;
          }
        }(r, t);
        if (u && u.error) throw new Error(u.error);
        var c = u && u.body || u;
        l.map = i || p;
        var m = c && function (r, t, e, a) {
          return n.visitor = t, n.h = e, n.options = a || o, n(r);
        }(c, l, a, s);
        return l.map = null, m && m.props && m.props.children || null;
      }(c, u, C, this.map, g);
    } catch (r) {
      f ? f({
        error: r
      }) : "undefined" != typeof console && console.error && console.error("preact-markup: " + r);
    }
    if (false === i) return s || null;
    var x = w.hasOwnProperty("className") ? "className" : "class",
      b = w[x];
    return b ? b.splice ? b.splice(0, 0, "markup") : "string" == typeof b ? w[x] += " markup" : "object" == typeof b && (b.markup = true) : w[x] = "markup", C("div", w, s || null);
  }, i;
})(Component);

/**
 * Copied from existing form-js#Sanitizer
 * cf. https://github.com/bpmn-io/form-js/blob/master/packages/form-js-viewer/src/render/components/Sanitizer.js
 */

const NODE_TYPE_TEXT = 3,
  NODE_TYPE_ELEMENT = 1;
const ALLOWED_NODES = ['h1', 'h2', 'h3', 'h4', 'h5', 'span', 'em', 'a', 'p', 'div', 'ul', 'ol', 'li', 'hr', 'blockquote', 'img', 'pre', 'code', 'br', 'strong'];
const ALLOWED_ATTRIBUTES = ['align', 'alt', 'class', 'href', 'id', 'name', 'rel', 'target', 'src'];
const ALLOWED_URI_PATTERN = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g; // eslint-disable-line no-control-regex

const FORM_ELEMENT = document.createElement('form');

/**
 * Sanitize a HTML string and return the cleaned, safe version.
 *
 * @param {string} html
 * @return {string}
 */
function sanitizeHTML(html) {
  const doc = new DOMParser().parseFromString(`<!DOCTYPE html>\n<html><body><div>${html}`, 'text/html');
  doc.normalize();
  const element = doc.body.firstChild;
  if (element) {
    sanitizeNode(/** @type Element */element);
    return new XMLSerializer().serializeToString(element);
  } else {
    // handle the case that document parsing
    // does not work at all, due to HTML gibberish
    return '';
  }
}

/**
 * Recursively sanitize a HTML node, potentially
 * removing it, its children or attributes.
 *
 * Inspired by https://github.com/developit/snarkdown/issues/70
 * and https://github.com/cure53/DOMPurify. Simplified
 * for our use-case.
 *
 * @param {Element} node
 */
function sanitizeNode(node) {
  // allow text nodes
  if (node.nodeType === NODE_TYPE_TEXT) {
    return;
  }

  // disallow all other nodes but Element
  if (node.nodeType !== NODE_TYPE_ELEMENT) {
    return node.remove();
  }
  const lcTag = node.tagName.toLowerCase();

  // disallow non-whitelisted tags
  if (!ALLOWED_NODES.includes(lcTag)) {
    return node.remove();
  }
  const attributes = node.attributes;

  // clean attributes
  for (let i = attributes.length; i--;) {
    const attribute = attributes[i];
    const name = attribute.name;
    const lcName = name.toLowerCase();

    // normalize node value
    const value = attribute.value.trim();
    node.removeAttribute(name);
    const valid = isValidAttribute(lcTag, lcName, value);
    if (valid) {
      node.setAttribute(name, value);
    }
  }

  // force noopener on target="_blank" links
  if (lcTag === 'a' && node.getAttribute('target') === '_blank' && node.getAttribute('rel') !== 'noopener') {
    node.setAttribute('rel', 'noopener');
  }
  for (let i = node.childNodes.length; i--;) {
    sanitizeNode(/** @type Element */node.childNodes[i]);
  }
}

/**
 * Validates attributes for validity.
 *
 * @param {string} lcTag
 * @param {string} lcName
 * @param {string} value
 * @return {boolean}
 */
function isValidAttribute(lcTag, lcName, value) {
  // disallow most attributes based on whitelist
  if (!ALLOWED_ATTRIBUTES.includes(lcName)) {
    return false;
  }

  // disallow "DOM clobbering" / polution of document and wrapping form elements
  if ((lcName === 'id' || lcName === 'name') && (value in document || value in FORM_ELEMENT)) {
    return false;
  }
  if (lcName === 'target' && value !== '_blank') {
    return false;
  }

  // allow valid url links only
  if (lcName === 'href' && !ALLOWED_URI_PATTERN.test(value.replace(ATTR_WHITESPACE_PATTERN, ''))) {
    return false;
  }
  return true;
}

function PropertyTooltip(props) {
  const {
    tooltip
  } = props;
  return tooltip && jsx(Markup, {
    markup: sanitizeHTML(tooltip),
    trim: false
  });
}

function usePropertyAccessors(bpmnFactory, commandStack, element, property) {
  const directSet = useCallback(propertySetter$1(bpmnFactory, commandStack, element, property), [bpmnFactory, commandStack, element, property]);
  const directGet = useCallback(propertyGetter$1(element, property), [element, property]);
  const [isFeelEnabled, setIsFeelEnabled] = useState(feelEnabled(property, directGet()));
  const handleFeelToggle = useCallback(value => {
    if (!isFeelEnabled && typeof value === 'string' && value.startsWith('=')) {
      setIsFeelEnabled(true);
    }
    if (isFeelEnabled && (typeof value !== 'string' || !value.startsWith('='))) {
      setIsFeelEnabled(false);
    }
  }, [isFeelEnabled]);
  const set = useCallback((value, error) => {
    handleFeelToggle(value);
    directSet(toFeelExpression(value, property.type));
  }, [directSet, property, handleFeelToggle]);
  const get = useCallback(() => {
    if (isFeelEnabled) {
      return directGet();
    }
    return fromFeelExpression(directGet(), property.type);
  }, [directGet, property, isFeelEnabled]);
  if (!shouldCastToFeel(property)) {
    return [directGet, directSet];
  }
  return [get, set];
}
const fromFeelExpression = (value, type) => {
  if (typeof value === 'undefined') {
    return value;
  }
  if (typeof value === 'string' && value.startsWith('=')) {
    value = value.slice(1);
  }
  if (type === 'Number') {
    return Number(value);
  }
  if (type === 'Boolean') {
    return value !== 'false';
  }
  return value;
};
const feelEnabled = (property, value) => {
  if (!shouldCastToFeel(property)) {
    return true;
  }
  if (property.type === 'Boolean') {
    return !(value === '=true' || value === '=false');
  }
  if (property.type === 'Number') {
    return isNaN(fromFeelExpression(value, property.type));
  }
  return true;
};
function propertyGetter$1(element, property) {
  return function getValue() {
    return getPropertyValue(element, property);
  };
}
function propertySetter$1(bpmnFactory, commandStack, element, property) {
  return function setValue(value) {
    return setPropertyValue(bpmnFactory, commandStack, element, property, value);
  };
}
function propertyValidator$1(translate, property) {
  return value => validateProperty(value, property, translate);
}
function groupByGroupId$1(properties) {
  return groupBy(properties, 'group');
}
function findCustomGroup$1(groups, id) {
  return find(groups, g => g.id === id);
}

/**
 * Is the given property executed by the engine?
 *
 * @param { { binding: { type: string } } } property
 * @return {boolean}
 */
function isExternalProperty(property) {
  return ['zeebe:property', 'zeebe:taskHeader'].includes(property.binding.type);
}

function PropertyDescription(props) {
  const {
    description
  } = props;
  const translate = useService('translate');
  return description && jsx(Markup, {
    markup: sanitizeHTML(translate(description)),
    trim: false
  });
}

function TextAreaProperty$1(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    feel,
    language,
    placeholder,
    tooltip
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  return TextAreaEntry({
    debounce,
    element,
    id,
    label,
    feel,
    placeholder,
    monospace: !!language,
    autoResize: true,
    description: PropertyDescription({
      description
    }),
    getValue: propertyGetter$1(element, property),
    setValue: propertySetter$1(bpmnFactory, commandStack, element, property),
    validate: propertyValidator$1(translate, property),
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function StringProperty$1(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    feel,
    placeholder,
    tooltip
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  return TextFieldEntry({
    debounce,
    element,
    getValue: propertyGetter$1(element, property),
    id,
    label,
    feel,
    placeholder,
    description: PropertyDescription({
      description
    }),
    setValue: propertySetter$1(bpmnFactory, commandStack, element, property),
    validate: propertyValidator$1(translate, property),
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function useServiceIfAvailable(service, fallback) {
  const resolved = useService(service, false);
  if (!resolved) {
    return fallback;
  }
  return resolved;
}
function withVariableContext(Component) {
  return props => {
    const {
      bpmnElement,
      element
    } = props;
    const bo = (bpmnElement || element).businessObject;
    const [variables, setVariables] = useState([]);
    const eventBus = useService('eventBus');
    const variableResolver = useServiceIfAvailable('variableResolver', {
      getVariablesForElement
    });
    useEffect(() => {
      const extractVariables = async () => {
        const variables = await variableResolver.getVariablesForElement(bo);
        setVariables(variables.map(variable => {
          return {
            ...variable,
            info: variable.info || variable.origin && 'Written in ' + variable.origin.map(origin => origin.name || origin.id).join(', ')
          };
        }));
      };

      // The callback must return undefined, so the event propagation is not canceled.
      // Cf. https://github.com/camunda/camunda-modeler/issues/3392
      const callback = () => {
        extractVariables();
      };
      eventBus.on('commandStack.changed', callback);
      callback();
      return () => {
        eventBus.off('commandStack.changed', callback);
      };
    }, [bo]);
    return jsx(Component, {
      ...props,
      variables: variables
    });
  };
}

function withTooltipContainer(Component) {
  return props => {
    const tooltipContainer = useMemo(() => {
      const config = useService('config');
      return config && config.propertiesPanel && config.propertiesPanel.feelTooltipContainer;
    }, []);
    return jsx(Component, {
      ...props,
      tooltipContainer: tooltipContainer
    });
  };
}

const FeelEntry = withTooltipContainer(FeelEntry$1);
const FeelTextAreaEntry = withTooltipContainer(FeelTextAreaEntry$1);
const FeelEntryWithVariableContext = withVariableContext(FeelEntry);
const FeelTextAreaEntryWithVariableContext = withVariableContext(FeelTextAreaEntry);

function FeelProperty(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    feel,
    placeholder,
    tooltip
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  const TextFieldComponent = !isExternalProperty(property) ? FeelEntryWithVariableContext : FeelEntry;
  return TextFieldComponent({
    debounce,
    element,
    getValue: propertyGetter$1(element, property),
    id,
    label,
    feel,
    placeholder,
    description: PropertyDescription({
      description
    }),
    setValue: propertySetter$1(bpmnFactory, commandStack, element, property),
    validate: propertyValidator$1(translate, property),
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function FeelTextAreaProperty(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    feel,
    placeholder,
    tooltip
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  const TextAreaComponent = !isExternalProperty(property) ? FeelTextAreaEntryWithVariableContext : FeelTextAreaEntry;
  return TextAreaComponent({
    debounce,
    element,
    getValue: propertyGetter$1(element, property),
    id,
    label,
    feel,
    placeholder,
    description: PropertyDescription({
      description
    }),
    setValue: propertySetter$1(bpmnFactory, commandStack, element, property),
    validate: propertyValidator$1(translate, property),
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function DropdownProperty$1(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    tooltip
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    translate = useService('translate');
  const getOptions = () => {
    const {
      choices,
      optional
    } = property;
    let dropdownOptions = [];
    dropdownOptions = choices.map(({
      name,
      value
    }) => {
      return {
        label: name,
        value
      };
    });
    if (optional) {
      dropdownOptions = [{
        label: '',
        value: undefined
      }, ...dropdownOptions];
    }
    return dropdownOptions;
  };
  return SelectEntry({
    element,
    id,
    label,
    getOptions,
    description: PropertyDescription({
      description
    }),
    getValue: propertyGetter$1(element, property),
    setValue: propertySetter$1(bpmnFactory, commandStack, element, property),
    validate: propertyValidator$1(translate, property),
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function BooleanProperty$1(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    tooltip,
    feel
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  const Component = feel === 'optional' ? FeelCheckboxEntry : CheckboxEntry;
  const [getValue, setValue] = usePropertyAccessors(bpmnFactory, commandStack, element, property);
  return Component({
    element,
    debounce,
    translate,
    getValue,
    id,
    label,
    description: PropertyDescription({
      description
    }),
    setValue,
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function NumberProperty(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    description,
    editable,
    label,
    feel,
    tooltip
  } = property;
  const Component = feel === 'optional' ? FeelNumberEntry : NumberFieldEntry;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  const [getValue, setValue] = usePropertyAccessors(bpmnFactory, commandStack, element, property);
  const validate = useCallback(value => {
    if (shouldCastToFeel(property) && isNumber(value) && value.toString().includes('e')) {
      return translate('Scientific notation is disallowed in FEEL.');
    }
    const defaultValidator = propertyValidator$1(translate, property);
    return defaultValidator(value);
  }, [translate, property]);
  return Component({
    debounce,
    element,
    getValue,
    step: 'any',
    id,
    label,
    description: PropertyDescription({
      description
    }),
    setValue,
    validate: validate,
    disabled: editable === false,
    tooltip: PropertyTooltip({
      tooltip
    })
  });
}

function CustomProperties$1(props) {
  const {
    element,
    elementTemplate,
    injector
  } = props;
  const translate = injector.get('translate');
  const groups = [];
  const {
    id,
    properties,
    groups: propertyGroups
  } = elementTemplate;

  // (1) group properties by group id
  const groupedProperties = groupByGroupId$1(properties);
  const defaultProps = [];
  forEach(groupedProperties, (properties, groupId) => {
    const group = findCustomGroup$1(propertyGroups, groupId);
    if (!group) {
      return defaultProps.push(...properties);
    }
    addCustomGroup$1(groups, {
      element,
      id: `ElementTemplates__CustomProperties-${groupId}`,
      label: translate(group.label),
      openByDefault: group.openByDefault,
      properties: properties,
      templateId: `${id}-${groupId}`,
      tooltip: PropertyTooltip({
        tooltip: group.tooltip
      })
    });
  });

  // (2) add default custom props
  if (defaultProps.length) {
    addCustomGroup$1(groups, {
      id: 'ElementTemplates__CustomProperties',
      label: translate('Custom properties'),
      element,
      properties: defaultProps,
      templateId: id
    });
  }
  return groups;
}
function addCustomGroup$1(groups, props) {
  const {
    element,
    id,
    label,
    openByDefault = false,
    properties,
    templateId,
    tooltip
  } = props;
  const customPropertiesGroup = {
    id,
    label,
    component: Group,
    entries: [],
    shouldOpen: openByDefault,
    tooltip
  };
  properties.forEach((property, index) => {
    const entry = createCustomEntry$1(`custom-entry-${templateId}-${index}`, element, property);
    if (entry) {
      customPropertiesGroup.entries.push(entry);
    }
  });
  if (customPropertiesGroup.entries.length) {
    groups.push(customPropertiesGroup);
  }
}
function createCustomEntry$1(id, element, property) {
  let {
    type,
    feel
  } = property;
  if (!type) {
    type = getDefaultType$1(property);
  }
  if (feel === 'required') {
    return {
      id,
      component: FeelProperty,
      isEdited: createIsEdited(isFeelEntryEdited, element, property),
      property
    };
  }
  if (type === 'Number') {
    return {
      id,
      component: NumberProperty,
      isEdited: createIsEdited(isNumberFieldEntryEdited, element, property),
      property
    };
  }
  if (type === 'Boolean') {
    return {
      id,
      component: BooleanProperty$1,
      isEdited: createIsEdited(isCheckboxEntryEdited, element, property),
      property
    };
  }
  if (type === 'Dropdown') {
    return {
      id,
      component: DropdownProperty$1,
      isEdited: createIsEdited(isSelectEntryEdited, element, property),
      property
    };
  }
  if (type === 'String') {
    if (feel) {
      return {
        id,
        component: FeelProperty,
        isEdited: createIsEdited(isFeelEntryEdited, element, property),
        property
      };
    }
    return {
      id,
      component: StringProperty$1,
      isEdited: createIsEdited(isTextFieldEntryEdited, element, property),
      property
    };
  }
  if (type === 'Text') {
    if (feel) {
      return {
        id,
        component: FeelTextAreaProperty,
        isEdited: createIsEdited(isFeelEntryEdited, element, property),
        property
      };
    }
    return {
      id,
      component: TextAreaProperty$1,
      isEdited: createIsEdited(isTextAreaEntryEdited, element, property),
      property
    };
  }
}
function getDefaultType$1(property) {
  const {
    binding
  } = property;
  const {
    type
  } = binding;
  if ([PROPERTY_TYPE$1, ZEEBE_TASK_DEFINITION_TYPE_TYPE, ZEEBE_TASK_DEFINITION, ZEBBE_INPUT_TYPE, ZEEBE_OUTPUT_TYPE, ZEEBE_PROPERTY_TYPE, ZEEBE_TASK_HEADER_TYPE].includes(type)) {
    return 'String';
  }
}
function createIsEdited(isEditedEntry, element, property) {
  if (property.generatedValue) return () => true;
  const actualValue = getPropertyValue(element, property);
  const defaultValue = getDefaultValue(property);
  return !isEmpty(defaultValue) ? () => actualValue !== defaultValue : isEditedEntry;
}

/**
 * Empty string is considered empty for input/output support.
 */
function isEmpty(value) {
  return value === undefined || value === '';
}

function ReferenceSelectEntry(props) {
  const {
    autoFocusEntry,
    element,
    getOptions
  } = props;
  const options = getOptions(element);
  const prevOptions = usePrevious(options);

  // auto focus specifc other entry when options changed
  useEffect(() => {
    if (autoFocusEntry && prevOptions && options.length > prevOptions.length) {
      const entry = query(`[data-entry-id="${autoFocusEntry}"]`);
      const focusableInput = query('.bio-properties-panel-input', entry);
      if (focusableInput) {
        focusableInput.select();
      }
    }
  }, [options]);
  return jsx(SelectEntry, {
    ...props
  });
}

function getEventDefinition(element, eventType) {
  const businessObject = getBusinessObject(element);
  const eventDefinitions = businessObject.get('eventDefinitions') || [];
  return find(eventDefinitions, function (definition) {
    return is(definition, eventType);
  });
}
function isMessageSupported(element) {
  return is(element, 'bpmn:ReceiveTask') || isAny(element, ['bpmn:StartEvent', 'bpmn:EndEvent', 'bpmn:IntermediateThrowEvent', 'bpmn:BoundaryEvent', 'bpmn:IntermediateCatchEvent']) && !!getMessageEventDefinition(element);
}
function getMessageEventDefinition(element) {
  if (is(element, 'bpmn:ReceiveTask')) {
    return getBusinessObject(element);
  }
  return getEventDefinition(element, 'bpmn:MessageEventDefinition');
}
function getMessage(element) {
  const messageEventDefinition = getMessageEventDefinition(element);
  return messageEventDefinition && messageEventDefinition.get('messageRef');
}
function getSignalEventDefinition(element) {
  return getEventDefinition(element, 'bpmn:SignalEventDefinition');
}
function isSignalSupported(element) {
  return is(element, 'bpmn:Event') && !!getSignalEventDefinition(element);
}
function getSignal(element) {
  const signalEventDefinition = getSignalEventDefinition(element);
  return signalEventDefinition && signalEventDefinition.get('signalRef');
}

const EMPTY_OPTION$1 = '';
const CREATE_NEW_OPTION$1 = 'create-new';

/**
 * @typedef { import('@bpmn-io/properties-panel').EntryDefinition } Entry
 */

/**
 * @returns {Array<Entry>} entries
 */
function MessageProps(props) {
  const {
    element
  } = props;
  if (!isMessageSupported(element)) {
    return [];
  }
  return [{
    id: 'messageRef',
    component: MessageRef,
    isEdited: isSelectEntryEdited
  }];
}
function MessageRef(props) {
  const {
    element
  } = props;
  const bpmnFactory = useService('bpmnFactory');
  const modeling = useService('modeling');
  const translate = useService('translate');
  const messageEventDefinition = getMessageEventDefinition(element);
  const getValue = () => {
    const message = getMessage(element);
    if (message) {
      return message.get('id');
    }
    return EMPTY_OPTION$1;
  };
  const setValue = value => {
    const root = getRoot(messageEventDefinition);
    let message;

    // (1) create new message
    if (value === CREATE_NEW_OPTION$1) {
      const id = nextId('Message_');
      message = createElement('bpmn:Message', {
        id,
        name: id
      }, root, bpmnFactory);
      value = message.get('id');
    }

    // (2) update (or remove) messageRef
    message = findRootElementById(messageEventDefinition, 'bpmn:Message', value) || message;

    // (3) commit all updates
    return modeling.updateModdleProperties(element, messageEventDefinition, {
      messageRef: message
    });
  };
  const getOptions = () => {
    let options = [{
      value: EMPTY_OPTION$1,
      label: translate('<none>')
    }, {
      value: CREATE_NEW_OPTION$1,
      label: translate('Create new ...')
    }];
    const messages = findRootElementsByType(getBusinessObject(element), 'bpmn:Message');
    const filteredMessages = withoutTemplatedMessages(messages);
    sortByName$1(filteredMessages).forEach(message => {
      options.push({
        value: message.get('id'),
        label: message.get('name')
      });
    });
    return options;
  };
  return ReferenceSelectEntry({
    element,
    id: 'messageRef',
    label: translate('Global message reference'),
    autoFocusEntry: 'messageName',
    getValue,
    setValue,
    getOptions
  });
}
function withoutTemplatedMessages(messages) {
  return messages.filter(message => !message.get('zeebe:modelerTemplate'));
}

// helper /////////////////////////

function sortByName$1(elements) {
  return sortBy(elements, e => (e.name || '').toLowerCase());
}

const EMPTY_OPTION = '';
const CREATE_NEW_OPTION = 'create-new';

/**
 * @typedef { import('@bpmn-io/properties-panel').EntryDefinition } Entry
 */

/**
 * @returns {Array<Entry>} entries
 */
function SignalProps(props) {
  const {
    element
  } = props;
  if (!isSignalSupported(element)) {
    return [];
  }
  return [{
    id: 'signalRef',
    component: SignalRef,
    isEdited: isSelectEntryEdited
  }];
}
function SignalRef(props) {
  const {
    element
  } = props;
  const bpmnFactory = useService('bpmnFactory');
  const modeling = useService('modeling');
  const translate = useService('translate');
  const signalEventDefinition = getSignalEventDefinition(element);
  const getValue = () => {
    const signal = getSignal(element);
    if (signal) {
      return signal.get('id');
    }
    return EMPTY_OPTION;
  };
  const setValue = value => {
    const root = getRoot(signalEventDefinition);
    let signal;

    // (1) create new signal
    if (value === CREATE_NEW_OPTION) {
      const id = nextId('Signal_');
      signal = createElement('bpmn:Signal', {
        id,
        name: id
      }, root, bpmnFactory);
      value = signal.get('id');
    }

    // (2) update (or remove) signalRef
    signal = findRootElementById(signalEventDefinition, 'bpmn:Signal', value) || signal;

    // (3) commit all updates
    return modeling.updateModdleProperties(element, signalEventDefinition, {
      signalRef: signal
    });
  };
  const getOptions = () => {
    let options = [{
      value: EMPTY_OPTION,
      label: translate('<none>')
    }, {
      value: CREATE_NEW_OPTION,
      label: translate('Create new ...')
    }];
    const signals = findRootElementsByType(getBusinessObject(element), 'bpmn:Signal');
    const filteredSignals = withoutTemplatedSignals(signals);
    sortByName(filteredSignals).forEach(signal => {
      options.push({
        value: signal.get('id'),
        label: signal.get('name')
      });
    });
    return options;
  };
  return ReferenceSelectEntry({
    element,
    id: 'signalRef',
    label: translate('Global signal reference'),
    autoFocusEntry: 'signalName',
    getValue,
    setValue,
    getOptions
  });
}
function withoutTemplatedSignals(signals) {
  return signals.filter(signal => !signal.get('zeebe:modelerTemplate'));
}

// helper /////////////////////////

function sortByName(elements) {
  return sortBy(elements, e => (e.name || '').toLowerCase());
}

const LOWER_PRIORITY$1 = 300;
const ALWAYS_VISIBLE_GROUPS = ['general', 'documentation', 'multiInstance', 'Zeebe__ExecutionListeners'];
let ElementTemplatesPropertiesProvider$1 = class ElementTemplatesPropertiesProvider {
  constructor(elementTemplates, propertiesPanel, injector) {
    propertiesPanel.registerProvider(LOWER_PRIORITY$1, this);
    this._elementTemplates = elementTemplates;
    this._injector = injector;
  }
  getGroups(element) {
    return groups => {
      const injector = this._injector;
      updateMessageGroup(groups, element);
      updateSignalGroup(groups, element);
      if (!this._shouldShowTemplateProperties(element)) {
        return groups;
      }
      const translate = injector.get('translate');

      // (0) Copy provided groups
      groups = groups.slice();
      const templatesGroup = {
        element,
        id: 'ElementTemplates__Template',
        label: translate('Template'),
        component: createElementTemplatesGroup({
          getTemplateId: getTemplateId$1
        }),
        entries: TemplateProps({
          element,
          elementTemplates: this._elementTemplates,
          getTemplateId: getTemplateId$1,
          getTemplateVersion: getTemplateVersion$1
        })
      };

      // (1) Add templates group
      addGroupsAfter$1('documentation', groups, [templatesGroup]);
      let elementTemplate = this._elementTemplates.get(element);
      if (elementTemplate) {
        elementTemplate = applyConditions(element, elementTemplate);
        const templateSpecificGroups = [].concat(CustomProperties$1({
          element,
          elementTemplate,
          injector
        }));

        // (2) add template-specific properties groups
        addGroupsAfter$1('ElementTemplates__Template', groups, templateSpecificGroups);
      }

      // (3) apply entries visible
      if (getTemplateId$1(element)) {
        groups = getVisibleGroups(elementTemplate || {}, groups);
      }
      return groups;
    };
  }
  _shouldShowTemplateProperties(element) {
    return getTemplateId$1(element) || this._elementTemplates.getAll(element).length;
  }
};
ElementTemplatesPropertiesProvider$1.$inject = ['elementTemplates', 'propertiesPanel', 'injector'];

// helper /////////////////////

function updateMessageGroup(groups, element) {
  const messageGroup = findGroup(groups, 'message');
  if (!messageGroup) {
    return;
  }
  messageGroup.entries = overrideGenericEntries(messageGroup.entries, MessageProps({
    element
  }));
}
function updateSignalGroup(groups, element) {
  const signalGroup = findGroup(groups, 'signal');
  if (!signalGroup) {
    return;
  }
  signalGroup.entries = overrideGenericEntries(signalGroup.entries, SignalProps({
    element
  }));
}
function findGroup(groups, id) {
  return groups.find(g => g.id === id);
}
function overrideGenericEntries(oldEntries, newEntries) {
  return oldEntries.map(oldEntry => newEntries.find(newEntry => newEntry.id === oldEntry.id) || oldEntry);
}

/**
 *
 * @param {string|string[]} idOrIds
 * @param {Array<{ id: string }} groups
 * @param {Array<{ id: string }>} groupsToAdd
 */
function addGroupsAfter$1(idOrIds, groups, groupsToAdd) {
  let ids = idOrIds;
  if (!Array.isArray(idOrIds)) {
    ids = [idOrIds];
  }

  // find index of last group with provided id
  const index = groups.reduce((acc, group, index) => {
    return ids.includes(group.id) ? index : acc;
  }, -1);
  if (index !== -1) {
    groups.splice(index + 1, 0, ...groupsToAdd);
  } else {
    // add in the beginning if group with provided id is missing
    groups.unshift(...groupsToAdd);
  }
}
function getVisibleGroups(template, groups) {
  if (!template.entriesVisible) {
    return groups.filter(group => {
      return ALWAYS_VISIBLE_GROUPS.includes(group.id) || group.id.startsWith('ElementTemplates__');
    });
  }
  return groups;
}

var propertiesProviderModule$1 = {
  __depends__: [translateModule],
  __init__: ['elementTemplatesPropertiesProvider'],
  elementTemplatesPropertiesProvider: ['type', ElementTemplatesPropertiesProvider$1]
};

var index$1 = {
  __depends__: [coreModule$1, propertiesProviderModule$1]
};

/**
 * Converts legacy scopes descriptor to newer supported array structure.
 *
 * For example, it transforms
 *
 * scopes: {
 *   'camunda:Connector':
 *     { properties: []
 *   }
 * }
 *
 * to
 *
 * scopes: [
 *   {
 *     type: 'camunda:Connector',
 *     properties: []
 *   }
 * ]
 *
 * @param {ScopesDescriptor} scopes
 *
 * @returns {Array}
 */
function handleLegacyScopes(scopes = []) {
  const scopesAsArray = [];
  if (!isObject(scopes)) {
    return scopes;
  }
  forEach(keys(scopes), function (scopeName) {
    scopesAsArray.push(assign({
      type: scopeName
    }, scopes[scopeName]));
  });
  return scopesAsArray;
}

/**
 * Create an input parameter representing the given
 * binding and value.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createInputParameter(binding, value, bpmnFactory) {
  const {
    name,
    scriptFormat
  } = binding;
  let parameterValue, parameterDefinition;
  if (scriptFormat) {
    parameterDefinition = bpmnFactory.create('camunda:Script', {
      scriptFormat,
      value
    });
  } else {
    parameterValue = value;
  }
  return bpmnFactory.create('camunda:InputParameter', {
    name,
    value: parameterValue,
    definition: parameterDefinition
  });
}

/**
 * Create an output parameter representing the given
 * binding and value.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createOutputParameter(binding, value, bpmnFactory) {
  const {
    scriptFormat,
    source
  } = binding;
  let parameterValue, parameterDefinition;
  if (scriptFormat) {
    parameterDefinition = bpmnFactory.create('camunda:Script', {
      scriptFormat,
      value: source
    });
  } else {
    parameterValue = source;
  }
  return bpmnFactory.create('camunda:OutputParameter', {
    name: value,
    value: parameterValue,
    definition: parameterDefinition
  });
}

/**
 * Create camunda property from the given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaProperty(binding, value = '', bpmnFactory) {
  const {
    name
  } = binding;
  return bpmnFactory.create('camunda:Property', {
    name,
    value
  });
}

/**
 * Create camunda:in element from given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaIn(binding, value, bpmnFactory) {
  const attrs = createCamundaInOutAttrs(binding, value);
  return bpmnFactory.create('camunda:In', attrs);
}

/**
 * Create camunda:in with businessKey element from given binding.
 *
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaInWithBusinessKey(value, bpmnFactory) {
  return bpmnFactory.create('camunda:In', {
    businessKey: value
  });
}

/**
 * Create camunda:out element from given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaOut(binding, value, bpmnFactory) {
  const attrs = createCamundaInOutAttrs(binding, value);
  return bpmnFactory.create('camunda:Out', attrs);
}

/**
 * Create camunda:executionListener element containing an inline script from given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaExecutionListener(binding, value, bpmnFactory) {
  const {
    event,
    implementationType,
    scriptFormat
  } = binding;

  // To guarantee backwards compatibility scriptFormat is taken into account and has precedence before any other type
  if (implementationType === 'script' || scriptFormat) {
    return bpmnFactory.create('camunda:ExecutionListener', {
      event,
      script: bpmnFactory.create('camunda:Script', {
        scriptFormat,
        value
      })
    });
  }
  return bpmnFactory.create('camunda:ExecutionListener', {
    event,
    [implementationType]: value
  });
}

/**
 * Create camunda:field element containing string or expression from given binding.
 *
 * @param {PropertyBinding} binding
 * @param {String} value
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaFieldInjection(binding, value, bpmnFactory) {
  const DEFAULT_PROPS = {
    'string': undefined,
    'expression': undefined,
    'name': undefined
  };
  const props = assign({}, DEFAULT_PROPS);
  const {
    expression,
    name
  } = binding;
  if (!expression) {
    props.string = value;
  } else {
    props.expression = value;
  }
  props.name = name;
  return bpmnFactory.create('camunda:Field', props);
}

/**
 * Create camunda:errorEventDefinition element containing expression and errorRef
 * from given binding.
 *
 * @param {String} expression
 * @param {ModdleElement} errorRef
 * @param {ModdleElement} parent
 * @param {BpmnFactory} bpmnFactory
 *
 * @return {ModdleElement}
 */
function createCamundaErrorEventDefinition(expression, errorRef, parent, bpmnFactory) {
  const errorEventDefinition = bpmnFactory.create('camunda:ErrorEventDefinition', {
    errorRef,
    expression
  });
  errorEventDefinition.$parent = parent;
  return errorEventDefinition;
}

/**
 * Create bpmn:error element containing a specific error id given by a binding.
 *
 * @param {String} bindingErrorRef
 * @param {ModdleElement} parent
 * @param {BpmnFactory} bpmnFactory
 *
 * @return { ModdleElement }
 */
function createError(bindingErrorRef, parent, bpmnFactory) {
  const error = bpmnFactory.create('bpmn:Error', {
    // we need to later retrieve the error from a binding
    id: nextId('Error_' + bindingErrorRef + '_')
  });
  error.$parent = parent;
  return error;
}

// helpers //////////

/**
 * Create properties for camunda:in and camunda:out types.
 */
function createCamundaInOutAttrs(binding, value) {
  const properties = {};
  const {
    expression,
    source,
    sourceExpression,
    target,
    type,
    variables
  } = binding;

  // explicitly cover all conditions as specified here:
  // https://github.com/camunda/camunda-modeler/blob/develop/docs/element-templates/README.md#camundain
  if (type === 'camunda:in') {
    if (target && !expression && !variables) {
      properties.target = target;
      properties.source = value;
    } else if (target && expression === true && !variables) {
      properties.target = target;
      properties.sourceExpression = value;
    } else if (!target && !expression && variables === 'local') {
      properties.local = true;
      properties.variables = 'all';
    } else if (target && !expression && variables === 'local') {
      properties.local = true;
      properties.source = value;
      properties.target = target;
    } else if (target && expression && variables === 'local') {
      properties.local = true;
      properties.sourceExpression = value;
      properties.target = target;
    } else if (!target && !expression && variables === 'all') {
      properties.variables = 'all';
    } else {
      throw new Error('invalid configuration for camunda:in element template binding');
    }
  }

  // explicitly cover all conditions as specified here:
  // https://github.com/camunda/camunda-modeler/blob/develop/docs/element-templates/README.md#camundaout
  if (type === 'camunda:out') {
    if (source && !sourceExpression && !variables) {
      properties.target = value;
      properties.source = source;
    } else if (!source && sourceExpression && !variables) {
      properties.target = value;
      properties.sourceExpression = sourceExpression;
    } else if (!source && !sourceExpression && variables === 'all') {
      properties.variables = 'all';
    } else if (source && !sourceExpression && variables === 'local') {
      properties.local = true;
      properties.source = source;
      properties.target = value;
    } else if (!source && sourceExpression && variables === 'local') {
      properties.local = true;
      properties.sourceExpression = sourceExpression;
      properties.target = value;
    } else if (!source && !sourceExpression && variables === 'local') {
      properties.local = true;
      properties.variables = 'all';
    } else {
      throw new Error('invalid configuration for camunda:out element template binding');
    }
  }
  return properties;
}

const CAMUNDA_SERVICE_TASK_LIKE = ['camunda:class', 'camunda:delegateExpression', 'camunda:expression'];

/**
 * Applies an element template to an element. Sets `camunda:modelerTemplate` and
 * `camunda:modelerTemplateVersion`.
 */
class ChangeElementTemplateHandler {
  constructor(bpmnFactory, bpmnReplace, commandStack, modeling) {
    this._bpmnFactory = bpmnFactory;
    this._bpmnReplace = bpmnReplace;
    this._commandStack = commandStack;
    this._modeling = modeling;
  }

  /**
   * Change an element's template and update its properties as specified in `newTemplate`. Specify
   * `oldTemplate` to update from one template to another. If `newTemplate` isn't specified the
   * `camunda:modelerTemplate` and `camunda:modelerTemplateVersion` properties will be removed from
   * the element.
   *
   * @param {Object} context
   * @param {Object} context.element
   * @param {Object} [context.oldTemplate]
   * @param {Object} [context.newTemplate]
   */
  preExecute(context) {
    const newTemplate = context.newTemplate,
      oldTemplate = context.oldTemplate;
    let element = context.element;

    // update camunda:modelerTemplate attribute
    this._updateCamundaModelerTemplate(element, newTemplate);
    if (newTemplate) {
      element = context.element = this._updateTaskType(element, newTemplate);

      // update properties
      this._updateProperties(element, oldTemplate, newTemplate);

      // update camunda:ExecutionListener properties
      this._updateCamundaExecutionListenerProperties(element, newTemplate);

      // update camunda:Field properties
      this._updateCamundaFieldProperties(element, oldTemplate, newTemplate);

      // update camunda:In and camunda:Out properties
      this._updateCamundaInOutProperties(element, oldTemplate, newTemplate);

      // update camunda:InputParameter and camunda:OutputParameter properties
      this._updateCamundaInputOutputParameterProperties(element, oldTemplate, newTemplate);

      // update camunda:Property properties
      this._updateCamundaPropertyProperties(element, oldTemplate, newTemplate);

      // update camunda:ErrorEventDefinition properties
      this._updateCamundaErrorEventDefinitionProperties(element, oldTemplate, newTemplate);

      // update properties for each scope
      handleLegacyScopes(newTemplate.scopes).forEach(newScopeTemplate => {
        this._updateScopeProperties(element, oldTemplate, newScopeTemplate, newTemplate);
      });
    }
  }
  _getOrCreateExtensionElements(element) {
    const bpmnFactory = this._bpmnFactory,
      modeling = this._modeling;
    const businessObject = getBusinessObject(element);
    let extensionElements = businessObject.get('extensionElements');
    if (!extensionElements) {
      extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
        values: []
      });
      extensionElements.$parent = businessObject;
      modeling.updateProperties(element, {
        extensionElements: extensionElements
      });
    }
    return extensionElements;
  }

  /**
   * Update `camunda:ErrorEventDefinition` properties of specified business object. Event
   * definitions can only exist in `bpmn:ExtensionElements`.
   *
   * Ensures an bpmn:Error exists for the event definition.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateCamundaErrorEventDefinitionProperties(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:errorEventDefinition';
    });

    // (1) do not override if no updates
    if (!newProperties.length) {
      return;
    }
    const extensionElements = this._getOrCreateExtensionElements(element);
    const oldErrorEventDefinitions = findExtensions(element, ['camunda:ErrorEventDefinition']);
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        oldEventDefinition = oldProperty && findOldBusinessObject(extensionElements, oldProperty),
        newBinding = newProperty.binding;

      // (2) update old event definitions
      if (oldProperty && oldEventDefinition) {
        if (!propertyChanged(oldEventDefinition, oldProperty)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldEventDefinition,
            properties: {
              expression: newProperty.value
            }
          });
        }
        remove(oldErrorEventDefinitions, oldEventDefinition);
      }

      // (3) create new event definition + error
      else {
        const rootElement = getRoot(getBusinessObject(element)),
          newError = createError(newBinding.errorRef, rootElement, bpmnFactory),
          newEventDefinition = createCamundaErrorEventDefinition(newProperty.value, newError, extensionElements, bpmnFactory);
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: rootElement,
          properties: {
            rootElements: [...rootElement.get('rootElements'), newError]
          }
        });
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), newEventDefinition]
          }
        });
      }
    });

    // (4) remove old event definitions
    if (oldErrorEventDefinitions.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElements,
        properties: {
          values: without(extensionElements.get('values'), value => oldErrorEventDefinitions.includes(value))
        }
      });
    }
  }

  /**
   * Update `camunda:ExecutionListener` properties of specified business object. Execution listeners
   * will always be overridden. Execution listeners can only exist in `bpmn:ExtensionElements`.
   *
   * @param {djs.model.Base} element
   * @param {Object} newTemplate
   */
  _updateCamundaExecutionListenerProperties(element, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:executionListener';
    });

    // (1) do not override old execution listeners if no new execution listeners specified
    if (!newProperties.length) {
      return;
    }
    const extensionElements = this._getOrCreateExtensionElements(element);

    // (2) remove old execution listeners
    const oldExecutionListeners = findExtensions(element, ['camunda:ExecutionListener']);

    // (3) add new execution listeners
    const newExecutionListeners = newProperties.map(newProperty => {
      const newBinding = newProperty.binding,
        propertyValue = newProperty.value;
      return createCamundaExecutionListener(newBinding, propertyValue, bpmnFactory);
    });
    commandStack.execute('element.updateModdleProperties', {
      element,
      moddleElement: extensionElements,
      properties: {
        values: [...without(extensionElements.get('values'), value => oldExecutionListeners.includes(value)), ...newExecutionListeners]
      }
    });
  }

  /**
   * Update `camunda:Field` properties of specified business object.
   * If business object is `camunda:ExecutionListener` or `camunda:TaskListener` `fields` property
   * will be updated. Otherwise `extensionElements.values` property will be updated.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   * @param {ModdleElement} businessObject
   */
  _updateCamundaFieldProperties(element, oldTemplate, newTemplate, businessObject) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:field';
    });

    // (1) do not override old fields if no new fields specified
    if (!newProperties.length) {
      return;
    }
    if (!businessObject) {
      businessObject = this._getOrCreateExtensionElements(element);
    }
    const propertyName = isAny(businessObject, ['camunda:ExecutionListener', 'camunda:TaskListener']) ? 'fields' : 'values';
    const oldFields = findExtensions(element, ['camunda:Field']);
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        oldField = oldProperty && findOldBusinessObject(businessObject, oldProperty),
        newBinding = newProperty.binding;

      // (2) update old fields
      if (oldProperty && oldField) {
        if (!propertyChanged(oldField, oldProperty)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldField,
            properties: {
              string: newProperty.value
            }
          });
        }
        remove(oldFields, oldField);
      }

      // (3) add new fields
      else {
        const newCamundaFieldInjection = createCamundaFieldInjection(newBinding, newProperty.value, bpmnFactory);
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties: {
            [propertyName]: [...businessObject.get(propertyName), newCamundaFieldInjection]
          }
        });
      }
    });

    // (4) remove old fields
    if (oldFields.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          [propertyName]: without(businessObject.get(propertyName), value => oldFields.includes(value))
        }
      });
    }
  }

  /**
   * Update `camunda:In` and `camunda:Out` properties of specified business object. Only
   * `bpmn:CallActivity` and events with `bpmn:SignalEventDefinition` can have ins. Only
   * `camunda:CallActivity` can have outs.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateCamundaInOutProperties(element, oldTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:in' || newBindingType === 'camunda:in:businessKey' || newBindingType === 'camunda:out';
    });

    // (1) do not override old fields if no new fields specified
    if (!newProperties.length) {
      return;
    }

    // get extension elements of either signal event definition or call activity
    const extensionElements = this._getOrCreateExtensionElements(getSignalEventDefinition(element) || element);
    const oldInsAndOuts = findExtensions(extensionElements, ['camunda:In', 'camunda:Out']);
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        oldBinding = oldProperty && oldProperty.binding,
        oldInOurOut = oldProperty && findOldBusinessObject(extensionElements, oldProperty),
        newPropertyValue = newProperty.value,
        newBinding = newProperty.binding,
        newBindingType = newBinding.type,
        properties = {};
      let newInOrOut;

      // (2) update old ins and outs
      if (oldProperty && oldInOurOut) {
        if (!propertyChanged(oldInOurOut, oldProperty)) {
          if (newBindingType === 'camunda:in') {
            if (newBinding.expression) {
              properties['camunda:sourceExpression'] = newPropertyValue;
            } else {
              properties['camunda:source'] = newPropertyValue;
            }
          } else if (newBindingType === 'camunda:in:businessKey') {
            properties['camunda:businessKey'] = newPropertyValue;
          } else if (newBindingType === 'camunda:out') {
            properties['camunda:target'] = newPropertyValue;
          }
        }

        // update camunda:local property if it changed
        if (oldBinding.local && !newBinding.local || !oldBinding.local && newBinding.local) {
          properties.local = newBinding.local;
        }
        if (keys(properties)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldInOurOut,
            properties
          });
        }
        remove(oldInsAndOuts, oldInOurOut);
      }

      // (3) add new ins and outs
      else {
        if (newBindingType === 'camunda:in') {
          newInOrOut = createCamundaIn(newBinding, newPropertyValue, bpmnFactory);
        } else if (newBindingType === 'camunda:out') {
          newInOrOut = createCamundaOut(newBinding, newPropertyValue, bpmnFactory);
        } else if (newBindingType === 'camunda:in:businessKey') {
          newInOrOut = createCamundaInWithBusinessKey(newPropertyValue, bpmnFactory);
        }
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: extensionElements,
          properties: {
            values: [...extensionElements.get('values'), newInOrOut]
          }
        });
      }
    });

    // (4) remove old ins and outs
    if (oldInsAndOuts.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: extensionElements,
        properties: {
          values: without(extensionElements.get('values'), value => oldInsAndOuts.includes(value))
        }
      });
    }
  }

  /**
   * Update `camunda:InputParameter` and `camunda:OutputParameter` properties of specified business
   * object. Both can only exist in `camunda:InputOutput` which can exist in `bpmn:ExtensionElements`
   * or `camunda:Connector`.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   */
  _updateCamundaInputOutputParameterProperties(element, oldTemplate, newTemplate, businessObject) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:inputParameter' || newBindingType === 'camunda:outputParameter';
    });

    // (1) do not override old inputs and outputs if no new inputs and outputs specified
    if (!newProperties.length) {
      return;
    }
    if (!businessObject) {
      businessObject = this._getOrCreateExtensionElements(element);
    }
    let inputOutput;
    if (is(businessObject, 'camunda:Connector')) {
      inputOutput = businessObject.get('camunda:inputOutput');
      if (!inputOutput) {
        inputOutput = bpmnFactory.create('camunda:InputOutput');
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties: {
            inputOutput
          }
        });
      }
    } else {
      inputOutput = findExtension(businessObject, 'camunda:InputOutput');
      if (!inputOutput) {
        inputOutput = bpmnFactory.create('camunda:InputOutput');
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties: {
            values: [...businessObject.get('values'), inputOutput]
          }
        });
      }
    }
    const oldInputs = inputOutput.get('camunda:inputParameters') ? inputOutput.get('camunda:inputParameters').slice() : [];
    const oldOutputs = inputOutput.get('camunda:outputParameters') ? inputOutput.get('camunda:outputParameters').slice() : [];
    let propertyName;
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        oldInputOrOutput = oldProperty && findOldBusinessObject(businessObject, oldProperty),
        newPropertyValue = newProperty.value,
        newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      let newInputOrOutput, properties;

      // (2) update old inputs and outputs
      if (oldProperty && oldInputOrOutput) {
        if (!propertyChanged(oldInputOrOutput, oldProperty)) {
          if (is(oldInputOrOutput, 'camunda:InputParameter')) {
            properties = {
              value: newPropertyValue
            };
          } else {
            properties = {
              name: newPropertyValue
            };
          }
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldInputOrOutput,
            properties
          });
        }
        if (is(oldInputOrOutput, 'camunda:InputParameter')) {
          remove(oldInputs, oldInputOrOutput);
        } else {
          remove(oldOutputs, oldInputOrOutput);
        }
      }

      // (3) add new inputs and outputs
      else {
        if (newBindingType === 'camunda:inputParameter') {
          propertyName = 'inputParameters';
          newInputOrOutput = createInputParameter(newBinding, newPropertyValue, bpmnFactory);
        } else {
          propertyName = 'outputParameters';
          newInputOrOutput = createOutputParameter(newBinding, newPropertyValue, bpmnFactory);
        }
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: inputOutput,
          properties: {
            [propertyName]: [...inputOutput.get(propertyName), newInputOrOutput]
          }
        });
      }
    });

    // (4) remove old inputs and outputs
    if (oldInputs.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: inputOutput,
        properties: {
          inputParameters: without(inputOutput.get('inputParameters'), inputParameter => oldInputs.includes(inputParameter))
        }
      });
    }
    if (oldOutputs.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: inputOutput,
        properties: {
          outputParameters: without(inputOutput.get('outputParameters'), outputParameter => oldOutputs.includes(outputParameter))
        }
      });
    }
  }
  _updateCamundaModelerTemplate(element, newTemplate) {
    const modeling = this._modeling;
    const newId = newTemplate && newTemplate.id;
    const newVersion = newTemplate && newTemplate.version;
    if (getTemplateId(element) !== newId || getTemplateVersion(element) !== newVersion) {
      modeling.updateProperties(element, {
        'camunda:modelerTemplate': newId,
        'camunda:modelerTemplateVersion': newVersion
      });
    }
  }

  /**
   * Update `camunda:Property` properties of specified business object. `camunda:Property` can only
   * exist in `camunda:Properties`.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newTemplate
   * @param {ModdleElement} businessObject
   */
  _updateCamundaPropertyProperties(element, oldTemplate, newTemplate, businessObject) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'camunda:property';
    });

    // (1) do not override old properties if no new properties specified
    if (!newProperties.length) {
      return;
    }
    if (businessObject) {
      businessObject = this._getOrCreateExtensionElements(businessObject);
    } else {
      businessObject = this._getOrCreateExtensionElements(element);
    }
    let camundaProperties = findExtension(businessObject, 'camunda:Properties');
    if (!camundaProperties) {
      camundaProperties = bpmnFactory.create('camunda:Properties');
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          values: [...businessObject.get('values'), camundaProperties]
        }
      });
    }
    const oldCamundaProperties = camundaProperties.get('camunda:values') ? camundaProperties.get('camunda:values').slice() : [];
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        oldCamundaProperty = oldProperty && findOldBusinessObject(businessObject, oldProperty),
        newPropertyValue = newProperty.value,
        newBinding = newProperty.binding;

      // (2) update old properties
      if (oldProperty && oldCamundaProperty) {
        if (!propertyChanged(oldCamundaProperty, oldProperty)) {
          commandStack.execute('element.updateModdleProperties', {
            element,
            moddleElement: oldCamundaProperty,
            properties: {
              value: newPropertyValue
            }
          });
        }
        remove(oldCamundaProperties, oldCamundaProperty);
      }

      // (3) add new properties
      else {
        const newCamundaProperty = createCamundaProperty(newBinding, newPropertyValue, bpmnFactory);
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: camundaProperties,
          properties: {
            values: [...camundaProperties.get('values'), newCamundaProperty]
          }
        });
      }
    });

    // (4) remove old properties
    if (oldCamundaProperties.length) {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: camundaProperties,
        properties: {
          values: without(camundaProperties.get('values'), value => oldCamundaProperties.includes(value))
        }
      });
    }
  }

  /**
   * Update `bpmn:conditionExpression` property of specified element. Since condition expression is
   * is not primitive it needs special handling.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldProperty
   * @param {Object} newProperty
   */
  _updateConditionExpression(element, oldProperty, newProperty) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack,
      modeling = this._modeling;
    const newBinding = newProperty.binding,
      newPropertyValue = newProperty.value;
    if (!oldProperty) {
      modeling.updateProperties(element, {
        conditionExpression: bpmnFactory.create('bpmn:FormalExpression', {
          body: newPropertyValue,
          language: newBinding.scriptFormat
        })
      });
      return;
    }
    const oldBinding = oldProperty.binding,
      oldPropertyValue = oldProperty.value;
    const businessObject = getBusinessObject(element),
      conditionExpression = businessObject.get('bpmn:conditionExpression');
    const properties = {};
    if (conditionExpression.get('body') === oldPropertyValue) {
      properties.body = newPropertyValue;
    }
    if (conditionExpression.get('language') === oldBinding.scriptFormat) {
      properties.language = newBinding.scriptFormat;
    }
    if (!keys(properties).length) {
      return;
    }
    commandStack.execute('element.updateModdleProperties', {
      element,
      moddleElement: conditionExpression,
      properties
    });
  }
  _updateProperties(element, oldTemplate, newTemplate, businessObject) {
    const commandStack = this._commandStack;
    const newProperties = newTemplate.properties.filter(newProperty => {
      const newBinding = newProperty.binding,
        newBindingType = newBinding.type;
      return newBindingType === 'property';
    });
    const oldProperties = oldTemplate && oldTemplate.properties.filter(oldProperty => {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'property';
    });
    if (!newProperties.length) {
      return;
    }
    if (!businessObject) {
      businessObject = getBusinessObject(element);
    }
    newProperties.forEach(newProperty => {
      const oldProperty = findOldProperty(oldTemplate, newProperty),
        newBinding = newProperty.binding,
        newBindingName = newBinding.name,
        newPropertyValue = newProperty.value;
      let changedElement, properties;
      if (oldProperty) {
        remove(oldProperties, oldProperty);
      }
      if (newBindingName === 'conditionExpression') {
        this._updateConditionExpression(element, oldProperty, newProperty);
      } else {
        if (is(businessObject, 'bpmn:Error')) {
          changedElement = businessObject;
        } else {
          changedElement = element;
        }
        if (oldProperty && propertyChanged(changedElement, oldProperty)) {
          return;
        }
        properties = {};
        properties[newBindingName] = newPropertyValue;

        // only one of `camunda:class`, `camunda:delegateExpression` and `camunda:expression` can be set
        // TODO(philippfromme): ensuring only one of these properties is set at a time should be
        // implemented in a behavior and not in this handler and properties panel UI
        if (CAMUNDA_SERVICE_TASK_LIKE.indexOf(newBindingName) !== -1) {
          CAMUNDA_SERVICE_TASK_LIKE.forEach(camundaServiceTaskLikeProperty => {
            if (camundaServiceTaskLikeProperty !== newBindingName) {
              properties[camundaServiceTaskLikeProperty] = undefined;
            }
          });
        }
        commandStack.execute('element.updateModdleProperties', {
          element,
          moddleElement: businessObject,
          properties
        });
      }
    });

    // remove old properties not present in new template
    oldProperties && oldProperties.forEach(oldProperty => {
      commandStack.execute('element.updateModdleProperties', {
        element,
        moddleElement: businessObject,
        properties: {
          [oldProperty.binding.name]: null
        }
      });
    });
  }

  /**
   * Update properties for a specified scope.
   *
   * @param {djs.model.Base} element
   * @param {Object} oldTemplate
   * @param {Object} newScopeTemplate
   * @param {Object} newTemplate
   */
  _updateScopeProperties(element, oldTemplate, newScopeTemplate, newTemplate) {
    const bpmnFactory = this._bpmnFactory,
      commandStack = this._commandStack;
    const scopeName = newScopeTemplate.type;
    let scopeElement;
    scopeElement = findOldScopeElement(element, newScopeTemplate, newTemplate);
    if (!scopeElement) {
      scopeElement = bpmnFactory.create(scopeName);
    }
    const oldScopeTemplate = findOldScopeTemplate(newScopeTemplate, oldTemplate);

    // update properties
    this._updateProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);

    // update camunda:ExecutionListener properties
    this._updateCamundaExecutionListenerProperties(element, newScopeTemplate);

    // update camunda:In and camunda:Out properties
    this._updateCamundaInOutProperties(element, oldScopeTemplate, newScopeTemplate);

    // update camunda:InputParameter and camunda:OutputParameter properties
    this._updateCamundaInputOutputParameterProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);

    // update camunda:Field properties
    this._updateCamundaFieldProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);

    // update camunda:Property properties
    this._updateCamundaPropertyProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);

    // assume that root elements were already created in root by referenced event definition binding
    if (isRootElementScope(scopeName)) {
      return;
    }
    const extensionElements = this._getOrCreateExtensionElements(element);
    commandStack.execute('element.updateModdleProperties', {
      element,
      moddleElement: extensionElements,
      properties: {
        values: [...extensionElements.get('values'), scopeElement]
      }
    });
  }

  /**
   * Replaces the element with the specified elementType
   *
   * @param {djs.model.Base} element
   * @param {Object} newTemplate
   */
  _updateTaskType(element, newTemplate) {
    // determine new task type
    const newType = newTemplate.elementType;
    if (!newType) {
      return element;
    }

    // don't replace Task that is already the correct type
    if (element.$type === newType.value) {
      return element;
    }
    return this._bpmnReplace.replaceElement(element, {
      type: newType.value
    });
  }
}
ChangeElementTemplateHandler.$inject = ['bpmnFactory', 'bpmnReplace', 'commandStack', 'modeling'];

// helpers //////////

/**
 * Find old business object matching specified old property.
 *
 * @param {djs.model.Base|ModdleElement} element
 * @param {Object} oldProperty
 *
 * @returns {ModdleElement}
 */
function findOldBusinessObject(element, oldProperty) {
  let businessObject = getBusinessObject(element),
    propertyName;
  const oldBinding = oldProperty.binding,
    oldBindingType = oldBinding.type;
  if (oldBindingType === 'camunda:field') {
    if (isAny(businessObject, ['camunda:ExecutionListener', 'camunda:TaskListener'])) {
      propertyName = 'camunda:fields';
    } else {
      propertyName = 'bpmn:values';
    }
    if (!businessObject || !businessObject.get(propertyName) || !businessObject.get(propertyName).length) {
      return;
    }
    return find(businessObject.get(propertyName), function (oldBusinessObject) {
      return oldBusinessObject.get('camunda:name') === oldBinding.name;
    });
  }
  if (oldBindingType === 'camunda:in') {
    return find(businessObject.get('values'), function (oldBusinessObject) {
      return oldBusinessObject.get('target') === oldBinding.target;
    });
  }
  if (oldBindingType === 'camunda:in:businessKey') {
    return find(businessObject.get('values'), function (oldBusinessObject) {
      return isString(oldBusinessObject.get('businessKey'));
    });
  }
  if (oldBindingType === 'camunda:out') {
    return find(businessObject.get('values'), function (oldBusinessObject) {
      return oldBusinessObject.get('source') === oldBinding.source || oldBusinessObject.get('sourceExpression') || oldBinding.sourceExpression;
    });
  }
  if (oldBindingType === 'camunda:inputParameter' || oldBindingType === 'camunda:outputParameter') {
    if (is(businessObject, 'camunda:Connector')) {
      businessObject = businessObject.get('camunda:inputOutput');
      if (!businessObject) {
        return;
      }
    } else {
      businessObject = findExtension(businessObject, 'camunda:InputOutput');
      if (!businessObject) {
        return;
      }
    }
    if (oldBindingType === 'camunda:inputParameter') {
      return find(businessObject.get('camunda:inputParameters'), function (oldBusinessObject) {
        return oldBusinessObject.get('camunda:name') === oldBinding.name;
      });
    } else {
      return find(businessObject.get('camunda:outputParameters'), function (oldBusinessObject) {
        if (oldBinding.scriptFormat) {
          const definition = oldBusinessObject.get('camunda:definition');
          return definition && definition.get('camunda:value') === oldBinding.source;
        } else {
          return oldBusinessObject.get('camunda:value') === oldBinding.source;
        }
      });
    }
  }
  if (oldBindingType === 'camunda:property') {
    if (!businessObject || !businessObject.get('values') || !businessObject.get('values').length) {
      return;
    }
    businessObject = findExtension(businessObject, 'camunda:Properties');
    if (!businessObject) {
      return;
    }
    return find(businessObject.get('values'), function (oldBusinessObject) {
      return oldBusinessObject.get('camunda:name') === oldBinding.name;
    });
  }
  if (oldBindingType === 'camunda:errorEventDefinition') {
    return findCamundaErrorEventDefinition(element, oldBinding.errorRef);
  }
}

/**
 * Find old property matching specified new property.
 *
 * @param {Object} oldTemplate
 * @param {Object} newProperty
 *
 * @returns {Object}
 */
function findOldProperty(oldTemplate, newProperty) {
  if (!oldTemplate) {
    return;
  }
  const oldProperties = oldTemplate.properties,
    newBinding = newProperty.binding,
    newBindingName = newBinding.name,
    newBindingType = newBinding.type;
  if (newBindingType === 'property') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'property' && oldBindingName === newBindingName;
    });
  }
  if (newBindingType === 'camunda:field') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'camunda:field' && oldBindingName === newBindingName;
    });
  }
  if (newBindingType === 'camunda:in') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'camunda:in') {
        return;
      }

      // always override if change from source to source expression or vice versa
      if (oldBinding.expression && !newBinding.expression || !oldBinding.expression && newBinding.expression) {
        return;
      }
      return oldBinding.target === newBinding.target;
    });
  }
  if (newBindingType === 'camunda:in:businessKey') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'camunda:in:businessKey';
    });
  }
  if (newBindingType === 'camunda:out') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'camunda:out' && (oldBinding.source === newBinding.source || oldBinding.sourceExpression === newBinding.sourceExpression);
    });
  }
  if (newBindingType === 'camunda:inputParameter') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'camunda:inputParameter') {
        return;
      }
      return oldBindingName === newBindingName && oldBinding.scriptFormat === newBinding.scriptFormat;
    });
  }
  if (newBindingType === 'camunda:outputParameter') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingType = oldBinding.type;
      if (oldBindingType !== 'camunda:outputParameter') {
        return;
      }
      return oldBinding.source === newBinding.source && oldBinding.scriptFormat === newBinding.scriptFormat;
    });
  }
  if (newBindingType === 'camunda:property') {
    return find(oldProperties, function (oldProperty) {
      const oldBinding = oldProperty.binding,
        oldBindingName = oldBinding.name,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'camunda:property' && oldBindingName === newBindingName;
    });
  }
  if (newBindingType === 'camunda:errorEventDefinition') {
    return find(oldProperties, function (oldProperty) {
      const newBindingRef = newBinding.errorRef,
        oldBinding = oldProperty.binding,
        oldBindingRef = oldBinding.errorRef,
        oldBindingType = oldBinding.type;
      return oldBindingType === 'camunda:errorEventDefinition' && oldBindingRef === newBindingRef;
    });
  }
}
function findOldScopeElement(element, scopeTemplate, template) {
  const scopeName = scopeTemplate.type,
    id = scopeTemplate.id;
  if (scopeName === 'camunda:Connector') {
    return findExtension(element, 'camunda:Connector');
  }
  if (scopeName === 'bpmn:Error') {
    // (1) find by error event definition binding
    const errorEventDefinitionBinding = findErrorEventDefinitionBinding(template, id);
    if (!errorEventDefinitionBinding) {
      return;
    }

    // (2) find error event definition
    const errorEventDefinition = findOldBusinessObject(element, errorEventDefinitionBinding);
    if (!errorEventDefinition) {
      return;
    }

    // (3) retrieve referenced error
    return errorEventDefinition.errorRef;
  }
}
function isRootElementScope(scopeName) {
  return ['bpmn:Error'].includes(scopeName);
}
function findOldScopeTemplate(scopeTemplate, oldTemplate) {
  const scopeName = scopeTemplate.type,
    scopeId = scopeTemplate.id,
    scopes = oldTemplate && handleLegacyScopes(oldTemplate.scopes);
  return scopes && find(scopes, function (scope) {
    if (isRootElementScope(scopeName)) {
      return scope.id === scopeId;
    }
    return scope.type === scopeName;
  });
}
function findErrorEventDefinitionBinding(template, templateErrorId) {
  return find(template.properties, function (property) {
    return property.binding.errorRef === templateErrorId;
  });
}

/**
 * Check whether property was changed after being set by template.
 *
 * @param {djs.model.Base|ModdleElement} element
 * @param {Object} oldProperty
 *
 * @returns {boolean}
 */
function propertyChanged(element, oldProperty) {
  const businessObject = getBusinessObject(element);
  const oldBinding = oldProperty.binding,
    oldBindingName = oldBinding.name,
    oldBindingType = oldBinding.type,
    oldPropertyValue = oldProperty.value;
  let conditionExpression, definition;
  if (oldBindingType === 'property') {
    if (oldBindingName === 'conditionExpression') {
      conditionExpression = businessObject.get('bpmn:conditionExpression');
      return conditionExpression.get('bpmn:body') !== oldPropertyValue;
    }
    return businessObject.get(oldBindingName) !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:field') {
    return businessObject.get('camunda:string') !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:in') {
    if (oldBinding.expression) {
      return businessObject.get('sourceExpression') !== oldPropertyValue;
    } else {
      return businessObject.get('camunda:source') !== oldPropertyValue;
    }
  }
  if (oldBindingType === 'camunda:in:businessKey') {
    return businessObject.get('camunda:businessKey') !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:out') {
    return businessObject.get('camunda:target') !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:inputParameter') {
    if (oldBinding.scriptFormat) {
      definition = businessObject.get('camunda:definition');
      return definition && definition.get('camunda:value') !== oldPropertyValue;
    } else {
      return businessObject.get('camunda:value') !== oldPropertyValue;
    }
  }
  if (oldBindingType === 'camunda:outputParameter') {
    return businessObject.get('camunda:name') !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:property') {
    return businessObject.get('camunda:value') !== oldPropertyValue;
  }
  if (oldBindingType === 'camunda:errorEventDefinition') {
    return businessObject.get('expression') !== oldPropertyValue;
  }
}
function remove(array, item) {
  const index = array.indexOf(item);
  if (isUndefined$1(index)) {
    return array;
  }
  array.splice(index, 1);
  return array;
}

class RemoveElementTemplateHandler {
  constructor(modeling, elementFactory, elementRegistry, canvas, bpmnFactory, replace, commandStack) {
    this._modeling = modeling;
    this._elementFactory = elementFactory;
    this._elementRegistry = elementRegistry;
    this._canvas = canvas;
    this._bpmnFactory = bpmnFactory;
    this._replace = replace;
    this._commandStack = commandStack;
  }
  preExecute(context) {
    const {
      element
    } = context;
    if (element.parent) {
      context.newElement = this._removeTemplate(element);
    } else {
      context.newElement = this._removeRootTemplate(element);
    }
  }
  _removeTemplate(element) {
    const replace = this._replace;
    const businessObject = getBusinessObject(element);
    const type = businessObject.$type,
      eventDefinitionType = this._getEventDefinitionType(businessObject);
    const newBusinessObject = this._createBlankBusinessObject(element);
    return replace.replaceElement(element, {
      type: type,
      businessObject: newBusinessObject,
      eventDefinitionType: eventDefinitionType
    }, {
      createElementsBehavior: false
    });
  }

  /**
   * Remove template from a given element.
   *
   * @param {djs.model.Base} element
   *
   * @return {djs.model.Base} the updated element
   */
  _removeRootTemplate(element) {
    var modeling = this._modeling,
      elementFactory = this._elementFactory,
      elementRegistry = this._elementRegistry,
      canvas = this._canvas;

    // We are inside a collapsed subprocess, move up to the parent before replacing the collapsed object
    if (isPlane(element)) {
      const shapeId = getShapeIdFromPlane(element);
      const shape = elementRegistry.get(shapeId);
      if (shape && shape !== element) {
        canvas.setRootElement(canvas.findRoot(shape));
        return this._removeTemplate(shape);
      }
    }
    const businessObject = getBusinessObject(element);
    const type = businessObject.$type;
    const newBusinessObject = this._createBlankBusinessObject(element);
    const newRoot = elementFactory.create('root', {
      type: type,
      businessObject: newBusinessObject
    });
    this._commandStack.execute('canvas.updateRoot', {
      newRoot: newRoot,
      oldRoot: element
    });
    modeling.moveElements(element.children, {
      x: 0,
      y: 0
    }, newRoot);
    return newRoot;
  }
  _getEventDefinitionType(businessObject) {
    if (!businessObject.eventDefinitions) {
      return null;
    }
    const eventDefinition = businessObject.eventDefinitions[0];
    if (!eventDefinition) {
      return null;
    }
    return eventDefinition.$type;
  }
  _createBlankBusinessObject(element) {
    const bpmnFactory = this._bpmnFactory;
    const bo = getBusinessObject(element),
      newBo = bpmnFactory.create(bo.$type),
      label = getLabel(element);
    if (!label) {
      return newBo;
    }
    if (is(element, 'bpmn:Group')) {
      newBo.categoryValueRef = bpmnFactory.create('bpmn:CategoryValue');
    }
    setLabel({
      businessObject: newBo
    }, label);
    return newBo;
  }
}
RemoveElementTemplateHandler.$inject = ['modeling', 'elementFactory', 'elementRegistry', 'canvas', 'bpmnFactory', 'replace', 'commandStack'];

class UnlinkElementTemplateHandler {
  constructor(commandStack) {
    this._commandStack = commandStack;
  }
  preExecute(context) {
    const {
      element,
      oldTemplate
    } = context;
    this._commandStack.execute('propertiesPanel.camunda.changeTemplate', {
      element,
      oldTemplate,
      newTemplate: null
    });
  }
}
UnlinkElementTemplateHandler.$inject = ['commandStack'];

class ElementTemplatesCommands {
  constructor(commandStack, elementTemplates, eventBus) {
    commandStack.registerHandler('element-templates.multi-command-executor', MultiCommandHandler);
    commandStack.registerHandler('propertiesPanel.camunda.changeTemplate', ChangeElementTemplateHandler);
    commandStack.registerHandler('propertiesPanel.removeTemplate', RemoveElementTemplateHandler);
    commandStack.registerHandler('propertiesPanel.unlinkTemplate', UnlinkElementTemplateHandler);

    // apply default element templates on shape creation
    eventBus.on(['commandStack.shape.create.postExecuted'], function (event) {
      const {
        context: {
          hints = {},
          shape
        }
      } = event;
      if (hints.createElementsBehavior !== false) {
        applyDefaultTemplate(shape, elementTemplates, commandStack);
      }
    });

    // apply default element templates on connection creation
    eventBus.on(['commandStack.connection.create.postExecuted'], function (event) {
      const {
        context: {
          hints = {},
          connection
        }
      } = event;
      if (hints.createElementsBehavior !== false) {
        applyDefaultTemplate(connection, elementTemplates, commandStack);
      }
    });
  }
}
ElementTemplatesCommands.$inject = ['commandStack', 'elementTemplates', 'eventBus'];
function applyDefaultTemplate(element, elementTemplates, commandStack) {
  if (!elementTemplates.get(element) && elementTemplates.getDefault(element)) {
    const command = 'propertiesPanel.camunda.changeTemplate';
    const commandContext = {
      element: element,
      newTemplate: elementTemplates.getDefault(element)
    };
    commandStack.execute(command, commandContext);
  }
}

var commandsModule = {
  __init__: ['elementTemplateCommands'],
  elementTemplateCommands: ['type', ElementTemplatesCommands]
};

/**
 * This Behavior checks if the new element's type is in
 * the list of elements the template applies to and unlinks
 * it if not.
 */
class ReplaceBehavior extends CommandInterceptor {
  constructor(elementTemplates, injector) {
    super(injector.get('eventBus'));
    this.postExecuted('shape.replace', function (e) {
      var context = e.context,
        oldShape = context.oldShape,
        oldBo = getBusinessObject(oldShape),
        newShape = context.newShape,
        newBo = getBusinessObject(newShape);
      if (!oldBo.modelerTemplate) {
        return;
      }
      const template = newBo.modelerTemplate;
      const version = newBo.modelerTemplateVersion;
      const elementTemplate = elementTemplates.get(template, version);
      if (!elementTemplate) {
        elementTemplates.unlinkTemplate(newShape, injector);
        return;
      }
      const {
        appliesTo,
        elementType
      } = elementTemplate;
      if (elementType) {
        if (!is(newShape, elementType.value)) {
          elementTemplates.unlinkTemplate(newShape, injector);
        }
        return;
      }
      const allowed = appliesTo.reduce((allowed, type) => {
        return allowed || is(newBo, type);
      }, false);
      if (!allowed) {
        elementTemplates.unlinkTemplate(newShape, injector);
      }
    });
  }
}
ReplaceBehavior.$inject = ['elementTemplates', 'injector'];

var behaviorModule = {
  __init__: ['elementTemplatesReplaceBehavior'],
  elementTemplatesReplaceBehavior: ['type', ReplaceBehavior]
};

var coreModule = {
  __depends__: [commandsModule, behaviorModule],
  __init__: ['elementTemplatesLoader'],
  elementTemplates: ['type', ElementTemplates$1],
  elementTemplatesLoader: ['type', ElementTemplatesLoader$1]
};

const CAMUNDA_ERROR_EVENT_DEFINITION_TYPE$1 = 'camunda:errorEventDefinition';
const CAMUNDA_EXECUTION_LISTENER_TYPE = 'camunda:executionListener';
const CAMUNDA_FIELD_TYPE = 'camunda:field';
const CAMUNDA_IN_BUSINESS_KEY_TYPE = 'camunda:in:businessKey';
const CAMUNDA_IN_TYPE = 'camunda:in';
const CAMUNDA_INPUT_PARAMETER_TYPE$1 = 'camunda:inputParameter';
const CAMUNDA_OUT_TYPE = 'camunda:out';
const CAMUNDA_OUTPUT_PARAMETER_TYPE$1 = 'camunda:outputParameter';
const CAMUNDA_PROPERTY_TYPE = 'camunda:property';
const PROPERTY_TYPE = 'property';
const EXTENSION_BINDING_TYPES = [CAMUNDA_ERROR_EVENT_DEFINITION_TYPE$1, CAMUNDA_FIELD_TYPE, CAMUNDA_IN_TYPE, CAMUNDA_IN_BUSINESS_KEY_TYPE, CAMUNDA_INPUT_PARAMETER_TYPE$1, CAMUNDA_OUT_TYPE, CAMUNDA_OUTPUT_PARAMETER_TYPE$1, CAMUNDA_PROPERTY_TYPE];
const IO_BINDING_TYPES = [CAMUNDA_INPUT_PARAMETER_TYPE$1, CAMUNDA_OUTPUT_PARAMETER_TYPE$1];
const IN_OUT_BINDING_TYPES = [CAMUNDA_IN_BUSINESS_KEY_TYPE, CAMUNDA_IN_TYPE, CAMUNDA_OUT_TYPE];

const PRIMITIVE_MODDLE_TYPES = ['Boolean', 'Integer', 'String'];
function CustomProperties(props) {
  const {
    element,
    elementTemplate,
    injector
  } = props;
  const translate = injector.get('translate');
  const groups = [];
  const {
    id,
    properties,
    groups: propertyGroups,
    scopes
  } = elementTemplate;

  // (1) group properties by group id
  const groupedProperties = groupByGroupId(properties);
  const defaultProps = [];
  forEach(groupedProperties, (properties, groupId) => {
    const group = findCustomGroup(propertyGroups, groupId);
    if (!group) {
      return defaultProps.push(...properties);
    }
    addCustomGroup(groups, {
      element,
      id: `ElementTemplates__CustomProperties-${groupId}`,
      label: translate(group.label),
      properties: properties,
      templateId: `${id}-${groupId}`
    });
  });

  // (2) add default custom props
  if (defaultProps.length) {
    addCustomGroup(groups, {
      id: 'ElementTemplates__CustomProperties',
      label: translate('Custom properties'),
      element,
      properties: defaultProps,
      templateId: id
    });
  }

  // (3) add custom scopes props
  if (isArray(scopes)) {
    scopes.forEach(scope => {
      const {
        properties,
        type
      } = scope;
      const id = type.replace(/:/g, '-');
      addCustomGroup(groups, {
        element,
        id: `ElementTemplates__CustomGroup-${id}`,
        label: translate(`Custom properties for scope <${type}>`),
        properties,
        templateId: id,
        scope
      });
    });
  }
  return groups;
}
function addCustomGroup(groups, props) {
  const {
    element,
    id,
    label,
    properties,
    scope,
    templateId
  } = props;
  const customPropertiesGroup = {
    id,
    label,
    component: Group,
    entries: [],
    shouldOpen: false
  };
  properties.forEach((property, index) => {
    const entry = createCustomEntry(`custom-entry-${templateId}-${index}`, element, property, scope);
    if (entry) {
      customPropertiesGroup.entries.push(entry);
    }
  });
  if (customPropertiesGroup.entries.length) {
    groups.push(customPropertiesGroup);
  }
}
function createCustomEntry(id, element, property, scope) {
  let {
    type
  } = property;
  if (!type) {
    type = getDefaultType(property);
  }
  if (type === 'Boolean') {
    return {
      id,
      component: BooleanProperty,
      isEdited: isCheckboxEntryEdited,
      property,
      scope
    };
  }
  if (type === 'Dropdown') {
    return {
      id,
      component: DropdownProperty,
      isEdited: isSelectEntryEdited,
      property,
      scope
    };
  }
  if (type === 'String') {
    return {
      id,
      component: StringProperty,
      isEdited: isTextFieldEntryEdited,
      property,
      scope
    };
  }
  if (type === 'Text') {
    return {
      id,
      component: TextAreaProperty,
      isEdited: isTextAreaEntryEdited,
      property,
      scope
    };
  }
}
function getDefaultType(property) {
  const {
    binding
  } = property;
  const {
    type
  } = binding;
  if ([PROPERTY_TYPE, CAMUNDA_PROPERTY_TYPE, CAMUNDA_IN_TYPE, CAMUNDA_IN_BUSINESS_KEY_TYPE, CAMUNDA_OUT_TYPE, CAMUNDA_FIELD_TYPE].includes(type)) {
    return 'String';
  }
  if (type === CAMUNDA_EXECUTION_LISTENER_TYPE) {
    return 'Hidden';
  }
}
function BooleanProperty(props) {
  const {
    element,
    id,
    property,
    scope
  } = props;
  const {
    description,
    editable,
    label
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    translate = useService('translate');
  return CheckboxEntry({
    element,
    getValue: propertyGetter(element, property, scope),
    id,
    label: label ? translate(label) : label,
    description: PropertyDescription({
      description
    }),
    setValue: propertySetter(bpmnFactory, commandStack, element, property, scope),
    disabled: editable === false
  });
}
function DropdownProperty(props) {
  const {
    element,
    id,
    property,
    scope
  } = props;
  const {
    description,
    editable,
    label
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    translate = useService('translate');
  const getOptions = () => {
    const {
      choices
    } = property;
    return choices.map(({
      name,
      value
    }) => {
      return {
        label: translate(name),
        value
      };
    });
  };
  return SelectEntry({
    element,
    id,
    label: label ? translate(label) : label,
    getOptions,
    description: PropertyDescription({
      description
    }),
    getValue: propertyGetter(element, property, scope),
    setValue: propertySetter(bpmnFactory, commandStack, element, property, scope),
    disabled: editable === false,
    validate: propertyValidator(translate, property)
  });
}
function StringProperty(props) {
  const {
    element,
    id,
    property,
    scope
  } = props;
  const {
    description,
    editable,
    label,
    placeholder
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  return TextFieldEntry({
    debounce,
    element,
    getValue: propertyGetter(element, property, scope),
    id,
    placeholder,
    label: label ? translate(label) : label,
    description: PropertyDescription({
      description
    }),
    setValue: propertySetter(bpmnFactory, commandStack, element, property, scope),
    validate: propertyValidator(translate, property),
    disabled: editable === false
  });
}
function TextAreaProperty(props) {
  const {
    element,
    id,
    property,
    scope
  } = props;
  const {
    description,
    editable,
    label,
    placeholder
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  return TextAreaEntry({
    debounce,
    element,
    id,
    placeholder,
    label: label ? translate(label) : label,
    description: PropertyDescription({
      description
    }),
    getValue: propertyGetter(element, property, scope),
    setValue: propertySetter(bpmnFactory, commandStack, element, property, scope),
    disabled: editable === false,
    validate: propertyValidator(translate, property)
  });
}
function propertyGetter(element, property, scope) {
  return function getValue() {
    let businessObject = getBusinessObject(element);
    const {
      binding,
      value: defaultValue = ''
    } = property;
    const {
      name,
      type
    } = binding;
    if (scope) {
      businessObject = getScopeBusinessObject(businessObject, scope);
      if (!businessObject) {
        return defaultValue;
      }
    }

    // property
    if (type === 'property') {
      const value = businessObject.get(name);
      if (name === 'conditionExpression') {
        if (value) {
          return value.get('body');
        }
        return defaultValue;
      } else {
        if (!isUndefined$1(value)) {
          return value;
        }
        return defaultValue;
      }
    }

    // camunda:ErrorEventDefinition
    if (type === CAMUNDA_ERROR_EVENT_DEFINITION_TYPE$1) {
      const {
        errorRef
      } = binding;
      const errorEventDefinition = findCamundaErrorEventDefinition(businessObject, errorRef);
      if (errorEventDefinition) {
        return errorEventDefinition.get('camunda:expression');
      } else {
        return '';
      }
    }

    // camunda:Field
    if (type === CAMUNDA_FIELD_TYPE) {
      const camundaFields = findExtensions(businessObject, ['camunda:Field']);
      const camundaField = camundaFields.find(camundaField => {
        return camundaField.get('camunda:name') === name;
      });
      if (camundaField) {
        return camundaField.get('camunda:string') || camundaField.get('camunda:expression');
      } else {
        return '';
      }
    }

    // camunda:Property
    if (type === CAMUNDA_PROPERTY_TYPE) {
      let camundaProperties;
      if (scope) {
        // TODO(philippfromme): as only bpmn:Error and camunda:Connector are supported this code is practically dead
        camundaProperties = businessObject.get('properties');
      } else {
        camundaProperties = findExtension(businessObject, 'camunda:Properties');
      }
      if (camundaProperties) {
        const camundaProperty = findCamundaProperty(camundaProperties, binding);
        if (camundaProperty) {
          return camundaProperty.get('camunda:value');
        }
      }
      return defaultValue;
    }
    if (IO_BINDING_TYPES.includes(type)) {
      let inputOutput;
      if (scope) {
        inputOutput = businessObject.get('inputOutput');
      } else {
        inputOutput = findExtension(businessObject, 'camunda:InputOutput');
      }
      if (!inputOutput) {
        return defaultValue;
      }

      // camunda:InputParameter
      if (type === CAMUNDA_INPUT_PARAMETER_TYPE$1) {
        const inputParameter = findInputParameter(inputOutput, binding);
        if (inputParameter) {
          const {
            scriptFormat
          } = binding;
          if (scriptFormat) {
            const definition = inputParameter.get('camunda:definition');
            if (definition) {
              return definition.get('camunda:value');
            }
          } else {
            return inputParameter.get('value') || '';
          }
        }
        return defaultValue;
      }

      // camunda:OutputParameter
      if (type === CAMUNDA_OUTPUT_PARAMETER_TYPE$1) {
        const outputParameter = findOutputParameter(inputOutput, binding);
        if (outputParameter) {
          return outputParameter.get('camunda:name');
        }
        return defaultValue;
      }
    }

    // camunda:In and camunda:Out
    if (IN_OUT_BINDING_TYPES.includes(type)) {
      const camundaInOut = findCamundaInOut(businessObject, binding);
      if (camundaInOut) {
        if (type === CAMUNDA_IN_BUSINESS_KEY_TYPE) {
          return camundaInOut.get('camunda:businessKey');
        } else if (type === CAMUNDA_OUT_TYPE) {
          return camundaInOut.get('camunda:target');
        } else if (type === CAMUNDA_IN_TYPE) {
          const {
            expression
          } = binding;
          if (expression) {
            return camundaInOut.get('camunda:sourceExpression');
          } else {
            return camundaInOut.get('camunda:source');
          }
        }
      }
      return defaultValue;
    }

    // should never throw as templates are validated beforehand
    throw unknownBindingError(element, property);
  };
}
function propertySetter(bpmnFactory, commandStack, element, property, scope) {
  return function setValue(value) {
    let businessObject = getBusinessObject(element);
    const {
      binding
    } = property;
    const {
      name,
      type
    } = binding;
    const rootElement = getRoot(businessObject);
    let extensionElements;
    let propertyValue;
    const commands = [];
    if (EXTENSION_BINDING_TYPES.includes(type)) {
      extensionElements = businessObject.get('extensionElements');
      if (!extensionElements) {
        extensionElements = createElement('bpmn:ExtensionElements', null, businessObject, bpmnFactory);
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: businessObject,
            properties: {
              extensionElements
            }
          }
        });
      }
    }
    if (scope) {
      businessObject = getScopeBusinessObject(businessObject, scope);
      if (!businessObject) {
        // bpmn:Error
        if (scope.type === 'bpmn:Error') {
          businessObject = createError(scope.id, rootElement, bpmnFactory);
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: rootElement,
              properties: {
                rootElements: [...rootElement.get('rootElements'), businessObject]
              }
            }
          });
        } else {
          businessObject = createElement(scope.type, null, element, bpmnFactory);
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: extensionElements,
              properties: {
                values: [...extensionElements.get('values'), businessObject]
              }
            }
          });
        }
      }
    }

    // property
    if (type === 'property') {
      if (name === 'conditionExpression') {
        const {
          scriptFormat
        } = binding;
        propertyValue = createElement('bpmn:FormalExpression', {
          body: value,
          language: scriptFormat
        }, businessObject, bpmnFactory);
      } else {
        const propertyDescriptor = businessObject.$descriptor.propertiesByName[name];
        const {
          type: propertyType
        } = propertyDescriptor;

        // do not override non-primitive types
        if (!PRIMITIVE_MODDLE_TYPES.includes(propertyType)) {
          throw new Error(`cannot set property of type <${propertyType}>`);
        }
        if (propertyType === 'Boolean') {
          propertyValue = !!value;
        } else if (propertyType === 'Integer') {
          propertyValue = parseInt(value, 10);
          if (isNaN(propertyValue)) {
            // do not set NaN value
            propertyValue = undefined;
          }
        } else {
          // make sure we don't remove the property
          propertyValue = value || '';
        }
      }
      if (!isUndefined$1(propertyValue)) {
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: businessObject,
            properties: {
              [name]: propertyValue
            }
          }
        });
      }
    }

    // camunda:ErrorEventDefinition
    if (type === CAMUNDA_ERROR_EVENT_DEFINITION_TYPE$1) {
      const {
        errorRef
      } = binding;
      const oldCamundaErrorEventDefinition = findCamundaErrorEventDefinition(businessObject, errorRef);
      if (oldCamundaErrorEventDefinition) {
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: oldCamundaErrorEventDefinition,
            properties: {
              'camunda:expression': value
            }
          }
        });
      } else {
        const newError = createError(binding.errorRef, rootElement, bpmnFactory),
          newCamundaErrorEventDefinition = createCamundaErrorEventDefinition(value, newError, extensionElements, bpmnFactory);
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: rootElement,
            properties: {
              rootElements: [...rootElement.get('rootElements'), newError]
            }
          }
        });
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: extensionElements,
            properties: {
              values: [...extensionElements.get('values'), newCamundaErrorEventDefinition]
            }
          }
        });
      }
    }

    // camunda:Field
    if (type === CAMUNDA_FIELD_TYPE) {
      const oldCamundaFields = findExtensions(businessObject, ['camunda:Field']);
      const newCamundaFields = [];
      if (oldCamundaFields.length) {
        oldCamundaFields.forEach(camundaField => {
          if (camundaField.name === name) {
            newCamundaFields.push(createCamundaFieldInjection(binding, value, bpmnFactory));
          } else {
            newCamundaFields.push(camundaField);
          }
        });
      } else {
        newCamundaFields.push(createCamundaFieldInjection(binding, value, bpmnFactory));
      }
      const values = extensionElements.get('values').filter(value => !oldCamundaFields.includes(value));
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          moddleElement: extensionElements,
          properties: {
            values: [...values, ...newCamundaFields]
          }
        }
      });
    }

    // camunda:Property
    if (type === CAMUNDA_PROPERTY_TYPE) {
      let camundaProperties;
      if (scope) {
        camundaProperties = businessObject.get('properties');
      } else {
        camundaProperties = findExtension(extensionElements, 'camunda:Properties');
      }
      if (!camundaProperties) {
        camundaProperties = createElement('camunda:Properties', null, businessObject, bpmnFactory);
        if (scope) {
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: businessObject,
              properties: {
                properties: camundaProperties
              }
            }
          });
        } else {
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: extensionElements,
              properties: {
                values: [...extensionElements.get('values'), camundaProperties]
              }
            }
          });
        }
      }
      const oldCamundaProperty = findCamundaProperty(camundaProperties, binding);
      const newCamundaProperty = createCamundaProperty(binding, value, bpmnFactory);
      const values = camundaProperties.get('values').filter(value => value !== oldCamundaProperty);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          moddleElement: camundaProperties,
          properties: {
            values: [...values, newCamundaProperty]
          }
        }
      });
    }
    if (IO_BINDING_TYPES.includes(type)) {
      let inputOutput;
      if (scope) {
        inputOutput = businessObject.get('inputOutput');
      } else {
        inputOutput = findExtension(extensionElements, 'camunda:InputOutput');
      }
      if (!inputOutput) {
        inputOutput = createElement('camunda:InputOutput', null, businessObject, bpmnFactory);
        if (scope) {
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: businessObject,
              properties: {
                inputOutput
              }
            }
          });
        } else {
          commands.push({
            cmd: 'element.updateModdleProperties',
            context: {
              element,
              moddleElement: extensionElements,
              properties: {
                values: [...extensionElements.get('values'), inputOutput]
              }
            }
          });
        }
      }

      // camunda:InputParameter
      if (type === CAMUNDA_INPUT_PARAMETER_TYPE$1) {
        const oldCamundaInputParameter = findInputParameter(inputOutput, binding);
        const newCamundaInputParameter = createInputParameter(binding, value, bpmnFactory);
        const values = inputOutput.get('camunda:inputParameters').filter(value => value !== oldCamundaInputParameter);
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: inputOutput,
            properties: {
              'camunda:inputParameters': [...values, newCamundaInputParameter]
            }
          }
        });
      }

      // camunda:OutputParameter
      if (type === CAMUNDA_OUTPUT_PARAMETER_TYPE$1) {
        const oldCamundaOutputParameter = findOutputParameter(inputOutput, binding);
        const newCamundaOutputParameter = createOutputParameter(binding, value, bpmnFactory);
        const values = inputOutput.get('camunda:outputParameters').filter(value => value !== oldCamundaOutputParameter);
        commands.push({
          cmd: 'element.updateModdleProperties',
          context: {
            element,
            moddleElement: inputOutput,
            properties: {
              'camunda:outputParameters': [...values, newCamundaOutputParameter]
            }
          }
        });
      }
    }

    // camunda:In and camunda:Out
    if (IN_OUT_BINDING_TYPES.includes(type)) {
      const oldCamundaInOut = findCamundaInOut(businessObject, binding);
      let newCamundaInOut;
      if (type === CAMUNDA_IN_TYPE) {
        newCamundaInOut = createCamundaIn(binding, value, bpmnFactory);
      } else if (type === CAMUNDA_OUT_TYPE) {
        newCamundaInOut = createCamundaOut(binding, value, bpmnFactory);
      } else {
        newCamundaInOut = createCamundaInWithBusinessKey(value, bpmnFactory);
      }
      const values = extensionElements.get('values').filter(value => value !== oldCamundaInOut);
      commands.push({
        cmd: 'element.updateModdleProperties',
        context: {
          element,
          moddleElement: extensionElements,
          properties: {
            values: [...values, newCamundaInOut]
          }
        }
      });
    }
    if (commands.length) {
      commandStack.execute('element-templates.multi-command-executor', commands);
      return;
    }

    // should never throw as templates are validated beforehand
    throw unknownBindingError(element, property);
  };
}
function propertyValidator(translate, property) {
  return function validate(value) {
    const {
      constraints = {}
    } = property;
    const {
      maxLength,
      minLength,
      notEmpty
    } = constraints;
    if (notEmpty && isEmptyString(value)) {
      return translate('Must not be empty.');
    }
    if (property.feel && isFeel(value)) {
      return;
    }
    if (maxLength && value.length > maxLength) {
      return translate('Must have max length {maxLength}.', {
        maxLength
      });
    }
    if (minLength && value.length < minLength) {
      return translate('Must have min length {minLength}.', {
        minLength
      });
    }
    let {
      pattern
    } = constraints;
    if (pattern) {
      let message;
      if (!isString(pattern)) {
        message = pattern.message;
        pattern = pattern.value;
      }
      if (!matchesPattern(value, pattern)) {
        return message || translate('Must match pattern {pattern}.', {
          pattern
        });
      }
    }
  };
}
function getScopeBusinessObject(businessObject, scope) {
  const {
    id,
    type
  } = scope;
  if (type === 'bpmn:Error') {
    // retrieve error through referenced error event definition
    const errorEventDefinition = findCamundaErrorEventDefinition(businessObject, id);
    if (errorEventDefinition) {
      return errorEventDefinition.get('errorRef');
    }
  }
  return findExtension(businessObject, type);
}
function unknownBindingError(element, property) {
  const businessObject = getBusinessObject(element);
  const id = businessObject.get('id');
  const {
    binding
  } = property;
  const {
    type
  } = binding;
  return new Error(`unknown binding <${type}> for element <${id}>, this should never happen`);
}
function isEmptyString(string) {
  return !string || !string.trim().length;
}
function matchesPattern(string, pattern) {
  return new RegExp(pattern).test(string);
}
function groupByGroupId(properties) {
  return groupBy(properties, 'group');
}
function findCustomGroup(groups, id) {
  return find(groups, g => g.id === id);
}
function isFeel(value) {
  return isString(value) && value.trim().startsWith('=');
}

function ErrorProperties(props) {
  const {
    element,
    index,
    property,
    groups
  } = props;
  const {
    binding,
    label
  } = property;
  const {
    errorRef
  } = binding;
  const businessObject = getBusinessObject(element),
    errorEventDefinitions = findExtensions(businessObject, ['camunda:ErrorEventDefinition']);
  if (!errorEventDefinitions.length) {
    return;
  }
  const errorEventDefinition = findCamundaErrorEventDefinition(element, errorRef);
  const id = `${element.id}-error-${index}`;
  let entries = [];
  const errorGroup = groups.find(({
    id
  }) => id === 'CamundaPlatform__Errors');
  const originalItem = errorGroup.items.find(({
    entries
  }) => entries[0].errorEventDefinition === errorEventDefinition);
  entries = originalItem.entries;

  // (1) remove global error referenced entry
  // entries.shift();
  entries = removeEntry$1(entries, '-errorRef');

  // (2) remove throw expression input
  // entries.pop();
  entries = removeEntry$1(entries, '-expression');

  // (3) add disabled throw expression input
  entries.push({
    id: `${id}-expression`,
    component: Expression,
    errorEventDefinition,
    property
  });
  const item = {
    id,
    label: label || getErrorLabel(errorEventDefinition),
    entries
  };
  return item;
}
function Expression(props) {
  const {
    errorEventDefinition,
    id
  } = props;
  const translate = useService('translate');
  const debounce = useService('debounceInput');
  const setValue = () => {};
  const getValue = () => {
    return errorEventDefinition.get('camunda:expression');
  };
  return TextFieldEntry({
    element: errorEventDefinition,
    id,
    label: translate('Throw expression'),
    getValue,
    setValue,
    debounce,
    disabled: true
  });
}
function removeEntry$1(entries, suffix) {
  const entry = entries.find(({
    id
  }) => id.endsWith(suffix));
  return without(entries, entry);
}
function getErrorLabel(errorEventDefinition) {
  const error = errorEventDefinition.get('errorRef');
  if (!error) {
    return '<no reference>';
  }
  const errorCode = error.get('errorCode'),
    name = error.get('name') || '<unnamed>';
  if (errorCode) {
    return `${name} (code = ${errorCode})`;
  }
  return name;
}

function InputProperties(props) {
  const {
    element,
    index,
    property,
    groups
  } = props;
  const {
    binding,
    description,
    label
  } = property;
  const {
    name
  } = binding;
  const businessObject = getBusinessObject(element),
    inputOutput = findExtension(businessObject, 'camunda:InputOutput');
  const inputParameter = inputOutput && findInputParameter(inputOutput, binding);
  const id = `${element.id}-inputParameter-${index}`;
  let entries = [];
  if (inputParameter) {
    const inputGroup = groups.find(({
      id
    }) => id === 'CamundaPlatform__Input');
    const item = inputGroup.items.find(({
      entries
    }) => entries[0].parameter === inputParameter);
    entries = item.entries;

    // (1) remove name entry
    entries = removeEntry(entries, '-name');
  }

  // (2) add local variable assignment entry
  entries.unshift({
    id: `${id}-local-variable-assignment`,
    component: LocalVariableAssignment,
    inputParameter,
    property
  });

  // (3) add description entry
  if (description) {
    entries.unshift({
      id: `${id}-description`,
      component: Description$1,
      text: description
    });
  }

  // @barmac: binding#name is required so there is no third option
  const item = {
    id,
    label: label || name,
    entries
  };
  return item;
}

// TODO(philippfromme): add text entry to properties-panel
function Description$1(props) {
  const {
    id,
    text
  } = props;
  return jsx("div", {
    class: "bio-properties-panel-entry",
    "data-entry-id": id,
    children: jsx("div", {
      class: "bio-properties-panel-description",
      children: jsx(PropertyDescription, {
        description: text
      })
    })
  });
}
function LocalVariableAssignment(props) {
  const {
    element,
    id,
    property,
    inputParameter
  } = props;
  const {
    binding
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    modeling = useService('modeling'),
    translate = useService('translate');
  const getValue = () => {
    return inputParameter;
  };
  const setValue = value => {
    if (value) {
      addInputParameter(element, property, bpmnFactory, modeling);
    } else {
      removeInputParameter(element, binding, modeling);
    }
  };
  return ToggleSwitchEntry({
    id,
    label: translate('Local variable assignment'),
    switcherLabel: inputParameter ? translate('On') : translate('Off'),
    description: inputParameter ? '' : translate('Parameter won\'t be created as local variable.'),
    getValue,
    setValue
  });
}
function addInputParameter(element, property, bpmnFactory, modeling) {
  const {
    binding,
    value
  } = property;
  const businessObject = getBusinessObject(element);
  const extensionElements = businessObject.get('extensionElements');
  const inputOutput = findExtension(businessObject, 'camunda:InputOutput');
  let updatedBusinessObject, update;
  if (!extensionElements) {
    updatedBusinessObject = businessObject;
    const extensionElements = createExtensionElements$1(businessObject, bpmnFactory),
      inputOutput = createInputOutput$1(binding, value, bpmnFactory, extensionElements);
    extensionElements.values.push(inputOutput);
    update = {
      extensionElements
    };
  } else if (!inputOutput) {
    updatedBusinessObject = extensionElements;
    const inputOutput = createInputOutput$1(binding, value, bpmnFactory, extensionElements);
    update = {
      values: extensionElements.get('values').concat(inputOutput)
    };
  } else {
    updatedBusinessObject = inputOutput;
    const inputParameter = createInputParameter(binding, value, bpmnFactory);
    inputParameter.$parent = inputOutput;
    update = {
      inputParameters: inputOutput.get('camunda:inputParameters').concat(inputParameter)
    };
  }
  modeling.updateModdleProperties(element, updatedBusinessObject, update);
}
function removeInputParameter(element, binding, modeling) {
  const businessObject = getBusinessObject(element);
  const inputOutput = findExtension(businessObject, 'camunda:InputOutput'),
    inputParameters = inputOutput.get('camunda:inputParameters');
  const inputParameter = findInputParameter(inputOutput, binding);
  modeling.updateModdleProperties(element, inputOutput, {
    inputParameters: without(inputParameters, inputParameter)
  });
}
function removeEntry(entries, suffix) {
  const entry = entries.find(({
    id
  }) => id.endsWith(suffix));
  return without(entries, entry);
}
function createExtensionElements$1(businessObject, bpmnFactory) {
  return createElement('bpmn:ExtensionElements', {
    values: []
  }, businessObject, bpmnFactory);
}
function createInputOutput$1(binding, value, bpmnFactory, extensionElements) {
  const inputParameter = createInputParameter(binding, value, bpmnFactory);
  const inputOutput = createElement('camunda:InputOutput', {
    inputParameters: [inputParameter],
    outputParameters: []
  }, extensionElements, bpmnFactory);
  inputParameter.$parent = inputOutput;
  return inputOutput;
}

const SPACE_REGEX = /\s/;
function containsSpace(value) {
  return SPACE_REGEX.test(value);
}

function OutputProperties(props) {
  const {
    element,
    index,
    injector,
    property
  } = props;
  const {
    binding,
    description,
    label
  } = property;
  const {
    name
  } = binding;
  const businessObject = getBusinessObject(element),
    inputOutput = findExtension(businessObject, 'camunda:InputOutput');
  const translate = injector.get('translate');
  const outputParameter = inputOutput && findOutputParameter(inputOutput, binding);
  const id = `${element.id}-outputParameter-${index}`;
  let entries = [];

  // (1) add description entry
  if (description) {
    entries.push({
      id: `${id}-description`,
      component: Description,
      text: description
    });
  }

  // (2) add local variable assignment entry
  entries.push({
    id: `${id}-local-variable-assignment`,
    component: ProcessVariableAssignment,
    outputParameter,
    property
  });
  if (outputParameter) {
    // (3) add assign to process variable entry
    entries.push({
      id: `${id}-assign-to-process-variable`,
      component: AssignToProcessVariable,
      property
    });
  }
  const item = {
    id,
    label: label || name || translate('<unnamed>'),
    entries
  };
  return item;
}

// TODO(philippfromme): add text entry to properties-panel
function Description(props) {
  const {
    id,
    text
  } = props;
  return jsx("div", {
    class: "bio-properties-panel-entry",
    "data-entry-id": id,
    children: jsx("div", {
      class: "bio-properties-panel-description",
      children: jsx(PropertyDescription, {
        description: text
      })
    })
  });
}
function ProcessVariableAssignment(props) {
  const {
    element,
    id,
    property,
    outputParameter
  } = props;
  const {
    binding
  } = property;
  const bpmnFactory = useService('bpmnFactory'),
    modeling = useService('modeling'),
    translate = useService('translate');
  const getValue = () => {
    return outputParameter;
  };
  const setValue = value => {
    if (value) {
      addOutputParameter(element, property, bpmnFactory, modeling);
    } else {
      removeOutputParameter(element, binding, modeling);
    }
  };
  return ToggleSwitchEntry({
    id,
    label: translate('Process variable assignment'),
    switcherLabel: outputParameter ? translate('On') : translate('Off'),
    description: outputParameter ? '' : translate('Parameter won\'t be available in process scope.'),
    getValue,
    setValue
  });
}
function AssignToProcessVariable(props) {
  const {
    element,
    id,
    property
  } = props;
  const {
    binding
  } = property;
  const inputOutput = findExtension(element, 'camunda:InputOutput'),
    outputParameter = findOutputParameter(inputOutput, binding);
  const commandStack = useService('commandStack'),
    debounce = useService('debounceInput'),
    translate = useService('translate');
  const setValue = value => {
    commandStack.execute('element.updateModdleProperties', {
      element,
      moddleElement: outputParameter,
      properties: {
        name: value
      }
    });
  };
  const getValue = () => {
    return outputParameter.get('camunda:name');
  };
  const validate = value => {
    if (!value) {
      return translate('Process variable name must not be empty.');
    } else if (containsSpace(value)) {
      return translate('Process variable name must not contain spaces.');
    }
  };
  return TextFieldEntry({
    debounce,
    element: outputParameter,
    id,
    label: translate('Assign to process variable'),
    getValue,
    setValue,
    validate
  });
}
function addOutputParameter(element, property, bpmnFactory, modeling) {
  const {
    binding,
    value
  } = property;
  const businessObject = getBusinessObject(element);
  const extensionElements = businessObject.get('extensionElements');
  const inputOutput = findExtension(businessObject, 'camunda:InputOutput');
  let updatedBusinessObject, update;
  if (!extensionElements) {
    updatedBusinessObject = businessObject;
    const extensionElements = createExtensionElements(businessObject, bpmnFactory),
      inputOutput = createInputOutput(binding, value, bpmnFactory, extensionElements);
    extensionElements.values.push(inputOutput);
    update = {
      extensionElements
    };
  } else if (!inputOutput) {
    updatedBusinessObject = extensionElements;
    const inputOutput = createInputOutput(binding, value, bpmnFactory, extensionElements);
    update = {
      values: extensionElements.get('values').concat(inputOutput)
    };
  } else {
    updatedBusinessObject = inputOutput;
    const outputParameter = createOutputParameter(binding, value, bpmnFactory);
    outputParameter.$parent = inputOutput;
    update = {
      outputParameters: inputOutput.get('camunda:outputParameters').concat(outputParameter)
    };
  }
  modeling.updateModdleProperties(element, updatedBusinessObject, update);
}
function removeOutputParameter(element, binding, modeling) {
  const businessObject = getBusinessObject(element);
  const inputOutput = findExtension(businessObject, 'camunda:InputOutput'),
    outputParameters = inputOutput.get('camunda:outputParameters');
  const outputParameter = findOutputParameter(inputOutput, binding);
  modeling.updateModdleProperties(element, inputOutput, {
    outputParameters: without(outputParameters, outputParameter)
  });
}
function createExtensionElements(businessObject, bpmnFactory) {
  return createElement('bpmn:ExtensionElements', {
    values: []
  }, businessObject, bpmnFactory);
}
function createInputOutput(binding, value, bpmnFactory, extensionElements) {
  const outputParameter = createOutputParameter(binding, value, bpmnFactory);
  const inputOutput = createElement('camunda:InputOutput', {
    inputParameters: [],
    outputParameters: [outputParameter]
  }, extensionElements, bpmnFactory);
  outputParameter.$parent = inputOutput;
  return inputOutput;
}

const CAMUNDA_ERROR_EVENT_DEFINITION_TYPE = 'camunda:errorEventDefinition',
  CAMUNDA_INPUT_PARAMETER_TYPE = 'camunda:inputParameter',
  CAMUNDA_OUTPUT_PARAMETER_TYPE = 'camunda:outputParameter';
const LOWER_PRIORITY = 300;
const ALWAYS_DISPLAYED_GROUPS = ['general', 'documentation', 'multiInstance'];
class ElementTemplatesPropertiesProvider {
  constructor(elementTemplates, propertiesPanel, injector) {
    propertiesPanel.registerProvider(LOWER_PRIORITY, this);
    this._elementTemplates = elementTemplates;
    this._injector = injector;
  }
  getGroups(element) {
    return groups => {
      const injector = this._injector;
      if (!this._shouldShowTemplateProperties(element)) {
        return groups;
      }
      const translate = injector.get('translate');

      // (0) Copy provided groups
      groups = groups.slice();
      const templatesGroup = {
        element,
        id: 'ElementTemplates__Template',
        label: translate('Template'),
        component: createElementTemplatesGroup({
          getTemplateId
        }),
        entries: TemplateProps({
          element,
          elementTemplates: this._elementTemplates,
          getTemplateId,
          getTemplateVersion
        })
      };

      // (1) Add templates group
      addGroupsAfter(ALWAYS_DISPLAYED_GROUPS, groups, [templatesGroup]);
      const elementTemplate = this._elementTemplates.get(element);
      if (elementTemplate) {
        const templateSpecificGroups = [].concat(createInputGroup(element, elementTemplate, injector, groups) || [], createOutputGroup(element, elementTemplate, injector) || [], createErrorGroup(element, elementTemplate, injector, groups) || [], CustomProperties({
          element,
          elementTemplate,
          injector
        }));

        // (2) add template-specific properties groups
        addGroupsAfter('ElementTemplates__Template', groups, templateSpecificGroups);
      }

      // (3) apply entries visible
      if (getTemplateId(element)) {
        groups = filterWithEntriesVisible(elementTemplate || {}, groups);
      }
      return groups;
    };
  }
  _shouldShowTemplateProperties(element) {
    return getTemplateId(element) || this._elementTemplates.getAll(element).length;
  }
}
ElementTemplatesPropertiesProvider.$inject = ['elementTemplates', 'propertiesPanel', 'injector'];

// helper /////////////////////

function createInputGroup(element, elementTemplate, injector, groups) {
  const translate = injector.get('translate');
  const group = {
    label: translate('Inputs'),
    id: 'ElementTemplates__Input',
    component: ListGroup,
    items: []
  };
  const properties = elementTemplate.properties.filter(({
    binding,
    type
  }) => {
    return !type && binding.type === CAMUNDA_INPUT_PARAMETER_TYPE;
  });
  properties.forEach((property, index) => {
    const item = InputProperties({
      element,
      index,
      property,
      groups
    });
    if (item) {
      group.items.push(item);
    }
  });

  // remove if empty
  if (!group.items.length) {
    return null;
  }
  return group;
}
function createOutputGroup(element, elementTemplate, injector, groups) {
  const translate = injector.get('translate');
  const group = {
    label: translate('Outputs'),
    id: 'ElementTemplates__Output',
    component: ListGroup,
    items: []
  };
  const properties = elementTemplate.properties.filter(({
    binding,
    type
  }) => {
    return !type && binding.type === CAMUNDA_OUTPUT_PARAMETER_TYPE;
  });
  properties.forEach((property, index) => {
    const item = OutputProperties({
      element,
      index,
      property,
      injector
    });
    if (item) {
      group.items.push(item);
    }
  });

  // remove if empty
  if (!group.items.length) {
    return null;
  }
  return group;
}
function createErrorGroup(element, elementTemplate, injector, groups) {
  const translate = injector.get('translate');
  const group = {
    label: translate('Errors'),
    id: 'ElementTemplates__Error',
    component: ListGroup,
    items: []
  };
  const properties = elementTemplate.properties.filter(({
    binding,
    type
  }) => {
    return !type && binding.type === CAMUNDA_ERROR_EVENT_DEFINITION_TYPE;
  });
  properties.forEach((property, index) => {
    const item = ErrorProperties({
      element,
      index,
      property,
      groups
    });
    if (item) {
      group.items.push(item);
    }
  });

  // remove if empty
  if (!group.items.length) {
    return null;
  }
  return group;
}

/**
 *
 * @param {string|string[]} idOrIds
 * @param {Array<{ id: string }} groups
 * @param {Array<{ id: string }>} groupsToAdd
 */
function addGroupsAfter(idOrIds, groups, groupsToAdd) {
  let ids = idOrIds;
  if (!Array.isArray(idOrIds)) {
    ids = [idOrIds];
  }

  // find index of last group with provided id
  const index = groups.reduce((acc, group, index) => {
    return ids.includes(group.id) ? index : acc;
  }, -1);
  if (index !== -1) {
    groups.splice(index + 1, 0, ...groupsToAdd);
  } else {
    // add in the beginning if group with provided id is missing
    groups.unshift(...groupsToAdd);
  }
}
function filterWithEntriesVisible(template, groups) {
  if (!template.entriesVisible) {
    return groups.filter(group => {
      return ALWAYS_DISPLAYED_GROUPS.includes(group.id) || group.id.startsWith('ElementTemplates__');
    });
  }
  return groups;
}

var propertiesProviderModule = {
  __depends__: [translateModule, CamundaPlatformPropertiesProviderModule],
  __init__: ['elementTemplatesPropertiesProvider'],
  elementTemplatesPropertiesProvider: ['type', ElementTemplatesPropertiesProvider]
};

var index = {
  __depends__: [coreModule, propertiesProviderModule]
};

/**
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.
 *
 * Camunda licenses this file to you under the MIT; you may not use this file
 * except in compliance with the MIT License.
 */

function validate ({
  templates = []
}) {
  // We use the ElementTemplates Module without the required bpmn-js modules
  // As we only use it to facilitate template ID and version lookup,
  // access to commandstack etc. is not required
  const eventBus = new EventBus();
  const elementTemplates = new ElementTemplates(null, null, eventBus, null, null);
  elementTemplates.set(templates);
  function check(node, reporter) {
    if (!is(node, 'bpmn:FlowElement')) {
      return;
    }
    let template = elementTemplates.get(node);
    const templateId = elementTemplates._getTemplateId(node);
    const templateVersion = elementTemplates._getTemplateVersion(node);

    // Handle missing template
    if (templateId && !template) {
      const versionText = templateVersion ? ` (version: ${templateVersion})` : '';
      reporter.report(node.id, `Linked element template '${templateId}'${versionText} not found`, {
        name: node.name
      });
      return;
    }
    if (!template) {
      return;
    }
    template = applyConditions(node, template);

    // Check attributes
    template.properties.forEach(property => {
      const value = getPropertyValue(node, property);
      const error = validateProperty(value, property);
      if (!error) {
        return;
      }
      reporter.report(node.id, error, {
        propertiesPanel: {
          entryIds: [getEntryId(property, template)]
        },
        name: node.name
      });
    });
  }
  return {
    check
  };
}

// helpers //////////////////////

function getEntryId(property, template) {
  const index = template.properties.filter(p => p.group === property.group).indexOf(property);
  const path = ['custom-entry', template.id];
  if (property.group) {
    path.push(property.group);
  }
  path.push(index);
  return path.join('-');
}

/**
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.
 *
 * Camunda licenses this file to you under the MIT; you may not use this file
 * except in compliance with the MIT License.
 */

function compatibility ({
  templates = []
}) {
  // We use the ElementTemplates Module without the required bpmn-js modules
  // As we only use it to facilitate template ID and version lookup,
  // access to commandstack etc. is not required
  const eventBus = new EventBus();
  const elementTemplates = new ElementTemplates(null, null, eventBus, null, null);
  elementTemplates.set(templates);
  function isUpdateAvailable(template) {
    const latestTemplate = elementTemplates.getLatest(template.id, {
      deprecated: true
    })[0];
    if (latestTemplate && latestTemplate !== template) {
      return true;
    }
    return false;
  }
  function check(node, reporter) {
    if (is(node, 'bpmn:Definitions')) {
      elementTemplates.setEngines(getEnginesConfig(node));
    }
    if (!is(node, 'bpmn:FlowElement')) {
      return;
    }
    let template = elementTemplates.get(node);
    if (!template) {
      return;
    }

    // Check compatibility
    if (template.engines) {
      const incomp = elementTemplates.getIncompatibleEngines(template);
      Object.keys(incomp).forEach(engine => {
        reporter.report(node.id, getIncompatibilityText(engine, incomp[engine], isUpdateAvailable(template)), {
          name: node.name
        });
      });
    }
  }
  return {
    check
  };
}

// helpers //////////////////////

function getEnginesConfig(definitions) {
  const engines = {};
  const executionPlatform = definitions.get('modeler:executionPlatform');
  const executionPlatformVersion = definitions.get('modeler:executionPlatformVersion');
  if (executionPlatform === 'Camunda Cloud' && executionPlatformVersion) {
    engines.camunda = executionPlatformVersion;
  }
  return engines;
}
function getIncompatibilityText(engine, {
  actual,
  required
}, updateAvailable) {
  const message = `Element template incompatible with current <${engine}> environment. ` + `Requires '${engine} ${required}'; found '${actual}'. ` + `${updateAvailable ? 'Update available.' : ''}`;
  return message.trim();
}

/**
 * Moddle base element.
 */
function Base() {}
Base.prototype.get = function (name) {
  return this.$model.properties.get(this, name);
};
Base.prototype.set = function (name, value) {
  this.$model.properties.set(this, name, value);
};

/**
 * A model element factory.
 *
 * @param {Moddle} model
 * @param {Properties} properties
 */
function Factory(model, properties) {
  this.model = model;
  this.properties = properties;
}
Factory.prototype.createType = function (descriptor) {
  var model = this.model;
  var props = this.properties,
    prototype = Object.create(Base.prototype);

  // initialize default values
  forEach(descriptor.properties, function (p) {
    if (!p.isMany && p.default !== undefined) {
      prototype[p.name] = p.default;
    }
  });
  props.defineModel(prototype, model);
  props.defineDescriptor(prototype, descriptor);
  var name = descriptor.ns.name;

  /**
   * The new type constructor
   */
  function ModdleElement(attrs) {
    props.define(this, '$type', {
      value: name,
      enumerable: true
    });
    props.define(this, '$attrs', {
      value: {}
    });
    props.define(this, '$parent', {
      writable: true
    });
    forEach(attrs, bind(function (val, key) {
      this.set(key, val);
    }, this));
  }
  ModdleElement.prototype = prototype;
  ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;

  // static links
  props.defineModel(ModdleElement, model);
  props.defineDescriptor(ModdleElement, descriptor);
  return ModdleElement;
};

/**
 * Built-in moddle types
 */
var BUILTINS = {
  String: true,
  Boolean: true,
  Integer: true,
  Real: true,
  Element: true
};

/**
 * Converters for built in types from string representations
 */
var TYPE_CONVERTERS = {
  String: function (s) {
    return s;
  },
  Boolean: function (s) {
    return s === 'true';
  },
  Integer: function (s) {
    return parseInt(s, 10);
  },
  Real: function (s) {
    return parseFloat(s);
  }
};

/**
 * Convert a type to its real representation
 */
function coerceType(type, value) {
  var converter = TYPE_CONVERTERS[type];
  if (converter) {
    return converter(value);
  } else {
    return value;
  }
}

/**
 * Return whether the given type is built-in
 */
function isBuiltIn(type) {
  return !!BUILTINS[type];
}

/**
 * Return whether the given type is simple
 */
function isSimple(type) {
  return !!TYPE_CONVERTERS[type];
}

/**
 * Parses a namespaced attribute name of the form (ns:)localName to an object,
 * given a default prefix to assume in case no explicit namespace is given.
 *
 * @param {String} name
 * @param {String} [defaultPrefix] the default prefix to take, if none is present.
 *
 * @return {Object} the parsed name
 */
function parseName(name, defaultPrefix) {
  var parts = name.split(/:/),
    localName,
    prefix;

  // no prefix (i.e. only local name)
  if (parts.length === 1) {
    localName = name;
    prefix = defaultPrefix;
  }

  // prefix + local name
  else if (parts.length === 2) {
    localName = parts[1];
    prefix = parts[0];
  } else {
    throw new Error('expected <prefix:localName> or <localName>, got ' + name);
  }
  name = (prefix ? prefix + ':' : '') + localName;
  return {
    name: name,
    prefix: prefix,
    localName: localName
  };
}

/**
 * A utility to build element descriptors.
 */
function DescriptorBuilder(nameNs) {
  this.ns = nameNs;
  this.name = nameNs.name;
  this.allTypes = [];
  this.allTypesByName = {};
  this.properties = [];
  this.propertiesByName = {};
}
DescriptorBuilder.prototype.build = function () {
  return pick(this, ['ns', 'name', 'allTypes', 'allTypesByName', 'properties', 'propertiesByName', 'bodyProperty', 'idProperty']);
};

/**
 * Add property at given index.
 *
 * @param {Object} p
 * @param {Number} [idx]
 * @param {Boolean} [validate=true]
 */
DescriptorBuilder.prototype.addProperty = function (p, idx, validate) {
  if (typeof idx === 'boolean') {
    validate = idx;
    idx = undefined;
  }
  this.addNamedProperty(p, validate !== false);
  var properties = this.properties;
  if (idx !== undefined) {
    properties.splice(idx, 0, p);
  } else {
    properties.push(p);
  }
};
DescriptorBuilder.prototype.replaceProperty = function (oldProperty, newProperty, replace) {
  var oldNameNs = oldProperty.ns;
  var props = this.properties,
    propertiesByName = this.propertiesByName,
    rename = oldProperty.name !== newProperty.name;
  if (oldProperty.isId) {
    if (!newProperty.isId) {
      throw new Error('property <' + newProperty.ns.name + '> must be id property ' + 'to refine <' + oldProperty.ns.name + '>');
    }
    this.setIdProperty(newProperty, false);
  }
  if (oldProperty.isBody) {
    if (!newProperty.isBody) {
      throw new Error('property <' + newProperty.ns.name + '> must be body property ' + 'to refine <' + oldProperty.ns.name + '>');
    }

    // TODO: Check compatibility
    this.setBodyProperty(newProperty, false);
  }

  // validate existence and get location of old property
  var idx = props.indexOf(oldProperty);
  if (idx === -1) {
    throw new Error('property <' + oldNameNs.name + '> not found in property list');
  }

  // remove old property
  props.splice(idx, 1);

  // replacing the named property is intentional
  //
  //  * validate only if this is a "rename" operation
  //  * add at specific index unless we "replace"
  //
  this.addProperty(newProperty, replace ? undefined : idx, rename);

  // make new property available under old name
  propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
};
DescriptorBuilder.prototype.redefineProperty = function (p, targetPropertyName, replace) {
  var nsPrefix = p.ns.prefix;
  var parts = targetPropertyName.split('#');
  var name = parseName(parts[0], nsPrefix);
  var attrName = parseName(parts[1], name.prefix).name;
  var redefinedProperty = this.propertiesByName[attrName];
  if (!redefinedProperty) {
    throw new Error('refined property <' + attrName + '> not found');
  } else {
    this.replaceProperty(redefinedProperty, p, replace);
  }
  delete p.redefines;
};
DescriptorBuilder.prototype.addNamedProperty = function (p, validate) {
  var ns = p.ns,
    propsByName = this.propertiesByName;
  if (validate) {
    this.assertNotDefined(p, ns.name);
    this.assertNotDefined(p, ns.localName);
  }
  propsByName[ns.name] = propsByName[ns.localName] = p;
};
DescriptorBuilder.prototype.removeNamedProperty = function (p) {
  var ns = p.ns,
    propsByName = this.propertiesByName;
  delete propsByName[ns.name];
  delete propsByName[ns.localName];
};
DescriptorBuilder.prototype.setBodyProperty = function (p, validate) {
  if (validate && this.bodyProperty) {
    throw new Error('body property defined multiple times ' + '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
  }
  this.bodyProperty = p;
};
DescriptorBuilder.prototype.setIdProperty = function (p, validate) {
  if (validate && this.idProperty) {
    throw new Error('id property defined multiple times ' + '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
  }
  this.idProperty = p;
};
DescriptorBuilder.prototype.assertNotTrait = function (typeDescriptor) {
  const _extends = typeDescriptor.extends || [];
  if (_extends.length) {
    throw new Error(`cannot create <${typeDescriptor.name}> extending <${typeDescriptor.extends}>`);
  }
};
DescriptorBuilder.prototype.assertNotDefined = function (p, name) {
  var propertyName = p.name,
    definedProperty = this.propertiesByName[propertyName];
  if (definedProperty) {
    throw new Error('property <' + propertyName + '> already defined; ' + 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' + '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
  }
};
DescriptorBuilder.prototype.hasProperty = function (name) {
  return this.propertiesByName[name];
};
DescriptorBuilder.prototype.addTrait = function (t, inherited) {
  if (inherited) {
    this.assertNotTrait(t);
  }
  var typesByName = this.allTypesByName,
    types = this.allTypes;
  var typeName = t.name;
  if (typeName in typesByName) {
    return;
  }
  forEach(t.properties, bind(function (p) {
    // clone property to allow extensions
    p = assign({}, p, {
      name: p.ns.localName,
      inherited: inherited
    });
    Object.defineProperty(p, 'definedBy', {
      value: t
    });
    var replaces = p.replaces,
      redefines = p.redefines;

    // add replace/redefine support
    if (replaces || redefines) {
      this.redefineProperty(p, replaces || redefines, replaces);
    } else {
      if (p.isBody) {
        this.setBodyProperty(p);
      }
      if (p.isId) {
        this.setIdProperty(p);
      }
      this.addProperty(p);
    }
  }, this));
  types.push(t);
  typesByName[typeName] = t;
};

/**
 * A registry of Moddle packages.
 *
 * @param {Array<Package>} packages
 * @param {Properties} properties
 */
function Registry(packages, properties) {
  this.packageMap = {};
  this.typeMap = {};
  this.packages = [];
  this.properties = properties;
  forEach(packages, bind(this.registerPackage, this));
}
Registry.prototype.getPackage = function (uriOrPrefix) {
  return this.packageMap[uriOrPrefix];
};
Registry.prototype.getPackages = function () {
  return this.packages;
};
Registry.prototype.registerPackage = function (pkg) {
  // copy package
  pkg = assign({}, pkg);
  var pkgMap = this.packageMap;
  ensureAvailable(pkgMap, pkg, 'prefix');
  ensureAvailable(pkgMap, pkg, 'uri');

  // register types
  forEach(pkg.types, bind(function (descriptor) {
    this.registerType(descriptor, pkg);
  }, this));
  pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
  this.packages.push(pkg);
};

/**
 * Register a type from a specific package with us
 */
Registry.prototype.registerType = function (type, pkg) {
  type = assign({}, type, {
    superClass: (type.superClass || []).slice(),
    extends: (type.extends || []).slice(),
    properties: (type.properties || []).slice(),
    meta: assign(type.meta || {})
  });
  var ns = parseName(type.name, pkg.prefix),
    name = ns.name,
    propertiesByName = {};

  // parse properties
  forEach(type.properties, bind(function (p) {
    // namespace property names
    var propertyNs = parseName(p.name, ns.prefix),
      propertyName = propertyNs.name;

    // namespace property types
    if (!isBuiltIn(p.type)) {
      p.type = parseName(p.type, propertyNs.prefix).name;
    }
    assign(p, {
      ns: propertyNs,
      name: propertyName
    });
    propertiesByName[propertyName] = p;
  }, this));

  // update ns + name
  assign(type, {
    ns: ns,
    name: name,
    propertiesByName: propertiesByName
  });
  forEach(type.extends, bind(function (extendsName) {
    var extendsNameNs = parseName(extendsName, ns.prefix);
    var extended = this.typeMap[extendsNameNs.name];
    extended.traits = extended.traits || [];
    extended.traits.push(name);
  }, this));

  // link to package
  this.definePackage(type, pkg);

  // register
  this.typeMap[name] = type;
};

/**
 * Traverse the type hierarchy from bottom to top,
 * calling iterator with (type, inherited) for all elements in
 * the inheritance chain.
 *
 * @param {Object} nsName
 * @param {Function} iterator
 * @param {Boolean} [trait=false]
 */
Registry.prototype.mapTypes = function (nsName, iterator, trait) {
  var type = isBuiltIn(nsName.name) ? {
    name: nsName.name
  } : this.typeMap[nsName.name];
  var self = this;

  /**
   * Traverse the selected super type or trait
   *
   * @param {String} cls
   * @param {Boolean} [trait=false]
   */
  function traverse(cls, trait) {
    var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
    self.mapTypes(parentNs, iterator, trait);
  }

  /**
   * Traverse the selected trait.
   *
   * @param {String} cls
   */
  function traverseTrait(cls) {
    return traverse(cls, true);
  }

  /**
   * Traverse the selected super type
   *
   * @param {String} cls
   */
  function traverseSuper(cls) {
    return traverse(cls, false);
  }
  if (!type) {
    throw new Error('unknown type <' + nsName.name + '>');
  }
  forEach(type.superClass, trait ? traverseTrait : traverseSuper);

  // call iterator with (type, inherited=!trait)
  iterator(type, !trait);
  forEach(type.traits, traverseTrait);
};

/**
 * Returns the effective descriptor for a type.
 *
 * @param  {String} type the namespaced name (ns:localName) of the type
 *
 * @return {Descriptor} the resulting effective descriptor
 */
Registry.prototype.getEffectiveDescriptor = function (name) {
  var nsName = parseName(name);
  var builder = new DescriptorBuilder(nsName);
  this.mapTypes(nsName, function (type, inherited) {
    builder.addTrait(type, inherited);
  });
  var descriptor = builder.build();

  // define package link
  this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);
  return descriptor;
};
Registry.prototype.definePackage = function (target, pkg) {
  this.properties.define(target, '$pkg', {
    value: pkg
  });
};

// helpers ////////////////////////////

function ensureAvailable(packageMap, pkg, identifierKey) {
  var value = pkg[identifierKey];
  if (value in packageMap) {
    throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
  }
}

/**
 * A utility that gets and sets properties of model elements.
 *
 * @param {Model} model
 */
function Properties(model) {
  this.model = model;
}

/**
 * Sets a named property on the target element.
 * If the value is undefined, the property gets deleted.
 *
 * @param {Object} target
 * @param {String} name
 * @param {Object} value
 */
Properties.prototype.set = function (target, name, value) {
  if (!isString(name) || !name.length) {
    throw new TypeError('property name must be a non-empty string');
  }
  var property = this.getProperty(target, name);
  var propertyName = property && property.name;
  if (isUndefined(value)) {
    // unset the property, if the specified value is undefined;
    // delete from $attrs (for extensions) or the target itself
    if (property) {
      delete target[propertyName];
    } else {
      delete target.$attrs[stripGlobal(name)];
    }
  } else {
    // set the property, defining well defined properties on the fly
    // or simply updating them in target.$attrs (for extensions)
    if (property) {
      if (propertyName in target) {
        target[propertyName] = value;
      } else {
        defineProperty(target, property, value);
      }
    } else {
      target.$attrs[stripGlobal(name)] = value;
    }
  }
};

/**
 * Returns the named property of the given element
 *
 * @param  {Object} target
 * @param  {String} name
 *
 * @return {Object}
 */
Properties.prototype.get = function (target, name) {
  var property = this.getProperty(target, name);
  if (!property) {
    return target.$attrs[stripGlobal(name)];
  }
  var propertyName = property.name;

  // check if access to collection property and lazily initialize it
  if (!target[propertyName] && property.isMany) {
    defineProperty(target, property, []);
  }
  return target[propertyName];
};

/**
 * Define a property on the target element
 *
 * @param  {Object} target
 * @param  {String} name
 * @param  {Object} options
 */
Properties.prototype.define = function (target, name, options) {
  if (!options.writable) {
    var value = options.value;

    // use getters for read-only variables to support ES6 proxies
    // cf. https://github.com/bpmn-io/internal-docs/issues/386
    options = assign({}, options, {
      get: function () {
        return value;
      }
    });
    delete options.value;
  }
  Object.defineProperty(target, name, options);
};

/**
 * Define the descriptor for an element
 */
Properties.prototype.defineDescriptor = function (target, descriptor) {
  this.define(target, '$descriptor', {
    value: descriptor
  });
};

/**
 * Define the model for an element
 */
Properties.prototype.defineModel = function (target, model) {
  this.define(target, '$model', {
    value: model
  });
};

/**
 * Return property with the given name on the element.
 *
 * @param {any} target
 * @param {string} name
 *
 * @return {object | null} property
 */
Properties.prototype.getProperty = function (target, name) {
  var model = this.model;
  var property = model.getPropertyDescriptor(target, name);
  if (property) {
    return property;
  }
  if (name.includes(':')) {
    return null;
  }
  const strict = model.config.strict;
  if (typeof strict !== 'undefined') {
    const error = new TypeError(`unknown property <${name}> on <${target.$type}>`);
    if (strict) {
      throw error;
    } else {
      // eslint-disable-next-line no-undef
      typeof console !== 'undefined' && console.warn(error);
    }
  }
  return null;
};
function isUndefined(val) {
  return typeof val === 'undefined';
}
function defineProperty(target, property, value) {
  Object.defineProperty(target, property.name, {
    enumerable: !property.isReference,
    writable: true,
    value: value,
    configurable: true
  });
}
function stripGlobal(name) {
  return name.replace(/^:/, '');
}

// Moddle implementation /////////////////////////////////////////////////

/**
 * @class Moddle
 *
 * A model that can be used to create elements of a specific type.
 *
 * @example
 *
 * var Moddle = require('moddle');
 *
 * var pkg = {
 *   name: 'mypackage',
 *   prefix: 'my',
 *   types: [
 *     { name: 'Root' }
 *   ]
 * };
 *
 * var moddle = new Moddle([pkg]);
 *
 * @param {Array<Package>} packages the packages to contain
 *
 * @param { { strict?: boolean } } [config] moddle configuration
 */
function Moddle(packages, config = {}) {
  this.properties = new Properties(this);
  this.factory = new Factory(this, this.properties);
  this.registry = new Registry(packages, this.properties);
  this.typeCache = {};
  this.config = config;
}

/**
 * Create an instance of the specified type.
 *
 * @method Moddle#create
 *
 * @example
 *
 * var foo = moddle.create('my:Foo');
 * var bar = moddle.create('my:Bar', { id: 'BAR_1' });
 *
 * @param  {String|Object} descriptor the type descriptor or name know to the model
 * @param  {Object} attrs   a number of attributes to initialize the model instance with
 * @return {Object}         model instance
 */
Moddle.prototype.create = function (descriptor, attrs) {
  var Type = this.getType(descriptor);
  if (!Type) {
    throw new Error('unknown type <' + descriptor + '>');
  }
  return new Type(attrs);
};

/**
 * Returns the type representing a given descriptor
 *
 * @method Moddle#getType
 *
 * @example
 *
 * var Foo = moddle.getType('my:Foo');
 * var foo = new Foo({ 'id' : 'FOO_1' });
 *
 * @param  {String|Object} descriptor the type descriptor or name know to the model
 * @return {Object}         the type representing the descriptor
 */
Moddle.prototype.getType = function (descriptor) {
  var cache = this.typeCache;
  var name = isString(descriptor) ? descriptor : descriptor.ns.name;
  var type = cache[name];
  if (!type) {
    descriptor = this.registry.getEffectiveDescriptor(name);
    type = cache[name] = this.factory.createType(descriptor);
  }
  return type;
};

/**
 * Creates an any-element type to be used within model instances.
 *
 * This can be used to create custom elements that lie outside the meta-model.
 * The created element contains all the meta-data required to serialize it
 * as part of meta-model elements.
 *
 * @method Moddle#createAny
 *
 * @example
 *
 * var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
 *   value: 'bar'
 * });
 *
 * var container = moddle.create('my:Container', 'http://my', {
 *   any: [ foo ]
 * });
 *
 * // go ahead and serialize the stuff
 *
 *
 * @param  {String} name  the name of the element
 * @param  {String} nsUri the namespace uri of the element
 * @param  {Object} [properties] a map of properties to initialize the instance with
 * @return {Object} the any type instance
 */
Moddle.prototype.createAny = function (name, nsUri, properties) {
  var nameNs = parseName(name);
  var element = {
    $type: name,
    $instanceOf: function (type) {
      return type === this.$type;
    },
    get: function (key) {
      return this[key];
    },
    set: function (key, value) {
      set(this, [key], value);
    }
  };
  var descriptor = {
    name: name,
    isGeneric: true,
    ns: {
      prefix: nameNs.prefix,
      localName: nameNs.localName,
      uri: nsUri
    }
  };
  this.properties.defineDescriptor(element, descriptor);
  this.properties.defineModel(element, this);
  this.properties.define(element, 'get', {
    enumerable: false,
    writable: true
  });
  this.properties.define(element, 'set', {
    enumerable: false,
    writable: true
  });
  this.properties.define(element, '$parent', {
    enumerable: false,
    writable: true
  });
  this.properties.define(element, '$instanceOf', {
    enumerable: false,
    writable: true
  });
  forEach(properties, function (a, key) {
    if (isObject(a) && a.value !== undefined) {
      element[a.name] = a.value;
    } else {
      element[key] = a;
    }
  });
  return element;
};

/**
 * Returns a registered package by uri or prefix
 *
 * @return {Object} the package
 */
Moddle.prototype.getPackage = function (uriOrPrefix) {
  return this.registry.getPackage(uriOrPrefix);
};

/**
 * Returns a snapshot of all known packages
 *
 * @return {Object} the package
 */
Moddle.prototype.getPackages = function () {
  return this.registry.getPackages();
};

/**
 * Returns the descriptor for an element
 */
Moddle.prototype.getElementDescriptor = function (element) {
  return element.$descriptor;
};

/**
 * Returns true if the given descriptor or instance
 * represents the given type.
 *
 * May be applied to this, if element is omitted.
 */
Moddle.prototype.hasType = function (element, type) {
  if (type === undefined) {
    type = element;
    element = this;
  }
  var descriptor = element.$model.getElementDescriptor(element);
  return type in descriptor.allTypesByName;
};

/**
 * Returns the descriptor of an elements named property
 */
Moddle.prototype.getPropertyDescriptor = function (element, property) {
  return this.getElementDescriptor(element).propertiesByName[property];
};

/**
 * Returns a mapped type's descriptor
 */
Moddle.prototype.getTypeDescriptor = function (type) {
  return this.registry.typeMap[type];
};

var fromCharCode = String.fromCharCode;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig;
var ENTITY_MAPPING = {
  'amp': '&',
  'apos': '\'',
  'gt': '>',
  'lt': '<',
  'quot': '"'
};

// map UPPERCASE variants of supported special chars
Object.keys(ENTITY_MAPPING).forEach(function (k) {
  ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k];
});
function replaceEntities(_, d, x, z) {
  // reserved names, i.e. &nbsp;
  if (z) {
    if (hasOwnProperty.call(ENTITY_MAPPING, z)) {
      return ENTITY_MAPPING[z];
    } else {
      // fall back to original value
      return '&' + z + ';';
    }
  }

  // decimal encoded char
  if (d) {
    return fromCharCode(d);
  }

  // hex encoded char
  return fromCharCode(parseInt(x, 16));
}

/**
 * A basic entity decoder that can decode a minimal
 * sub-set of reserved names (&amp;) as well as
 * hex (&#xaaf;) and decimal (&#1231;) encoded characters.
 *
 * @param {string} s
 *
 * @return {string} decoded string
 */
function decodeEntities(s) {
  if (s.length > 3 && s.indexOf('&') !== -1) {
    return s.replace(ENTITY_PATTERN, replaceEntities);
  }
  return s;
}
var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';
function error$1(msg) {
  return new Error(msg);
}
function missingNamespaceForPrefix(prefix) {
  return 'missing namespace for prefix <' + prefix + '>';
}
function getter(getFn) {
  return {
    'get': getFn,
    'enumerable': true
  };
}
function cloneNsMatrix(nsMatrix) {
  var clone = {},
    key;
  for (key in nsMatrix) {
    clone[key] = nsMatrix[key];
  }
  return clone;
}
function uriPrefix(prefix) {
  return prefix + '$uri';
}
function buildNsMatrix(nsUriToPrefix) {
  var nsMatrix = {},
    uri,
    prefix;
  for (uri in nsUriToPrefix) {
    prefix = nsUriToPrefix[uri];
    nsMatrix[prefix] = prefix;
    nsMatrix[uriPrefix(prefix)] = uri;
  }
  return nsMatrix;
}
function noopGetContext() {
  return {
    line: 0,
    column: 0
  };
}
function throwFunc(err) {
  throw err;
}

/**
 * Creates a new parser with the given options.
 *
 * @constructor
 *
 * @param  {!Object<string, ?>=} options
 */
function Parser(options) {
  if (!this) {
    return new Parser(options);
  }
  var proxy = options && options['proxy'];
  var onText,
    onOpenTag,
    onCloseTag,
    onCDATA,
    onError = throwFunc,
    onWarning,
    onComment,
    onQuestion,
    onAttention;
  var getContext = noopGetContext;

  /**
   * Do we need to parse the current elements attributes for namespaces?
   *
   * @type {boolean}
   */
  var maybeNS = false;

  /**
   * Do we process namespaces at all?
   *
   * @type {boolean}
   */
  var isNamespace = false;

  /**
   * The caught error returned on parse end
   *
   * @type {Error}
   */
  var returnError = null;

  /**
   * Should we stop parsing?
   *
   * @type {boolean}
   */
  var parseStop = false;

  /**
   * A map of { uri: prefix } used by the parser.
   *
   * This map will ensure we can normalize prefixes during processing;
   * for each uri, only one prefix will be exposed to the handlers.
   *
   * @type {!Object<string, string>}}
   */
  var nsUriToPrefix;

  /**
   * Handle parse error.
   *
   * @param  {string|Error} err
   */
  function handleError(err) {
    if (!(err instanceof Error)) {
      err = error$1(err);
    }
    returnError = err;
    onError(err, getContext);
  }

  /**
   * Handle parse error.
   *
   * @param  {string|Error} err
   */
  function handleWarning(err) {
    if (!onWarning) {
      return;
    }
    if (!(err instanceof Error)) {
      err = error$1(err);
    }
    onWarning(err, getContext);
  }

  /**
   * Register parse listener.
   *
   * @param  {string}   name
   * @param  {Function} cb
   *
   * @return {Parser}
   */
  this['on'] = function (name, cb) {
    if (typeof cb !== 'function') {
      throw error$1('required args <name, cb>');
    }
    switch (name) {
      case 'openTag':
        onOpenTag = cb;
        break;
      case 'text':
        onText = cb;
        break;
      case 'closeTag':
        onCloseTag = cb;
        break;
      case 'error':
        onError = cb;
        break;
      case 'warn':
        onWarning = cb;
        break;
      case 'cdata':
        onCDATA = cb;
        break;
      case 'attention':
        onAttention = cb;
        break;
      // <!XXXXX zzzz="eeee">
      case 'question':
        onQuestion = cb;
        break;
      // <? ....  ?>
      case 'comment':
        onComment = cb;
        break;
      default:
        throw error$1('unsupported event: ' + name);
    }
    return this;
  };

  /**
   * Set the namespace to prefix mapping.
   *
   * @example
   *
   * parser.ns({
   *   'http://foo': 'foo',
   *   'http://bar': 'bar'
   * });
   *
   * @param  {!Object<string, string>} nsMap
   *
   * @return {Parser}
   */
  this['ns'] = function (nsMap) {
    if (typeof nsMap === 'undefined') {
      nsMap = {};
    }
    if (typeof nsMap !== 'object') {
      throw error$1('required args <nsMap={}>');
    }
    var _nsUriToPrefix = {},
      k;
    for (k in nsMap) {
      _nsUriToPrefix[k] = nsMap[k];
    }
    isNamespace = true;
    nsUriToPrefix = _nsUriToPrefix;
    return this;
  };

  /**
   * Parse xml string.
   *
   * @param  {string} xml
   *
   * @return {Error} returnError, if not thrown
   */
  this['parse'] = function (xml) {
    if (typeof xml !== 'string') {
      throw error$1('required args <xml=string>');
    }
    returnError = null;
    parse(xml);
    getContext = noopGetContext;
    parseStop = false;
    return returnError;
  };

  /**
   * Stop parsing.
   */
  this['stop'] = function () {
    parseStop = true;
  };

  /**
   * Parse string, invoking configured listeners on element.
   *
   * @param  {string} xml
   */
  function parse(xml) {
    var nsMatrixStack = isNamespace ? [] : null,
      nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null,
      _nsMatrix,
      nodeStack = [],
      anonymousNsCount = 0,
      tagStart = false,
      tagEnd = false,
      i = 0,
      j = 0,
      x,
      y,
      q,
      w,
      v,
      xmlns,
      elementName,
      _elementName,
      elementProxy;
    var attrsString = '',
      attrsStart = 0,
      cachedAttrs // false = parsed with errors, null = needs parsing
    ;

    /**
     * Parse attributes on demand and returns the parsed attributes.
     *
     * Return semantics: (1) `false` on attribute parse error,
     * (2) object hash on extracted attrs.
     *
     * @return {boolean|Object}
     */
    function getAttrs() {
      if (cachedAttrs !== null) {
        return cachedAttrs;
      }
      var nsUri,
        nsUriPrefix,
        nsName,
        defaultAlias = isNamespace && nsMatrix['xmlns'],
        attrList = isNamespace && maybeNS ? [] : null,
        i = attrsStart,
        s = attrsString,
        l = s.length,
        hasNewMatrix,
        newalias,
        value,
        alias,
        name,
        attrs = {},
        seenAttrs = {},
        skipAttr,
        w,
        j;
      parseAttr: for (; i < l; i++) {
        skipAttr = false;
        w = s.charCodeAt(i);
        if (w === 32 || w < 14 && w > 8) {
          // WHITESPACE={ \f\n\r\t\v}
          continue;
        }

        // wait for non whitespace character
        if (w < 65 || w > 122 || w > 90 && w < 97) {
          if (w !== 95 && w !== 58) {
            // char 95"_" 58":"
            handleWarning('illegal first char attribute name');
            skipAttr = true;
          }
        }

        // parse attribute name
        for (j = i + 1; j < l; j++) {
          w = s.charCodeAt(j);
          if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 46 ||
          // '.'
          w === 45 ||
          // '-'
          w === 95 // '_'
          ) {
            continue;
          }

          // unexpected whitespace
          if (w === 32 || w < 14 && w > 8) {
            // WHITESPACE
            handleWarning('missing attribute value');
            i = j;
            continue parseAttr;
          }

          // expected "="
          if (w === 61) {
            // "=" == 61
            break;
          }
          handleWarning('illegal attribute name char');
          skipAttr = true;
        }
        name = s.substring(i, j);
        if (name === 'xmlns:xmlns') {
          handleWarning('illegal declaration of xmlns');
          skipAttr = true;
        }
        w = s.charCodeAt(j + 1);
        if (w === 34) {
          // '"'
          j = s.indexOf('"', i = j + 2);
          if (j === -1) {
            j = s.indexOf('\'', i);
            if (j !== -1) {
              handleWarning('attribute value quote missmatch');
              skipAttr = true;
            }
          }
        } else if (w === 39) {
          // "'"
          j = s.indexOf('\'', i = j + 2);
          if (j === -1) {
            j = s.indexOf('"', i);
            if (j !== -1) {
              handleWarning('attribute value quote missmatch');
              skipAttr = true;
            }
          }
        } else {
          handleWarning('missing attribute value quotes');
          skipAttr = true;

          // skip to next space
          for (j = j + 1; j < l; j++) {
            w = s.charCodeAt(j + 1);
            if (w === 32 || w < 14 && w > 8) {
              // WHITESPACE
              break;
            }
          }
        }
        if (j === -1) {
          handleWarning('missing closing quotes');
          j = l;
          skipAttr = true;
        }
        if (!skipAttr) {
          value = s.substring(i, j);
        }
        i = j;

        // ensure SPACE follows attribute
        // skip illegal content otherwise
        // example a="b"c
        for (; j + 1 < l; j++) {
          w = s.charCodeAt(j + 1);
          if (w === 32 || w < 14 && w > 8) {
            // WHITESPACE
            break;
          }

          // FIRST ILLEGAL CHAR
          if (i === j) {
            handleWarning('illegal character after attribute end');
            skipAttr = true;
          }
        }

        // advance cursor to next attribute
        i = j + 1;
        if (skipAttr) {
          continue parseAttr;
        }

        // check attribute re-declaration
        if (name in seenAttrs) {
          handleWarning('attribute <' + name + '> already defined');
          continue;
        }
        seenAttrs[name] = true;
        if (!isNamespace) {
          attrs[name] = value;
          continue;
        }

        // try to extract namespace information
        if (maybeNS) {
          newalias = name === 'xmlns' ? 'xmlns' : name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:' ? name.substr(6) : null;

          // handle xmlns(:alias) assignment
          if (newalias !== null) {
            nsUri = decodeEntities(value);
            nsUriPrefix = uriPrefix(newalias);
            alias = nsUriToPrefix[nsUri];
            if (!alias) {
              // no prefix defined or prefix collision
              if (newalias === 'xmlns' || nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri) {
                // alocate free ns prefix
                do {
                  alias = 'ns' + anonymousNsCount++;
                } while (typeof nsMatrix[alias] !== 'undefined');
              } else {
                alias = newalias;
              }
              nsUriToPrefix[nsUri] = alias;
            }
            if (nsMatrix[newalias] !== alias) {
              if (!hasNewMatrix) {
                nsMatrix = cloneNsMatrix(nsMatrix);
                hasNewMatrix = true;
              }
              nsMatrix[newalias] = alias;
              if (newalias === 'xmlns') {
                nsMatrix[uriPrefix(alias)] = nsUri;
                defaultAlias = alias;
              }
              nsMatrix[nsUriPrefix] = nsUri;
            }

            // expose xmlns(:asd)="..." in attributes
            attrs[name] = value;
            continue;
          }

          // collect attributes until all namespace
          // declarations are processed
          attrList.push(name, value);
          continue;
        } /** end if (maybeNs) */

        // handle attributes on element without
        // namespace declarations
        w = name.indexOf(':');
        if (w === -1) {
          attrs[name] = value;
          continue;
        }

        // normalize ns attribute name
        if (!(nsName = nsMatrix[name.substring(0, w)])) {
          handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
          continue;
        }
        name = defaultAlias === nsName ? name.substr(w + 1) : nsName + name.substr(w);

        // end: normalize ns attribute name

        attrs[name] = value;
      }

      // handle deferred, possibly namespaced attributes
      if (maybeNS) {
        // normalize captured attributes
        for (i = 0, l = attrList.length; i < l; i++) {
          name = attrList[i++];
          value = attrList[i];
          w = name.indexOf(':');
          if (w !== -1) {
            // normalize ns attribute name
            if (!(nsName = nsMatrix[name.substring(0, w)])) {
              handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
              continue;
            }
            name = defaultAlias === nsName ? name.substr(w + 1) : nsName + name.substr(w);

            // end: normalize ns attribute name
          }
          attrs[name] = value;
        }

        // end: normalize captured attributes
      }
      return cachedAttrs = attrs;
    }

    /**
     * Extract the parse context { line, column, part }
     * from the current parser position.
     *
     * @return {Object} parse context
     */
    function getParseContext() {
      var splitsRe = /(\r\n|\r|\n)/g;
      var line = 0;
      var column = 0;
      var startOfLine = 0;
      var endOfLine = j;
      var match;
      var data;
      while (i >= startOfLine) {
        match = splitsRe.exec(xml);
        if (!match) {
          break;
        }

        // end of line = (break idx + break chars)
        endOfLine = match[0].length + match.index;
        if (endOfLine > i) {
          break;
        }

        // advance to next line
        line += 1;
        startOfLine = endOfLine;
      }

      // EOF errors
      if (i == -1) {
        column = endOfLine;
        data = xml.substring(j);
      } else
        // start errors
        if (j === 0) {
          data = xml.substring(j, i);
        }

        // other errors
        else {
          column = i - startOfLine;
          data = j == -1 ? xml.substring(i) : xml.substring(i, j + 1);
        }
      return {
        'data': data,
        'line': line,
        'column': column
      };
    }
    getContext = getParseContext;
    if (proxy) {
      elementProxy = Object.create({}, {
        'name': getter(function () {
          return elementName;
        }),
        'originalName': getter(function () {
          return _elementName;
        }),
        'attrs': getter(getAttrs),
        'ns': getter(function () {
          return nsMatrix;
        })
      });
    }

    // actual parse logic
    while (j !== -1) {
      if (xml.charCodeAt(j) === 60) {
        // "<"
        i = j;
      } else {
        i = xml.indexOf('<', j);
      }

      // parse end
      if (i === -1) {
        if (nodeStack.length) {
          return handleError('unexpected end of file');
        }
        if (j === 0) {
          return handleError('missing start tag');
        }
        if (j < xml.length) {
          if (xml.substring(j).trim()) {
            handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
          }
        }
        return;
      }

      // parse text
      if (j !== i) {
        if (nodeStack.length) {
          if (onText) {
            onText(xml.substring(j, i), decodeEntities, getContext);
            if (parseStop) {
              return;
            }
          }
        } else {
          if (xml.substring(j, i).trim()) {
            handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
            if (parseStop) {
              return;
            }
          }
        }
      }
      w = xml.charCodeAt(i + 1);

      // parse comments + CDATA
      if (w === 33) {
        // "!"
        q = xml.charCodeAt(i + 2);

        // CDATA section
        if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') {
          // 91 == "["
          j = xml.indexOf(']]>', i);
          if (j === -1) {
            return handleError('unclosed cdata');
          }
          if (onCDATA) {
            onCDATA(xml.substring(i + 9, j), getContext);
            if (parseStop) {
              return;
            }
          }
          j += 3;
          continue;
        }

        // comment
        if (q === 45 && xml.charCodeAt(i + 3) === 45) {
          // 45 == "-"
          j = xml.indexOf('-->', i);
          if (j === -1) {
            return handleError('unclosed comment');
          }
          if (onComment) {
            onComment(xml.substring(i + 4, j), decodeEntities, getContext);
            if (parseStop) {
              return;
            }
          }
          j += 3;
          continue;
        }
      }

      // parse question <? ... ?>
      if (w === 63) {
        // "?"
        j = xml.indexOf('?>', i);
        if (j === -1) {
          return handleError('unclosed question');
        }
        if (onQuestion) {
          onQuestion(xml.substring(i, j + 2), getContext);
          if (parseStop) {
            return;
          }
        }
        j += 2;
        continue;
      }

      // find matching closing tag for attention or standard tags
      // for that we must skip through attribute values
      // (enclosed in single or double quotes)
      for (x = i + 1;; x++) {
        v = xml.charCodeAt(x);
        if (isNaN(v)) {
          j = -1;
          return handleError('unclosed tag');
        }

        // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"
        // skips the quoted string
        // (double quotes) does not appear in a literal enclosed by (double quotes)
        // (single quote) does not appear in a literal enclosed by (single quote)
        if (v === 34) {
          //  '"'
          q = xml.indexOf('"', x + 1);
          x = q !== -1 ? q : x;
        } else if (v === 39) {
          // "'"
          q = xml.indexOf("'", x + 1);
          x = q !== -1 ? q : x;
        } else if (v === 62) {
          // '>'
          j = x;
          break;
        }
      }

      // parse attention <! ...>
      // previously comment and CDATA have already been parsed
      if (w === 33) {
        // "!"

        if (onAttention) {
          onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
          if (parseStop) {
            return;
          }
        }
        j += 1;
        continue;
      }

      // don't process attributes;
      // there are none
      cachedAttrs = {};

      // if (xml.charCodeAt(i+1) === 47) { // </...
      if (w === 47) {
        // </...
        tagStart = false;
        tagEnd = true;
        if (!nodeStack.length) {
          return handleError('missing open tag');
        }

        // verify open <-> close tag match
        x = elementName = nodeStack.pop();
        q = i + 2 + x.length;
        if (xml.substring(i + 2, q) !== x) {
          return handleError('closing tag mismatch');
        }

        // verify chars in close tag
        for (; q < j; q++) {
          w = xml.charCodeAt(q);
          if (w === 32 || w > 8 && w < 14) {
            // \f\n\r\t\v space
            continue;
          }
          return handleError('close tag');
        }
      } else {
        if (xml.charCodeAt(j - 1) === 47) {
          // .../>
          x = elementName = xml.substring(i + 1, j - 1);
          tagStart = true;
          tagEnd = true;
        } else {
          x = elementName = xml.substring(i + 1, j);
          tagStart = true;
          tagEnd = false;
        }
        if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) {
          // char 95"_" 58":"
          return handleError('illegal first char nodeName');
        }
        for (q = 1, y = x.length; q < y; q++) {
          w = x.charCodeAt(q);
          if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) {
            continue;
          }
          if (w === 32 || w < 14 && w > 8) {
            // \f\n\r\t\v space
            elementName = x.substring(0, q);

            // maybe there are attributes
            cachedAttrs = null;
            break;
          }
          return handleError('invalid nodeName');
        }
        if (!tagEnd) {
          nodeStack.push(elementName);
        }
      }
      if (isNamespace) {
        _nsMatrix = nsMatrix;
        if (tagStart) {
          // remember old namespace
          // unless we're self-closing
          if (!tagEnd) {
            nsMatrixStack.push(_nsMatrix);
          }
          if (cachedAttrs === null) {
            // quick check, whether there may be namespace
            // declarations on the node; if that is the case
            // we need to eagerly parse the node attributes
            if (maybeNS = x.indexOf('xmlns', q) !== -1) {
              attrsStart = q;
              attrsString = x;
              getAttrs();
              maybeNS = false;
            }
          }
        }
        _elementName = elementName;
        w = elementName.indexOf(':');
        if (w !== -1) {
          xmlns = nsMatrix[elementName.substring(0, w)];

          // prefix given; namespace must exist
          if (!xmlns) {
            return handleError('missing namespace on <' + _elementName + '>');
          }
          elementName = elementName.substr(w + 1);
        } else {
          xmlns = nsMatrix['xmlns'];

          // if no default namespace is defined,
          // we'll import the element as anonymous.
          //
          // it is up to users to correct that to the document defined
          // targetNamespace, or whatever their undersanding of the
          // XML spec mandates.
        }

        // adjust namespace prefixs as configured
        if (xmlns) {
          elementName = xmlns + ':' + elementName;
        }
      }
      if (tagStart) {
        attrsStart = q;
        attrsString = x;
        if (onOpenTag) {
          if (proxy) {
            onOpenTag(elementProxy, decodeEntities, tagEnd, getContext);
          } else {
            onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext);
          }
          if (parseStop) {
            return;
          }
        }
      }
      if (tagEnd) {
        if (onCloseTag) {
          onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext);
          if (parseStop) {
            return;
          }
        }

        // restore old namespace
        if (isNamespace) {
          if (!tagStart) {
            nsMatrix = nsMatrixStack.pop();
          } else {
            nsMatrix = _nsMatrix;
          }
        }
      }
      j += 1;
    }
  } /** end parse */
}

function hasLowerCaseAlias(pkg) {
  return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
}
var DEFAULT_NS_MAP = {
  'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
  'xml': 'http://www.w3.org/XML/1998/namespace'
};
var SERIALIZE_PROPERTY = 'property';
function getSerialization(element) {
  return element.xml && element.xml.serialize;
}
function getSerializationType(element) {
  const type = getSerialization(element);
  return type !== SERIALIZE_PROPERTY && (type || null);
}
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
function aliasToName(aliasNs, pkg) {
  if (!hasLowerCaseAlias(pkg)) {
    return aliasNs.name;
  }
  return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
}

/**
 * Un-prefix a potentially prefixed type name.
 *
 * @param {NsName} nameNs
 * @param {Object} [pkg]
 *
 * @return {string}
 */
function prefixedToName(nameNs, pkg) {
  var name = nameNs.name,
    localName = nameNs.localName;
  var typePrefix = pkg && pkg.xml && pkg.xml.typePrefix;
  if (typePrefix && localName.indexOf(typePrefix) === 0) {
    return nameNs.prefix + ':' + localName.slice(typePrefix.length);
  } else {
    return name;
  }
}
function normalizeTypeName(name, nsMap, model) {
  // normalize against actual NS
  const nameNs = parseName(name, nsMap.xmlns);
  const normalizedName = `${nsMap[nameNs.prefix] || nameNs.prefix}:${nameNs.localName}`;
  const normalizedNameNs = parseName(normalizedName);

  // determine actual type name, based on package-defined prefix
  var pkg = model.getPackage(normalizedNameNs.prefix);
  return prefixedToName(normalizedNameNs, pkg);
}
function error(message) {
  return new Error(message);
}

/**
 * Get the moddle descriptor for a given instance or type.
 *
 * @param  {ModdleElement|Function} element
 *
 * @return {Object} the moddle descriptor
 */
function getModdleDescriptor(element) {
  return element.$descriptor;
}

/**
 * A parse context.
 *
 * @class
 *
 * @param {Object} options
 * @param {ElementHandler} options.rootHandler the root handler for parsing a document
 * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
 */
function Context(options) {
  /**
   * @property {ElementHandler} rootHandler
   */

  /**
   * @property {Boolean} lax
   */

  assign(this, options);
  this.elementsById = {};
  this.references = [];
  this.warnings = [];

  /**
   * Add an unresolved reference.
   *
   * @param {Object} reference
   */
  this.addReference = function (reference) {
    this.references.push(reference);
  };

  /**
   * Add a processed element.
   *
   * @param {ModdleElement} element
   */
  this.addElement = function (element) {
    if (!element) {
      throw error('expected element');
    }
    var elementsById = this.elementsById;
    var descriptor = getModdleDescriptor(element);
    var idProperty = descriptor.idProperty,
      id;
    if (idProperty) {
      id = element.get(idProperty.name);
      if (id) {
        // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
        if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
          throw new Error('illegal ID <' + id + '>');
        }
        if (elementsById[id]) {
          throw error('duplicate ID <' + id + '>');
        }
        elementsById[id] = element;
      }
    }
  };

  /**
   * Add an import warning.
   *
   * @param {Object} warning
   * @param {String} warning.message
   * @param {Error} [warning.error]
   */
  this.addWarning = function (warning) {
    this.warnings.push(warning);
  };
}
function BaseHandler() {}
BaseHandler.prototype.handleEnd = function () {};
BaseHandler.prototype.handleText = function () {};
BaseHandler.prototype.handleNode = function () {};

/**
 * A simple pass through handler that does nothing except for
 * ignoring all input it receives.
 *
 * This is used to ignore unknown elements and
 * attributes.
 */
function NoopHandler() {}
NoopHandler.prototype = Object.create(BaseHandler.prototype);
NoopHandler.prototype.handleNode = function () {
  return this;
};
function BodyHandler() {}
BodyHandler.prototype = Object.create(BaseHandler.prototype);
BodyHandler.prototype.handleText = function (text) {
  this.body = (this.body || '') + text;
};
function ReferenceHandler(property, context) {
  this.property = property;
  this.context = context;
}
ReferenceHandler.prototype = Object.create(BodyHandler.prototype);
ReferenceHandler.prototype.handleNode = function (node) {
  if (this.element) {
    throw error('expected no sub nodes');
  } else {
    this.element = this.createReference(node);
  }
  return this;
};
ReferenceHandler.prototype.handleEnd = function () {
  this.element.id = this.body;
};
ReferenceHandler.prototype.createReference = function (node) {
  return {
    property: this.property.ns.name,
    id: ''
  };
};
function ValueHandler(propertyDesc, element) {
  this.element = element;
  this.propertyDesc = propertyDesc;
}
ValueHandler.prototype = Object.create(BodyHandler.prototype);
ValueHandler.prototype.handleEnd = function () {
  var value = this.body || '',
    element = this.element,
    propertyDesc = this.propertyDesc;
  value = coerceType(propertyDesc.type, value);
  if (propertyDesc.isMany) {
    element.get(propertyDesc.name).push(value);
  } else {
    element.set(propertyDesc.name, value);
  }
};
function BaseElementHandler() {}
BaseElementHandler.prototype = Object.create(BodyHandler.prototype);
BaseElementHandler.prototype.handleNode = function (node) {
  var parser = this,
    element = this.element;
  if (!element) {
    element = this.element = this.createElement(node);
    this.context.addElement(element);
  } else {
    parser = this.handleChild(node);
  }
  return parser;
};

/**
 * @class Reader.ElementHandler
 *
 */
function ElementHandler(model, typeName, context) {
  this.model = model;
  this.type = model.getType(typeName);
  this.context = context;
}
ElementHandler.prototype = Object.create(BaseElementHandler.prototype);
ElementHandler.prototype.addReference = function (reference) {
  this.context.addReference(reference);
};
ElementHandler.prototype.handleText = function (text) {
  var element = this.element,
    descriptor = getModdleDescriptor(element),
    bodyProperty = descriptor.bodyProperty;
  if (!bodyProperty) {
    throw error('unexpected body text <' + text + '>');
  }
  BodyHandler.prototype.handleText.call(this, text);
};
ElementHandler.prototype.handleEnd = function () {
  var value = this.body,
    element = this.element,
    descriptor = getModdleDescriptor(element),
    bodyProperty = descriptor.bodyProperty;
  if (bodyProperty && value !== undefined) {
    value = coerceType(bodyProperty.type, value);
    element.set(bodyProperty.name, value);
  }
};

/**
 * Create an instance of the model from the given node.
 *
 * @param  {Element} node the xml node
 */
ElementHandler.prototype.createElement = function (node) {
  var attributes = node.attributes,
    Type = this.type,
    descriptor = getModdleDescriptor(Type),
    context = this.context,
    instance = new Type({}),
    model = this.model,
    propNameNs;
  forEach(attributes, function (value, name) {
    var prop = descriptor.propertiesByName[name],
      values;
    if (prop && prop.isReference) {
      if (!prop.isMany) {
        context.addReference({
          element: instance,
          property: prop.ns.name,
          id: value
        });
      } else {
        // IDREFS: parse references as whitespace-separated list
        values = value.split(' ');
        forEach(values, function (v) {
          context.addReference({
            element: instance,
            property: prop.ns.name,
            id: v
          });
        });
      }
    } else {
      if (prop) {
        value = coerceType(prop.type, value);
      } else if (name === 'xmlns') {
        name = ':' + name;
      } else {
        propNameNs = parseName(name, descriptor.ns.prefix);

        // check whether attribute is defined in a well-known namespace
        // if that is the case we emit a warning to indicate potential misuse
        if (model.getPackage(propNameNs.prefix)) {
          context.addWarning({
            message: 'unknown attribute <' + name + '>',
            element: instance,
            property: name,
            value: value
          });
        }
      }
      instance.set(name, value);
    }
  });
  return instance;
};
ElementHandler.prototype.getPropertyForNode = function (node) {
  var name = node.name;
  var nameNs = parseName(name);
  var type = this.type,
    model = this.model,
    descriptor = getModdleDescriptor(type);
  var propertyName = nameNs.name,
    property = descriptor.propertiesByName[propertyName];

  // search for properties by name first

  if (property && !property.isAttr) {
    const serializationType = getSerializationType(property);
    if (serializationType) {
      const elementTypeName = node.attributes[serializationType];

      // type is optional, if it does not exists the
      // default type is assumed
      if (elementTypeName) {
        // convert the prefix used to the mapped form, but also
        // take possible type prefixes from XML
        // into account, i.e.: xsi:type="t{ActualType}",
        const normalizedTypeName = normalizeTypeName(elementTypeName, node.ns, model);
        const elementType = model.getType(normalizedTypeName);
        return assign({}, property, {
          effectiveType: getModdleDescriptor(elementType).name
        });
      }
    }

    // search for properties by name first
    return property;
  }
  var pkg = model.getPackage(nameNs.prefix);
  if (pkg) {
    const elementTypeName = aliasToName(nameNs, pkg);
    const elementType = model.getType(elementTypeName);

    // search for collection members later
    property = find(descriptor.properties, function (p) {
      return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
    });
    if (property) {
      return assign({}, property, {
        effectiveType: getModdleDescriptor(elementType).name
      });
    }
  } else {
    // parse unknown element (maybe extension)
    property = find(descriptor.properties, function (p) {
      return !p.isReference && !p.isAttribute && p.type === 'Element';
    });
    if (property) {
      return property;
    }
  }
  throw error('unrecognized element <' + nameNs.name + '>');
};
ElementHandler.prototype.toString = function () {
  return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
};
ElementHandler.prototype.valueHandler = function (propertyDesc, element) {
  return new ValueHandler(propertyDesc, element);
};
ElementHandler.prototype.referenceHandler = function (propertyDesc) {
  return new ReferenceHandler(propertyDesc, this.context);
};
ElementHandler.prototype.handler = function (type) {
  if (type === 'Element') {
    return new GenericElementHandler(this.model, type, this.context);
  } else {
    return new ElementHandler(this.model, type, this.context);
  }
};

/**
 * Handle the child element parsing
 *
 * @param  {Element} node the xml node
 */
ElementHandler.prototype.handleChild = function (node) {
  var propertyDesc, type, element, childHandler;
  propertyDesc = this.getPropertyForNode(node);
  element = this.element;
  type = propertyDesc.effectiveType || propertyDesc.type;
  if (isSimple(type)) {
    return this.valueHandler(propertyDesc, element);
  }
  if (propertyDesc.isReference) {
    childHandler = this.referenceHandler(propertyDesc).handleNode(node);
  } else {
    childHandler = this.handler(type).handleNode(node);
  }
  var newElement = childHandler.element;

  // child handles may decide to skip elements
  // by not returning anything
  if (newElement !== undefined) {
    if (propertyDesc.isMany) {
      element.get(propertyDesc.name).push(newElement);
    } else {
      element.set(propertyDesc.name, newElement);
    }
    if (propertyDesc.isReference) {
      assign(newElement, {
        element: element
      });
      this.context.addReference(newElement);
    } else {
      // establish child -> parent relationship
      newElement.$parent = element;
    }
  }
  return childHandler;
};

/**
 * An element handler that performs special validation
 * to ensure the node it gets initialized with matches
 * the handlers type (namespace wise).
 *
 * @param {Moddle} model
 * @param {String} typeName
 * @param {Context} context
 */
function RootElementHandler(model, typeName, context) {
  ElementHandler.call(this, model, typeName, context);
}
RootElementHandler.prototype = Object.create(ElementHandler.prototype);
RootElementHandler.prototype.createElement = function (node) {
  var name = node.name,
    nameNs = parseName(name),
    model = this.model,
    type = this.type,
    pkg = model.getPackage(nameNs.prefix),
    typeName = pkg && aliasToName(nameNs, pkg) || name;

  // verify the correct namespace if we parse
  // the first element in the handler tree
  //
  // this ensures we don't mistakenly import wrong namespace elements
  if (!type.hasType(typeName)) {
    throw error('unexpected element <' + node.originalName + '>');
  }
  return ElementHandler.prototype.createElement.call(this, node);
};
function GenericElementHandler(model, typeName, context) {
  this.model = model;
  this.context = context;
}
GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);
GenericElementHandler.prototype.createElement = function (node) {
  var name = node.name,
    ns = parseName(name),
    prefix = ns.prefix,
    uri = node.ns[prefix + '$uri'],
    attributes = node.attributes;
  return this.model.createAny(name, uri, attributes);
};
GenericElementHandler.prototype.handleChild = function (node) {
  var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
    element = this.element;
  var newElement = handler.element,
    children;
  if (newElement !== undefined) {
    children = element.$children = element.$children || [];
    children.push(newElement);

    // establish child -> parent relationship
    newElement.$parent = element;
  }
  return handler;
};
GenericElementHandler.prototype.handleEnd = function () {
  if (this.body) {
    this.element.$body = this.body;
  }
};

/**
 * A reader for a meta-model
 *
 * @param {Object} options
 * @param {Model} options.model used to read xml files
 * @param {Boolean} options.lax whether to make parse errors warnings
 */
function Reader(options) {
  if (options instanceof Moddle) {
    options = {
      model: options
    };
  }
  assign(this, {
    lax: false
  }, options);
}

/**
 * The fromXML result.
 *
 * @typedef {Object} ParseResult
 *
 * @property {ModdleElement} rootElement
 * @property {Array<Object>} references
 * @property {Array<Error>} warnings
 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
 */

/**
 * The fromXML result.
 *
 * @typedef {Error} ParseError
 *
 * @property {Array<Error>} warnings
 */

/**
 * Parse the given XML into a moddle document tree.
 *
 * @param {String} xml
 * @param {ElementHandler|Object} options or rootHandler
 *
 * @returns {Promise<ParseResult, ParseError>}
 */
Reader.prototype.fromXML = function (xml, options, done) {
  var rootHandler = options.rootHandler;
  if (options instanceof ElementHandler) {
    // root handler passed via (xml, { rootHandler: ElementHandler }, ...)
    rootHandler = options;
    options = {};
  } else {
    if (typeof options === 'string') {
      // rootHandler passed via (xml, 'someString', ...)
      rootHandler = this.handler(options);
      options = {};
    } else if (typeof rootHandler === 'string') {
      // rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
      rootHandler = this.handler(rootHandler);
    }
  }
  var model = this.model,
    lax = this.lax;
  var context = new Context(assign({}, options, {
      rootHandler: rootHandler
    })),
    parser = new Parser({
      proxy: true
    }),
    stack = createStack();
  rootHandler.context = context;

  // push root handler
  stack.push(rootHandler);

  /**
   * Handle error.
   *
   * @param  {Error} err
   * @param  {Function} getContext
   * @param  {boolean} lax
   *
   * @return {boolean} true if handled
   */
  function handleError(err, getContext, lax) {
    var ctx = getContext();
    var line = ctx.line,
      column = ctx.column,
      data = ctx.data;

    // we receive the full context data here,
    // for elements trim down the information
    // to the tag name, only
    if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
      data = data.slice(0, data.indexOf(' ')) + '>';
    }
    var message = 'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' + 'line: ' + line + '\n\t' + 'column: ' + column + '\n\t' + 'nested error: ' + err.message;
    if (lax) {
      context.addWarning({
        message: message,
        error: err
      });
      return true;
    } else {
      throw error(message);
    }
  }
  function handleWarning(err, getContext) {
    // just like handling errors in <lax=true> mode
    return handleError(err, getContext, true);
  }

  /**
   * Resolve collected references on parse end.
   */
  function resolveReferences() {
    var elementsById = context.elementsById;
    var references = context.references;
    var i, r;
    for (i = 0; r = references[i]; i++) {
      var element = r.element;
      var reference = elementsById[r.id];
      var property = getModdleDescriptor(element).propertiesByName[r.property];
      if (!reference) {
        context.addWarning({
          message: 'unresolved reference <' + r.id + '>',
          element: r.element,
          property: r.property,
          value: r.id
        });
      }
      if (property.isMany) {
        var collection = element.get(property.name),
          idx = collection.indexOf(r);

        // we replace an existing place holder (idx != -1) or
        // append to the collection instead
        if (idx === -1) {
          idx = collection.length;
        }
        if (!reference) {
          // remove unresolvable reference
          collection.splice(idx, 1);
        } else {
          // add or update reference in collection
          collection[idx] = reference;
        }
      } else {
        element.set(property.name, reference);
      }
    }
  }
  function handleClose() {
    stack.pop().handleEnd();
  }
  var PREAMBLE_START_PATTERN = /^<\?xml /i;
  var ENCODING_PATTERN = / encoding="([^"]+)"/i;
  var UTF_8_PATTERN = /^utf-8$/i;
  function handleQuestion(question) {
    if (!PREAMBLE_START_PATTERN.test(question)) {
      return;
    }
    var match = ENCODING_PATTERN.exec(question);
    var encoding = match && match[1];
    if (!encoding || UTF_8_PATTERN.test(encoding)) {
      return;
    }
    context.addWarning({
      message: 'unsupported document encoding <' + encoding + '>, ' + 'falling back to UTF-8'
    });
  }
  function handleOpen(node, getContext) {
    var handler = stack.peek();
    try {
      stack.push(handler.handleNode(node));
    } catch (err) {
      if (handleError(err, getContext, lax)) {
        stack.push(new NoopHandler());
      }
    }
  }
  function handleCData(text, getContext) {
    try {
      stack.peek().handleText(text);
    } catch (err) {
      handleWarning(err, getContext);
    }
  }
  function handleText(text, getContext) {
    // strip whitespace only nodes, i.e. before
    // <!CDATA[ ... ]> sections and in between tags

    if (!text.trim()) {
      return;
    }
    handleCData(text, getContext);
  }
  var uriMap = model.getPackages().reduce(function (uriMap, p) {
    uriMap[p.uri] = p.prefix;
    return uriMap;
  }, Object.entries(DEFAULT_NS_MAP).reduce(function (map, [prefix, url]) {
    map[url] = prefix;
    return map;
  }, model.config && model.config.nsMap || {}));
  parser.ns(uriMap).on('openTag', function (obj, decodeStr, selfClosing, getContext) {
    // gracefully handle unparsable attributes (attrs=false)
    var attrs = obj.attrs || {};
    var decodedAttrs = Object.keys(attrs).reduce(function (d, key) {
      var value = decodeStr(attrs[key]);
      d[key] = value;
      return d;
    }, {});
    var node = {
      name: obj.name,
      originalName: obj.originalName,
      attributes: decodedAttrs,
      ns: obj.ns
    };
    handleOpen(node, getContext);
  }).on('question', handleQuestion).on('closeTag', handleClose).on('cdata', handleCData).on('text', function (text, decodeEntities, getContext) {
    handleText(decodeEntities(text), getContext);
  }).on('error', handleError).on('warn', handleWarning);

  // async XML parsing to make sure the execution environment
  // (node or brower) is kept responsive and that certain optimization
  // strategies can kick in.
  return new Promise(function (resolve, reject) {
    var err;
    try {
      parser.parse(xml);
      resolveReferences();
    } catch (e) {
      err = e;
    }
    var rootElement = rootHandler.element;
    if (!err && !rootElement) {
      err = error('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
    }
    var warnings = context.warnings;
    var references = context.references;
    var elementsById = context.elementsById;
    if (err) {
      err.warnings = warnings;
      return reject(err);
    } else {
      return resolve({
        rootElement: rootElement,
        elementsById: elementsById,
        references: references,
        warnings: warnings
      });
    }
  });
};
Reader.prototype.handler = function (name) {
  return new RootElementHandler(this.model, name);
};

// helpers //////////////////////////

function createStack() {
  var stack = [];
  Object.defineProperty(stack, 'peek', {
    value: function () {
      return this[this.length - 1];
    }
  });
  return stack;
}
var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n';
var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g;
var ESCAPE_CHARS = /<|>|&/g;
function Namespaces(parent) {
  this.prefixMap = {};
  this.uriMap = {};
  this.used = {};
  this.wellknown = [];
  this.custom = [];
  this.parent = parent;
  this.defaultPrefixMap = parent && parent.defaultPrefixMap || {};
}
Namespaces.prototype.mapDefaultPrefixes = function (defaultPrefixMap) {
  this.defaultPrefixMap = defaultPrefixMap;
};
Namespaces.prototype.defaultUriByPrefix = function (prefix) {
  return this.defaultPrefixMap[prefix];
};
Namespaces.prototype.byUri = function (uri) {
  return this.uriMap[uri] || this.parent && this.parent.byUri(uri);
};
Namespaces.prototype.add = function (ns, isWellknown) {
  this.uriMap[ns.uri] = ns;
  if (isWellknown) {
    this.wellknown.push(ns);
  } else {
    this.custom.push(ns);
  }
  this.mapPrefix(ns.prefix, ns.uri);
};
Namespaces.prototype.uriByPrefix = function (prefix) {
  return this.prefixMap[prefix || 'xmlns'] || this.parent && this.parent.uriByPrefix(prefix);
};
Namespaces.prototype.mapPrefix = function (prefix, uri) {
  this.prefixMap[prefix || 'xmlns'] = uri;
};
Namespaces.prototype.getNSKey = function (ns) {
  return ns.prefix !== undefined ? ns.uri + '|' + ns.prefix : ns.uri;
};
Namespaces.prototype.logUsed = function (ns) {
  var uri = ns.uri;
  var nsKey = this.getNSKey(ns);
  this.used[nsKey] = this.byUri(uri);

  // Inform parent recursively about the usage of this NS
  if (this.parent) {
    this.parent.logUsed(ns);
  }
};
Namespaces.prototype.getUsed = function (ns) {
  var allNs = [].concat(this.wellknown, this.custom);
  return allNs.filter(ns => {
    var nsKey = this.getNSKey(ns);
    return this.used[nsKey];
  });
};
function lower(string) {
  return string.charAt(0).toLowerCase() + string.slice(1);
}
function nameToAlias(name, pkg) {
  if (hasLowerCaseAlias(pkg)) {
    return lower(name);
  } else {
    return name;
  }
}
function inherits(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
}
function nsName(ns) {
  if (isString(ns)) {
    return ns;
  } else {
    return (ns.prefix ? ns.prefix + ':' : '') + ns.localName;
  }
}
function getNsAttrs(namespaces) {
  return namespaces.getUsed().filter(function (ns) {
    // do not serialize built in <xml> namespace
    return ns.prefix !== 'xml';
  }).map(function (ns) {
    var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : '');
    return {
      name: name,
      value: ns.uri
    };
  });
}
function getElementNs(ns, descriptor) {
  if (descriptor.isGeneric) {
    return assign({
      localName: descriptor.ns.localName
    }, ns);
  } else {
    return assign({
      localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg)
    }, ns);
  }
}
function getPropertyNs(ns, descriptor) {
  return assign({
    localName: descriptor.ns.localName
  }, ns);
}
function getSerializableProperties(element) {
  var descriptor = element.$descriptor;
  return filter(descriptor.properties, function (p) {
    var name = p.name;
    if (p.isVirtual) {
      return false;
    }

    // do not serialize defaults
    if (!has(element, name)) {
      return false;
    }
    var value = element[name];

    // do not serialize default equals
    if (value === p.default) {
      return false;
    }

    // do not serialize null properties
    if (value === null) {
      return false;
    }
    return p.isMany ? value.length : true;
  });
}
var ESCAPE_ATTR_MAP = {
  '\n': '#10',
  '\n\r': '#10',
  '"': '#34',
  '\'': '#39',
  '<': '#60',
  '>': '#62',
  '&': '#38'
};
var ESCAPE_MAP = {
  '<': 'lt',
  '>': 'gt',
  '&': 'amp'
};
function escape(str, charPattern, replaceMap) {
  // ensure we are handling strings here
  str = isString(str) ? str : '' + str;
  return str.replace(charPattern, function (s) {
    return '&' + replaceMap[s] + ';';
  });
}

/**
 * Escape a string attribute to not contain any bad values (line breaks, '"', ...)
 *
 * @param {String} str the string to escape
 * @return {String} the escaped string
 */
function escapeAttr(str) {
  return escape(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
}
function escapeBody(str) {
  return escape(str, ESCAPE_CHARS, ESCAPE_MAP);
}
function filterAttributes(props) {
  return filter(props, function (p) {
    return p.isAttr;
  });
}
function filterContained(props) {
  return filter(props, function (p) {
    return !p.isAttr;
  });
}
function ReferenceSerializer(tagName) {
  this.tagName = tagName;
}
ReferenceSerializer.prototype.build = function (element) {
  this.element = element;
  return this;
};
ReferenceSerializer.prototype.serializeTo = function (writer) {
  writer.appendIndent().append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>').appendNewLine();
};
function BodySerializer() {}
BodySerializer.prototype.serializeValue = BodySerializer.prototype.serializeTo = function (writer) {
  writer.append(this.escape ? escapeBody(this.value) : this.value);
};
BodySerializer.prototype.build = function (prop, value) {
  this.value = value;
  if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) {
    this.escape = true;
  }
  return this;
};
function ValueSerializer(tagName) {
  this.tagName = tagName;
}
inherits(ValueSerializer, BodySerializer);
ValueSerializer.prototype.serializeTo = function (writer) {
  writer.appendIndent().append('<' + this.tagName + '>');
  this.serializeValue(writer);
  writer.append('</' + this.tagName + '>').appendNewLine();
};
function ElementSerializer(parent, propertyDescriptor) {
  this.body = [];
  this.attrs = [];
  this.parent = parent;
  this.propertyDescriptor = propertyDescriptor;
}
ElementSerializer.prototype.build = function (element) {
  this.element = element;
  var elementDescriptor = element.$descriptor,
    propertyDescriptor = this.propertyDescriptor;
  var otherAttrs, properties;
  var isGeneric = elementDescriptor.isGeneric;
  if (isGeneric) {
    otherAttrs = this.parseGenericNsAttributes(element);
  } else {
    otherAttrs = this.parseNsAttributes(element);
  }
  if (propertyDescriptor) {
    this.ns = this.nsPropertyTagName(propertyDescriptor);
  } else {
    this.ns = this.nsTagName(elementDescriptor);
  }

  // compute tag name
  this.tagName = this.addTagName(this.ns);
  if (isGeneric) {
    this.parseGenericContainments(element);
  } else {
    properties = getSerializableProperties(element);
    this.parseAttributes(filterAttributes(properties));
    this.parseContainments(filterContained(properties));
  }
  this.parseGenericAttributes(element, otherAttrs);
  return this;
};
ElementSerializer.prototype.nsTagName = function (descriptor) {
  var effectiveNs = this.logNamespaceUsed(descriptor.ns);
  return getElementNs(effectiveNs, descriptor);
};
ElementSerializer.prototype.nsPropertyTagName = function (descriptor) {
  var effectiveNs = this.logNamespaceUsed(descriptor.ns);
  return getPropertyNs(effectiveNs, descriptor);
};
ElementSerializer.prototype.isLocalNs = function (ns) {
  return ns.uri === this.ns.uri;
};

/**
 * Get the actual ns attribute name for the given element.
 *
 * @param {Object} element
 * @param {Boolean} [element.inherited=false]
 *
 * @return {Object} nsName
 */
ElementSerializer.prototype.nsAttributeName = function (element) {
  var ns;
  if (isString(element)) {
    ns = parseName(element);
  } else {
    ns = element.ns;
  }

  // return just local name for inherited attributes
  if (element.inherited) {
    return {
      localName: ns.localName
    };
  }

  // parse + log effective ns
  var effectiveNs = this.logNamespaceUsed(ns);

  // LOG ACTUAL namespace use
  this.getNamespaces().logUsed(effectiveNs);

  // strip prefix if same namespace like parent
  if (this.isLocalNs(effectiveNs)) {
    return {
      localName: ns.localName
    };
  } else {
    return assign({
      localName: ns.localName
    }, effectiveNs);
  }
};
ElementSerializer.prototype.parseGenericNsAttributes = function (element) {
  return Object.entries(element).filter(([key, value]) => !key.startsWith('$') && this.parseNsAttribute(element, key, value)).map(([key, value]) => ({
    name: key,
    value: value
  }));
};
ElementSerializer.prototype.parseGenericContainments = function (element) {
  var body = element.$body;
  if (body) {
    this.body.push(new BodySerializer().build({
      type: 'String'
    }, body));
  }
  var children = element.$children;
  if (children) {
    forEach(children, child => {
      this.body.push(new ElementSerializer(this).build(child));
    });
  }
};
ElementSerializer.prototype.parseNsAttribute = function (element, name, value) {
  var model = element.$model;
  var nameNs = parseName(name);
  var ns;

  // parse xmlns:foo="http://foo.bar"
  if (nameNs.prefix === 'xmlns') {
    ns = {
      prefix: nameNs.localName,
      uri: value
    };
  }

  // parse xmlns="http://foo.bar"
  if (!nameNs.prefix && nameNs.localName === 'xmlns') {
    ns = {
      uri: value
    };
  }
  if (!ns) {
    return {
      name: name,
      value: value
    };
  }
  if (model && model.getPackage(value)) {
    // register well known namespace
    this.logNamespace(ns, true, true);
  } else {
    // log custom namespace directly as used
    var actualNs = this.logNamespaceUsed(ns, true);
    this.getNamespaces().logUsed(actualNs);
  }
};

/**
 * Parse namespaces and return a list of left over generic attributes
 *
 * @param  {Object} element
 * @return {Array<Object>}
 */
ElementSerializer.prototype.parseNsAttributes = function (element) {
  var self = this;
  var genericAttrs = element.$attrs;
  var attributes = [];

  // parse namespace attributes first
  // and log them. push non namespace attributes to a list
  // and process them later
  forEach(genericAttrs, function (value, name) {
    var nonNsAttr = self.parseNsAttribute(element, name, value);
    if (nonNsAttr) {
      attributes.push(nonNsAttr);
    }
  });
  return attributes;
};
ElementSerializer.prototype.parseGenericAttributes = function (element, attributes) {
  var self = this;
  forEach(attributes, function (attr) {
    try {
      self.addAttribute(self.nsAttributeName(attr.name), attr.value);
    } catch (e) {
      // eslint-disable-next-line no-undef
      typeof console !== 'undefined' && console.warn(`missing namespace information for <${attr.name}=${attr.value}> on`, element, e);
    }
  });
};
ElementSerializer.prototype.parseContainments = function (properties) {
  var self = this,
    body = this.body,
    element = this.element;
  forEach(properties, function (p) {
    var value = element.get(p.name),
      isReference = p.isReference,
      isMany = p.isMany;
    if (!isMany) {
      value = [value];
    }
    if (p.isBody) {
      body.push(new BodySerializer().build(p, value[0]));
    } else if (isSimple(p.type)) {
      forEach(value, function (v) {
        body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
      });
    } else if (isReference) {
      forEach(value, function (v) {
        body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v));
      });
    } else {
      // allow serialization via type
      // rather than element name
      var serialization = getSerialization(p);
      forEach(value, function (v) {
        var serializer;
        if (serialization) {
          if (serialization === SERIALIZE_PROPERTY) {
            serializer = new ElementSerializer(self, p);
          } else {
            serializer = new TypeSerializer(self, p, serialization);
          }
        } else {
          serializer = new ElementSerializer(self);
        }
        body.push(serializer.build(v));
      });
    }
  });
};
ElementSerializer.prototype.getNamespaces = function (local) {
  var namespaces = this.namespaces,
    parent = this.parent,
    parentNamespaces;
  if (!namespaces) {
    parentNamespaces = parent && parent.getNamespaces();
    if (local || !parentNamespaces) {
      this.namespaces = namespaces = new Namespaces(parentNamespaces);
    } else {
      namespaces = parentNamespaces;
    }
  }
  return namespaces;
};
ElementSerializer.prototype.logNamespace = function (ns, wellknown, local) {
  var namespaces = this.getNamespaces(local);
  var nsUri = ns.uri,
    nsPrefix = ns.prefix;
  var existing = namespaces.byUri(nsUri);
  if (!existing || local) {
    namespaces.add(ns, wellknown);
  }
  namespaces.mapPrefix(nsPrefix, nsUri);
  return ns;
};
ElementSerializer.prototype.logNamespaceUsed = function (ns, local) {
  var namespaces = this.getNamespaces(local);

  // ns may be
  //
  //   * prefix only
  //   * prefix:uri
  //   * localName only

  var prefix = ns.prefix,
    uri = ns.uri,
    newPrefix,
    idx,
    wellknownUri;

  // handle anonymous namespaces (elementForm=unqualified), cf. #23
  if (!prefix && !uri) {
    return {
      localName: ns.localName
    };
  }
  wellknownUri = namespaces.defaultUriByPrefix(prefix);
  uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);
  if (!uri) {
    throw new Error('no namespace uri given for prefix <' + prefix + '>');
  }
  ns = namespaces.byUri(uri);

  // register new default prefix <xmlns> in local scope
  if (!ns && !prefix) {
    ns = this.logNamespace({
      uri
    }, wellknownUri === uri, true);
  }
  if (!ns) {
    newPrefix = prefix;
    idx = 1;

    // find a prefix that is not mapped yet
    while (namespaces.uriByPrefix(newPrefix)) {
      newPrefix = prefix + '_' + idx++;
    }
    ns = this.logNamespace({
      prefix: newPrefix,
      uri: uri
    }, wellknownUri === uri);
  }
  if (prefix) {
    namespaces.mapPrefix(prefix, uri);
  }
  return ns;
};
ElementSerializer.prototype.parseAttributes = function (properties) {
  var self = this,
    element = this.element;
  forEach(properties, function (p) {
    var value = element.get(p.name);
    if (p.isReference) {
      if (!p.isMany) {
        value = value.id;
      } else {
        var values = [];
        forEach(value, function (v) {
          values.push(v.id);
        });

        // IDREFS is a whitespace-separated list of references.
        value = values.join(' ');
      }
    }
    self.addAttribute(self.nsAttributeName(p), value);
  });
};
ElementSerializer.prototype.addTagName = function (nsTagName) {
  var actualNs = this.logNamespaceUsed(nsTagName);
  this.getNamespaces().logUsed(actualNs);
  return nsName(nsTagName);
};
ElementSerializer.prototype.addAttribute = function (name, value) {
  var attrs = this.attrs;
  if (isString(value)) {
    value = escapeAttr(value);
  }

  // de-duplicate attributes
  // https://github.com/bpmn-io/moddle-xml/issues/66
  var idx = findIndex(attrs, function (element) {
    return element.name.localName === name.localName && element.name.uri === name.uri && element.name.prefix === name.prefix;
  });
  var attr = {
    name: name,
    value: value
  };
  if (idx !== -1) {
    attrs.splice(idx, 1, attr);
  } else {
    attrs.push(attr);
  }
};
ElementSerializer.prototype.serializeAttributes = function (writer) {
  var attrs = this.attrs,
    namespaces = this.namespaces;
  if (namespaces) {
    attrs = getNsAttrs(namespaces).concat(attrs);
  }
  forEach(attrs, function (a) {
    writer.append(' ').append(nsName(a.name)).append('="').append(a.value).append('"');
  });
};
ElementSerializer.prototype.serializeTo = function (writer) {
  var firstBody = this.body[0],
    indent = firstBody && firstBody.constructor !== BodySerializer;
  writer.appendIndent().append('<' + this.tagName);
  this.serializeAttributes(writer);
  writer.append(firstBody ? '>' : ' />');
  if (firstBody) {
    if (indent) {
      writer.appendNewLine().indent();
    }
    forEach(this.body, function (b) {
      b.serializeTo(writer);
    });
    if (indent) {
      writer.unindent().appendIndent();
    }
    writer.append('</' + this.tagName + '>');
  }
  writer.appendNewLine();
};

/**
 * A serializer for types that handles serialization of data types
 */
function TypeSerializer(parent, propertyDescriptor, serialization) {
  ElementSerializer.call(this, parent, propertyDescriptor);
  this.serialization = serialization;
}
inherits(TypeSerializer, ElementSerializer);
TypeSerializer.prototype.parseNsAttributes = function (element) {
  // extracted attributes with serialization attribute
  // <type=typeName> stripped; it may be later
  var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element).filter(attr => attr.name !== this.serialization);
  var descriptor = element.$descriptor;

  // only serialize <type=typeName> if necessary
  if (descriptor.name === this.propertyDescriptor.type) {
    return attributes;
  }
  var typeNs = this.typeNs = this.nsTagName(descriptor);
  this.getNamespaces().logUsed(this.typeNs);

  // add xsi:type attribute to represent the elements
  // actual type

  var pkg = element.$model.getPackage(typeNs.uri),
    typePrefix = pkg.xml && pkg.xml.typePrefix || '';
  this.addAttribute(this.nsAttributeName(this.serialization), (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName);
  return attributes;
};
TypeSerializer.prototype.isLocalNs = function (ns) {
  return ns.uri === (this.typeNs || this.ns).uri;
};
function SavingWriter() {
  this.value = '';
  this.write = function (str) {
    this.value += str;
  };
}
function FormatingWriter(out, format) {
  var indent = [''];
  this.append = function (str) {
    out.write(str);
    return this;
  };
  this.appendNewLine = function () {
    if (format) {
      out.write('\n');
    }
    return this;
  };
  this.appendIndent = function () {
    if (format) {
      out.write(indent.join('  '));
    }
    return this;
  };
  this.indent = function () {
    indent.push('');
    return this;
  };
  this.unindent = function () {
    indent.pop();
    return this;
  };
}

/**
 * A writer for meta-model backed document trees
 *
 * @param {Object} options output options to pass into the writer
 */
function Writer(options) {
  options = assign({
    format: false,
    preamble: true
  }, options || {});
  function toXML(tree, writer) {
    var internalWriter = writer || new SavingWriter();
    var formatingWriter = new FormatingWriter(internalWriter, options.format);
    if (options.preamble) {
      formatingWriter.append(XML_PREAMBLE);
    }
    var serializer = new ElementSerializer();
    var model = tree.$model;
    serializer.getNamespaces().mapDefaultPrefixes(getDefaultPrefixMappings(model));
    serializer.build(tree).serializeTo(formatingWriter);
    if (!writer) {
      return internalWriter.value;
    }
  }
  return {
    toXML: toXML
  };
}

// helpers ///////////

/**
 * @param {Moddle} model
 *
 * @return { Record<string, string> } map from prefix to URI
 */
function getDefaultPrefixMappings(model) {
  const nsMap = model.config && model.config.nsMap || {};
  const prefixMap = {};

  // { prefix -> uri }
  for (const prefix in DEFAULT_NS_MAP) {
    prefixMap[prefix] = DEFAULT_NS_MAP[prefix];
  }

  // { uri -> prefix }
  for (const uri in nsMap) {
    const prefix = nsMap[uri];
    prefixMap[prefix] = uri;
  }
  for (const pkg of model.getPackages()) {
    prefixMap[pkg.prefix] = pkg.uri;
  }
  return prefixMap;
}

/**
 * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files.
 *
 * @class BpmnModdle
 * @extends Moddle
 *
 * @param {Object|Array} packages to use for instantiating the model
 * @param {Object} [options] additional options to pass over
 */
function BpmnModdle(packages, options) {
  Moddle.call(this, packages, options);
}
BpmnModdle.prototype = Object.create(Moddle.prototype);

/**
 * The fromXML result.
 *
 * @typedef {Object} ParseResult
 *
 * @property {ModdleElement} rootElement
 * @property {Array<Object>} references
 * @property {Array<Error>} warnings
 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
 */

/**
 * The fromXML error.
 *
 * @typedef {Error} ParseError
 *
 * @property {Array<Error>} warnings
 */

/**
 * Instantiates a BPMN model tree from a given xml string.
 *
 * @param {String}   xmlStr
 * @param {String}   [typeName='bpmn:Definitions'] name of the root element
 * @param {Object}   [options]  options to pass to the underlying reader
 *
 * @returns {Promise<ParseResult, ParseError>}
 */
BpmnModdle.prototype.fromXML = function (xmlStr, typeName, options) {
  if (!isString(typeName)) {
    options = typeName;
    typeName = 'bpmn:Definitions';
  }
  var reader = new Reader(assign({
    model: this,
    lax: true
  }, options));
  var rootHandler = reader.handler(typeName);
  return reader.fromXML(xmlStr, rootHandler);
};

/**
 * The toXML result.
 *
 * @typedef {Object} SerializationResult
 *
 * @property {String} xml
 */

/**
 * Serializes a BPMN 2.0 object tree to XML.
 *
 * @param {String}   element    the root element, typically an instance of `bpmn:Definitions`
 * @param {Object}   [options]  to pass to the underlying writer
 *
 * @returns {Promise<SerializationResult, Error>}
 */
BpmnModdle.prototype.toXML = function (element, options) {
  var writer = new Writer(options);
  return new Promise(function (resolve, reject) {
    try {
      var result = writer.toXML(element);
      return resolve({
        xml: result
      });
    } catch (err) {
      return reject(err);
    }
  });
};
var name$5 = "BPMN20";
var uri$5 = "http://www.omg.org/spec/BPMN/20100524/MODEL";
var prefix$5 = "bpmn";
var associations$5 = [];
var types$5 = [{
  name: "Interface",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "operations",
    type: "Operation",
    isMany: true
  }, {
    name: "implementationRef",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Operation",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "inMessageRef",
    type: "Message",
    isReference: true
  }, {
    name: "outMessageRef",
    type: "Message",
    isReference: true
  }, {
    name: "errorRef",
    type: "Error",
    isMany: true,
    isReference: true
  }, {
    name: "implementationRef",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "EndPoint",
  superClass: ["RootElement"]
}, {
  name: "Auditing",
  superClass: ["BaseElement"]
}, {
  name: "GlobalTask",
  superClass: ["CallableElement"],
  properties: [{
    name: "resources",
    type: "ResourceRole",
    isMany: true
  }]
}, {
  name: "Monitoring",
  superClass: ["BaseElement"]
}, {
  name: "Performer",
  superClass: ["ResourceRole"]
}, {
  name: "Process",
  superClass: ["FlowElementsContainer", "CallableElement"],
  properties: [{
    name: "processType",
    type: "ProcessType",
    isAttr: true
  }, {
    name: "isClosed",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "auditing",
    type: "Auditing"
  }, {
    name: "monitoring",
    type: "Monitoring"
  }, {
    name: "properties",
    type: "Property",
    isMany: true
  }, {
    name: "laneSets",
    isMany: true,
    replaces: "FlowElementsContainer#laneSets",
    type: "LaneSet"
  }, {
    name: "flowElements",
    isMany: true,
    replaces: "FlowElementsContainer#flowElements",
    type: "FlowElement"
  }, {
    name: "artifacts",
    type: "Artifact",
    isMany: true
  }, {
    name: "resources",
    type: "ResourceRole",
    isMany: true
  }, {
    name: "correlationSubscriptions",
    type: "CorrelationSubscription",
    isMany: true
  }, {
    name: "supports",
    type: "Process",
    isMany: true,
    isReference: true
  }, {
    name: "definitionalCollaborationRef",
    type: "Collaboration",
    isAttr: true,
    isReference: true
  }, {
    name: "isExecutable",
    isAttr: true,
    type: "Boolean"
  }]
}, {
  name: "LaneSet",
  superClass: ["BaseElement"],
  properties: [{
    name: "lanes",
    type: "Lane",
    isMany: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Lane",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "partitionElementRef",
    type: "BaseElement",
    isAttr: true,
    isReference: true
  }, {
    name: "partitionElement",
    type: "BaseElement"
  }, {
    name: "flowNodeRef",
    type: "FlowNode",
    isMany: true,
    isReference: true
  }, {
    name: "childLaneSet",
    type: "LaneSet",
    xml: {
      serialize: "xsi:type"
    }
  }]
}, {
  name: "GlobalManualTask",
  superClass: ["GlobalTask"]
}, {
  name: "ManualTask",
  superClass: ["Task"]
}, {
  name: "UserTask",
  superClass: ["Task"],
  properties: [{
    name: "renderings",
    type: "Rendering",
    isMany: true
  }, {
    name: "implementation",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Rendering",
  superClass: ["BaseElement"]
}, {
  name: "HumanPerformer",
  superClass: ["Performer"]
}, {
  name: "PotentialOwner",
  superClass: ["HumanPerformer"]
}, {
  name: "GlobalUserTask",
  superClass: ["GlobalTask"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }, {
    name: "renderings",
    type: "Rendering",
    isMany: true
  }]
}, {
  name: "Gateway",
  isAbstract: true,
  superClass: ["FlowNode"],
  properties: [{
    name: "gatewayDirection",
    type: "GatewayDirection",
    "default": "Unspecified",
    isAttr: true
  }]
}, {
  name: "EventBasedGateway",
  superClass: ["Gateway"],
  properties: [{
    name: "instantiate",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "eventGatewayType",
    type: "EventBasedGatewayType",
    isAttr: true,
    "default": "Exclusive"
  }]
}, {
  name: "ComplexGateway",
  superClass: ["Gateway"],
  properties: [{
    name: "activationCondition",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "default",
    type: "SequenceFlow",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ExclusiveGateway",
  superClass: ["Gateway"],
  properties: [{
    name: "default",
    type: "SequenceFlow",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "InclusiveGateway",
  superClass: ["Gateway"],
  properties: [{
    name: "default",
    type: "SequenceFlow",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ParallelGateway",
  superClass: ["Gateway"]
}, {
  name: "RootElement",
  isAbstract: true,
  superClass: ["BaseElement"]
}, {
  name: "Relationship",
  superClass: ["BaseElement"],
  properties: [{
    name: "type",
    isAttr: true,
    type: "String"
  }, {
    name: "direction",
    type: "RelationshipDirection",
    isAttr: true
  }, {
    name: "source",
    isMany: true,
    isReference: true,
    type: "Element"
  }, {
    name: "target",
    isMany: true,
    isReference: true,
    type: "Element"
  }]
}, {
  name: "BaseElement",
  isAbstract: true,
  properties: [{
    name: "id",
    isAttr: true,
    type: "String",
    isId: true
  }, {
    name: "documentation",
    type: "Documentation",
    isMany: true
  }, {
    name: "extensionDefinitions",
    type: "ExtensionDefinition",
    isMany: true,
    isReference: true
  }, {
    name: "extensionElements",
    type: "ExtensionElements"
  }]
}, {
  name: "Extension",
  properties: [{
    name: "mustUnderstand",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "definition",
    type: "ExtensionDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ExtensionDefinition",
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "extensionAttributeDefinitions",
    type: "ExtensionAttributeDefinition",
    isMany: true
  }]
}, {
  name: "ExtensionAttributeDefinition",
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "type",
    isAttr: true,
    type: "String"
  }, {
    name: "isReference",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "extensionDefinition",
    type: "ExtensionDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ExtensionElements",
  properties: [{
    name: "valueRef",
    isAttr: true,
    isReference: true,
    type: "Element"
  }, {
    name: "values",
    type: "Element",
    isMany: true
  }, {
    name: "extensionAttributeDefinition",
    type: "ExtensionAttributeDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Documentation",
  superClass: ["BaseElement"],
  properties: [{
    name: "text",
    type: "String",
    isBody: true
  }, {
    name: "textFormat",
    "default": "text/plain",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Event",
  isAbstract: true,
  superClass: ["FlowNode", "InteractionNode"],
  properties: [{
    name: "properties",
    type: "Property",
    isMany: true
  }]
}, {
  name: "IntermediateCatchEvent",
  superClass: ["CatchEvent"]
}, {
  name: "IntermediateThrowEvent",
  superClass: ["ThrowEvent"]
}, {
  name: "EndEvent",
  superClass: ["ThrowEvent"]
}, {
  name: "StartEvent",
  superClass: ["CatchEvent"],
  properties: [{
    name: "isInterrupting",
    "default": true,
    isAttr: true,
    type: "Boolean"
  }]
}, {
  name: "ThrowEvent",
  isAbstract: true,
  superClass: ["Event"],
  properties: [{
    name: "dataInputs",
    type: "DataInput",
    isMany: true
  }, {
    name: "dataInputAssociations",
    type: "DataInputAssociation",
    isMany: true
  }, {
    name: "inputSet",
    type: "InputSet"
  }, {
    name: "eventDefinitions",
    type: "EventDefinition",
    isMany: true
  }, {
    name: "eventDefinitionRef",
    type: "EventDefinition",
    isMany: true,
    isReference: true
  }]
}, {
  name: "CatchEvent",
  isAbstract: true,
  superClass: ["Event"],
  properties: [{
    name: "parallelMultiple",
    isAttr: true,
    type: "Boolean",
    "default": false
  }, {
    name: "dataOutputs",
    type: "DataOutput",
    isMany: true
  }, {
    name: "dataOutputAssociations",
    type: "DataOutputAssociation",
    isMany: true
  }, {
    name: "outputSet",
    type: "OutputSet"
  }, {
    name: "eventDefinitions",
    type: "EventDefinition",
    isMany: true
  }, {
    name: "eventDefinitionRef",
    type: "EventDefinition",
    isMany: true,
    isReference: true
  }]
}, {
  name: "BoundaryEvent",
  superClass: ["CatchEvent"],
  properties: [{
    name: "cancelActivity",
    "default": true,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "attachedToRef",
    type: "Activity",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "EventDefinition",
  isAbstract: true,
  superClass: ["RootElement"]
}, {
  name: "CancelEventDefinition",
  superClass: ["EventDefinition"]
}, {
  name: "ErrorEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "errorRef",
    type: "Error",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "TerminateEventDefinition",
  superClass: ["EventDefinition"]
}, {
  name: "EscalationEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "escalationRef",
    type: "Escalation",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Escalation",
  properties: [{
    name: "structureRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "escalationCode",
    isAttr: true,
    type: "String"
  }],
  superClass: ["RootElement"]
}, {
  name: "CompensateEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "waitForCompletion",
    isAttr: true,
    type: "Boolean",
    "default": true
  }, {
    name: "activityRef",
    type: "Activity",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "TimerEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "timeDate",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "timeCycle",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "timeDuration",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }]
}, {
  name: "LinkEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "target",
    type: "LinkEventDefinition",
    isReference: true
  }, {
    name: "source",
    type: "LinkEventDefinition",
    isMany: true,
    isReference: true
  }]
}, {
  name: "MessageEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "messageRef",
    type: "Message",
    isAttr: true,
    isReference: true
  }, {
    name: "operationRef",
    type: "Operation",
    isReference: true
  }]
}, {
  name: "ConditionalEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "condition",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }]
}, {
  name: "SignalEventDefinition",
  superClass: ["EventDefinition"],
  properties: [{
    name: "signalRef",
    type: "Signal",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Signal",
  superClass: ["RootElement"],
  properties: [{
    name: "structureRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ImplicitThrowEvent",
  superClass: ["ThrowEvent"]
}, {
  name: "DataState",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ItemAwareElement",
  superClass: ["BaseElement"],
  properties: [{
    name: "itemSubjectRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }, {
    name: "dataState",
    type: "DataState"
  }]
}, {
  name: "DataAssociation",
  superClass: ["BaseElement"],
  properties: [{
    name: "sourceRef",
    type: "ItemAwareElement",
    isMany: true,
    isReference: true
  }, {
    name: "targetRef",
    type: "ItemAwareElement",
    isReference: true
  }, {
    name: "transformation",
    type: "FormalExpression",
    xml: {
      serialize: "property"
    }
  }, {
    name: "assignment",
    type: "Assignment",
    isMany: true
  }]
}, {
  name: "DataInput",
  superClass: ["ItemAwareElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "isCollection",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "inputSetRef",
    type: "InputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "inputSetWithOptional",
    type: "InputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "inputSetWithWhileExecuting",
    type: "InputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }]
}, {
  name: "DataOutput",
  superClass: ["ItemAwareElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "isCollection",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "outputSetRef",
    type: "OutputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "outputSetWithOptional",
    type: "OutputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "outputSetWithWhileExecuting",
    type: "OutputSet",
    isMany: true,
    isVirtual: true,
    isReference: true
  }]
}, {
  name: "InputSet",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "dataInputRefs",
    type: "DataInput",
    isMany: true,
    isReference: true
  }, {
    name: "optionalInputRefs",
    type: "DataInput",
    isMany: true,
    isReference: true
  }, {
    name: "whileExecutingInputRefs",
    type: "DataInput",
    isMany: true,
    isReference: true
  }, {
    name: "outputSetRefs",
    type: "OutputSet",
    isMany: true,
    isReference: true
  }]
}, {
  name: "OutputSet",
  superClass: ["BaseElement"],
  properties: [{
    name: "dataOutputRefs",
    type: "DataOutput",
    isMany: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "inputSetRefs",
    type: "InputSet",
    isMany: true,
    isReference: true
  }, {
    name: "optionalOutputRefs",
    type: "DataOutput",
    isMany: true,
    isReference: true
  }, {
    name: "whileExecutingOutputRefs",
    type: "DataOutput",
    isMany: true,
    isReference: true
  }]
}, {
  name: "Property",
  superClass: ["ItemAwareElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "DataInputAssociation",
  superClass: ["DataAssociation"]
}, {
  name: "DataOutputAssociation",
  superClass: ["DataAssociation"]
}, {
  name: "InputOutputSpecification",
  superClass: ["BaseElement"],
  properties: [{
    name: "dataInputs",
    type: "DataInput",
    isMany: true
  }, {
    name: "dataOutputs",
    type: "DataOutput",
    isMany: true
  }, {
    name: "inputSets",
    type: "InputSet",
    isMany: true
  }, {
    name: "outputSets",
    type: "OutputSet",
    isMany: true
  }]
}, {
  name: "DataObject",
  superClass: ["FlowElement", "ItemAwareElement"],
  properties: [{
    name: "isCollection",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }]
}, {
  name: "InputOutputBinding",
  properties: [{
    name: "inputDataRef",
    type: "InputSet",
    isAttr: true,
    isReference: true
  }, {
    name: "outputDataRef",
    type: "OutputSet",
    isAttr: true,
    isReference: true
  }, {
    name: "operationRef",
    type: "Operation",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Assignment",
  superClass: ["BaseElement"],
  properties: [{
    name: "from",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "to",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }]
}, {
  name: "DataStore",
  superClass: ["RootElement", "ItemAwareElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "capacity",
    isAttr: true,
    type: "Integer"
  }, {
    name: "isUnlimited",
    "default": true,
    isAttr: true,
    type: "Boolean"
  }]
}, {
  name: "DataStoreReference",
  superClass: ["ItemAwareElement", "FlowElement"],
  properties: [{
    name: "dataStoreRef",
    type: "DataStore",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "DataObjectReference",
  superClass: ["ItemAwareElement", "FlowElement"],
  properties: [{
    name: "dataObjectRef",
    type: "DataObject",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ConversationLink",
  superClass: ["BaseElement"],
  properties: [{
    name: "sourceRef",
    type: "InteractionNode",
    isAttr: true,
    isReference: true
  }, {
    name: "targetRef",
    type: "InteractionNode",
    isAttr: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ConversationAssociation",
  superClass: ["BaseElement"],
  properties: [{
    name: "innerConversationNodeRef",
    type: "ConversationNode",
    isAttr: true,
    isReference: true
  }, {
    name: "outerConversationNodeRef",
    type: "ConversationNode",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "CallConversation",
  superClass: ["ConversationNode"],
  properties: [{
    name: "calledCollaborationRef",
    type: "Collaboration",
    isAttr: true,
    isReference: true
  }, {
    name: "participantAssociations",
    type: "ParticipantAssociation",
    isMany: true
  }]
}, {
  name: "Conversation",
  superClass: ["ConversationNode"]
}, {
  name: "SubConversation",
  superClass: ["ConversationNode"],
  properties: [{
    name: "conversationNodes",
    type: "ConversationNode",
    isMany: true
  }]
}, {
  name: "ConversationNode",
  isAbstract: true,
  superClass: ["InteractionNode", "BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "participantRef",
    type: "Participant",
    isMany: true,
    isReference: true
  }, {
    name: "messageFlowRefs",
    type: "MessageFlow",
    isMany: true,
    isReference: true
  }, {
    name: "correlationKeys",
    type: "CorrelationKey",
    isMany: true
  }]
}, {
  name: "GlobalConversation",
  superClass: ["Collaboration"]
}, {
  name: "PartnerEntity",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "participantRef",
    type: "Participant",
    isMany: true,
    isReference: true
  }]
}, {
  name: "PartnerRole",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "participantRef",
    type: "Participant",
    isMany: true,
    isReference: true
  }]
}, {
  name: "CorrelationProperty",
  superClass: ["RootElement"],
  properties: [{
    name: "correlationPropertyRetrievalExpression",
    type: "CorrelationPropertyRetrievalExpression",
    isMany: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "type",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Error",
  superClass: ["RootElement"],
  properties: [{
    name: "structureRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "errorCode",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "CorrelationKey",
  superClass: ["BaseElement"],
  properties: [{
    name: "correlationPropertyRef",
    type: "CorrelationProperty",
    isMany: true,
    isReference: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Expression",
  superClass: ["BaseElement"],
  isAbstract: false,
  properties: [{
    name: "body",
    isBody: true,
    type: "String"
  }]
}, {
  name: "FormalExpression",
  superClass: ["Expression"],
  properties: [{
    name: "language",
    isAttr: true,
    type: "String"
  }, {
    name: "evaluatesToTypeRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Message",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "itemRef",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ItemDefinition",
  superClass: ["RootElement"],
  properties: [{
    name: "itemKind",
    type: "ItemKind",
    isAttr: true
  }, {
    name: "structureRef",
    isAttr: true,
    type: "String"
  }, {
    name: "isCollection",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "import",
    type: "Import",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "FlowElement",
  isAbstract: true,
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "auditing",
    type: "Auditing"
  }, {
    name: "monitoring",
    type: "Monitoring"
  }, {
    name: "categoryValueRef",
    type: "CategoryValue",
    isMany: true,
    isReference: true
  }]
}, {
  name: "SequenceFlow",
  superClass: ["FlowElement"],
  properties: [{
    name: "isImmediate",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "conditionExpression",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "sourceRef",
    type: "FlowNode",
    isAttr: true,
    isReference: true
  }, {
    name: "targetRef",
    type: "FlowNode",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "FlowElementsContainer",
  isAbstract: true,
  superClass: ["BaseElement"],
  properties: [{
    name: "laneSets",
    type: "LaneSet",
    isMany: true
  }, {
    name: "flowElements",
    type: "FlowElement",
    isMany: true
  }]
}, {
  name: "CallableElement",
  isAbstract: true,
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "ioSpecification",
    type: "InputOutputSpecification",
    xml: {
      serialize: "property"
    }
  }, {
    name: "supportedInterfaceRef",
    type: "Interface",
    isMany: true,
    isReference: true
  }, {
    name: "ioBinding",
    type: "InputOutputBinding",
    isMany: true,
    xml: {
      serialize: "property"
    }
  }]
}, {
  name: "FlowNode",
  isAbstract: true,
  superClass: ["FlowElement"],
  properties: [{
    name: "incoming",
    type: "SequenceFlow",
    isMany: true,
    isReference: true
  }, {
    name: "outgoing",
    type: "SequenceFlow",
    isMany: true,
    isReference: true
  }, {
    name: "lanes",
    type: "Lane",
    isMany: true,
    isVirtual: true,
    isReference: true
  }]
}, {
  name: "CorrelationPropertyRetrievalExpression",
  superClass: ["BaseElement"],
  properties: [{
    name: "messagePath",
    type: "FormalExpression"
  }, {
    name: "messageRef",
    type: "Message",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "CorrelationPropertyBinding",
  superClass: ["BaseElement"],
  properties: [{
    name: "dataPath",
    type: "FormalExpression"
  }, {
    name: "correlationPropertyRef",
    type: "CorrelationProperty",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Resource",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "resourceParameters",
    type: "ResourceParameter",
    isMany: true
  }]
}, {
  name: "ResourceParameter",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "isRequired",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "type",
    type: "ItemDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "CorrelationSubscription",
  superClass: ["BaseElement"],
  properties: [{
    name: "correlationKeyRef",
    type: "CorrelationKey",
    isAttr: true,
    isReference: true
  }, {
    name: "correlationPropertyBinding",
    type: "CorrelationPropertyBinding",
    isMany: true
  }]
}, {
  name: "MessageFlow",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "sourceRef",
    type: "InteractionNode",
    isAttr: true,
    isReference: true
  }, {
    name: "targetRef",
    type: "InteractionNode",
    isAttr: true,
    isReference: true
  }, {
    name: "messageRef",
    type: "Message",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "MessageFlowAssociation",
  superClass: ["BaseElement"],
  properties: [{
    name: "innerMessageFlowRef",
    type: "MessageFlow",
    isAttr: true,
    isReference: true
  }, {
    name: "outerMessageFlowRef",
    type: "MessageFlow",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "InteractionNode",
  isAbstract: true,
  properties: [{
    name: "incomingConversationLinks",
    type: "ConversationLink",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "outgoingConversationLinks",
    type: "ConversationLink",
    isMany: true,
    isVirtual: true,
    isReference: true
  }]
}, {
  name: "Participant",
  superClass: ["InteractionNode", "BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "interfaceRef",
    type: "Interface",
    isMany: true,
    isReference: true
  }, {
    name: "participantMultiplicity",
    type: "ParticipantMultiplicity"
  }, {
    name: "endPointRefs",
    type: "EndPoint",
    isMany: true,
    isReference: true
  }, {
    name: "processRef",
    type: "Process",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ParticipantAssociation",
  superClass: ["BaseElement"],
  properties: [{
    name: "innerParticipantRef",
    type: "Participant",
    isAttr: true,
    isReference: true
  }, {
    name: "outerParticipantRef",
    type: "Participant",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ParticipantMultiplicity",
  properties: [{
    name: "minimum",
    "default": 0,
    isAttr: true,
    type: "Integer"
  }, {
    name: "maximum",
    "default": 1,
    isAttr: true,
    type: "Integer"
  }],
  superClass: ["BaseElement"]
}, {
  name: "Collaboration",
  superClass: ["RootElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "isClosed",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "participants",
    type: "Participant",
    isMany: true
  }, {
    name: "messageFlows",
    type: "MessageFlow",
    isMany: true
  }, {
    name: "artifacts",
    type: "Artifact",
    isMany: true
  }, {
    name: "conversations",
    type: "ConversationNode",
    isMany: true
  }, {
    name: "conversationAssociations",
    type: "ConversationAssociation"
  }, {
    name: "participantAssociations",
    type: "ParticipantAssociation",
    isMany: true
  }, {
    name: "messageFlowAssociations",
    type: "MessageFlowAssociation",
    isMany: true
  }, {
    name: "correlationKeys",
    type: "CorrelationKey",
    isMany: true
  }, {
    name: "choreographyRef",
    type: "Choreography",
    isMany: true,
    isReference: true
  }, {
    name: "conversationLinks",
    type: "ConversationLink",
    isMany: true
  }]
}, {
  name: "ChoreographyActivity",
  isAbstract: true,
  superClass: ["FlowNode"],
  properties: [{
    name: "participantRef",
    type: "Participant",
    isMany: true,
    isReference: true
  }, {
    name: "initiatingParticipantRef",
    type: "Participant",
    isAttr: true,
    isReference: true
  }, {
    name: "correlationKeys",
    type: "CorrelationKey",
    isMany: true
  }, {
    name: "loopType",
    type: "ChoreographyLoopType",
    "default": "None",
    isAttr: true
  }]
}, {
  name: "CallChoreography",
  superClass: ["ChoreographyActivity"],
  properties: [{
    name: "calledChoreographyRef",
    type: "Choreography",
    isAttr: true,
    isReference: true
  }, {
    name: "participantAssociations",
    type: "ParticipantAssociation",
    isMany: true
  }]
}, {
  name: "SubChoreography",
  superClass: ["ChoreographyActivity", "FlowElementsContainer"],
  properties: [{
    name: "artifacts",
    type: "Artifact",
    isMany: true
  }]
}, {
  name: "ChoreographyTask",
  superClass: ["ChoreographyActivity"],
  properties: [{
    name: "messageFlowRef",
    type: "MessageFlow",
    isMany: true,
    isReference: true
  }]
}, {
  name: "Choreography",
  superClass: ["Collaboration", "FlowElementsContainer"]
}, {
  name: "GlobalChoreographyTask",
  superClass: ["Choreography"],
  properties: [{
    name: "initiatingParticipantRef",
    type: "Participant",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "TextAnnotation",
  superClass: ["Artifact"],
  properties: [{
    name: "text",
    type: "String"
  }, {
    name: "textFormat",
    "default": "text/plain",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Group",
  superClass: ["Artifact"],
  properties: [{
    name: "categoryValueRef",
    type: "CategoryValue",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Association",
  superClass: ["Artifact"],
  properties: [{
    name: "associationDirection",
    type: "AssociationDirection",
    isAttr: true
  }, {
    name: "sourceRef",
    type: "BaseElement",
    isAttr: true,
    isReference: true
  }, {
    name: "targetRef",
    type: "BaseElement",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "Category",
  superClass: ["RootElement"],
  properties: [{
    name: "categoryValue",
    type: "CategoryValue",
    isMany: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Artifact",
  isAbstract: true,
  superClass: ["BaseElement"]
}, {
  name: "CategoryValue",
  superClass: ["BaseElement"],
  properties: [{
    name: "categorizedFlowElements",
    type: "FlowElement",
    isMany: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "value",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Activity",
  isAbstract: true,
  superClass: ["FlowNode"],
  properties: [{
    name: "isForCompensation",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "default",
    type: "SequenceFlow",
    isAttr: true,
    isReference: true
  }, {
    name: "ioSpecification",
    type: "InputOutputSpecification",
    xml: {
      serialize: "property"
    }
  }, {
    name: "boundaryEventRefs",
    type: "BoundaryEvent",
    isMany: true,
    isReference: true
  }, {
    name: "properties",
    type: "Property",
    isMany: true
  }, {
    name: "dataInputAssociations",
    type: "DataInputAssociation",
    isMany: true
  }, {
    name: "dataOutputAssociations",
    type: "DataOutputAssociation",
    isMany: true
  }, {
    name: "startQuantity",
    "default": 1,
    isAttr: true,
    type: "Integer"
  }, {
    name: "resources",
    type: "ResourceRole",
    isMany: true
  }, {
    name: "completionQuantity",
    "default": 1,
    isAttr: true,
    type: "Integer"
  }, {
    name: "loopCharacteristics",
    type: "LoopCharacteristics"
  }]
}, {
  name: "ServiceTask",
  superClass: ["Task"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }, {
    name: "operationRef",
    type: "Operation",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "SubProcess",
  superClass: ["Activity", "FlowElementsContainer", "InteractionNode"],
  properties: [{
    name: "triggeredByEvent",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "artifacts",
    type: "Artifact",
    isMany: true
  }]
}, {
  name: "LoopCharacteristics",
  isAbstract: true,
  superClass: ["BaseElement"]
}, {
  name: "MultiInstanceLoopCharacteristics",
  superClass: ["LoopCharacteristics"],
  properties: [{
    name: "isSequential",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "behavior",
    type: "MultiInstanceBehavior",
    "default": "All",
    isAttr: true
  }, {
    name: "loopCardinality",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "loopDataInputRef",
    type: "ItemAwareElement",
    isReference: true
  }, {
    name: "loopDataOutputRef",
    type: "ItemAwareElement",
    isReference: true
  }, {
    name: "inputDataItem",
    type: "DataInput",
    xml: {
      serialize: "property"
    }
  }, {
    name: "outputDataItem",
    type: "DataOutput",
    xml: {
      serialize: "property"
    }
  }, {
    name: "complexBehaviorDefinition",
    type: "ComplexBehaviorDefinition",
    isMany: true
  }, {
    name: "completionCondition",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "oneBehaviorEventRef",
    type: "EventDefinition",
    isAttr: true,
    isReference: true
  }, {
    name: "noneBehaviorEventRef",
    type: "EventDefinition",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "StandardLoopCharacteristics",
  superClass: ["LoopCharacteristics"],
  properties: [{
    name: "testBefore",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "loopCondition",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "loopMaximum",
    type: "Integer",
    isAttr: true
  }]
}, {
  name: "CallActivity",
  superClass: ["Activity", "InteractionNode"],
  properties: [{
    name: "calledElement",
    type: "String",
    isAttr: true
  }]
}, {
  name: "Task",
  superClass: ["Activity", "InteractionNode"]
}, {
  name: "SendTask",
  superClass: ["Task"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }, {
    name: "operationRef",
    type: "Operation",
    isAttr: true,
    isReference: true
  }, {
    name: "messageRef",
    type: "Message",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ReceiveTask",
  superClass: ["Task"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }, {
    name: "instantiate",
    "default": false,
    isAttr: true,
    type: "Boolean"
  }, {
    name: "operationRef",
    type: "Operation",
    isAttr: true,
    isReference: true
  }, {
    name: "messageRef",
    type: "Message",
    isAttr: true,
    isReference: true
  }]
}, {
  name: "ScriptTask",
  superClass: ["Task"],
  properties: [{
    name: "scriptFormat",
    isAttr: true,
    type: "String"
  }, {
    name: "script",
    type: "String"
  }]
}, {
  name: "BusinessRuleTask",
  superClass: ["Task"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "AdHocSubProcess",
  superClass: ["SubProcess"],
  properties: [{
    name: "completionCondition",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "ordering",
    type: "AdHocOrdering",
    isAttr: true
  }, {
    name: "cancelRemainingInstances",
    "default": true,
    isAttr: true,
    type: "Boolean"
  }]
}, {
  name: "Transaction",
  superClass: ["SubProcess"],
  properties: [{
    name: "protocol",
    isAttr: true,
    type: "String"
  }, {
    name: "method",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "GlobalScriptTask",
  superClass: ["GlobalTask"],
  properties: [{
    name: "scriptLanguage",
    isAttr: true,
    type: "String"
  }, {
    name: "script",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "GlobalBusinessRuleTask",
  superClass: ["GlobalTask"],
  properties: [{
    name: "implementation",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ComplexBehaviorDefinition",
  superClass: ["BaseElement"],
  properties: [{
    name: "condition",
    type: "FormalExpression"
  }, {
    name: "event",
    type: "ImplicitThrowEvent"
  }]
}, {
  name: "ResourceRole",
  superClass: ["BaseElement"],
  properties: [{
    name: "resourceRef",
    type: "Resource",
    isReference: true
  }, {
    name: "resourceParameterBindings",
    type: "ResourceParameterBinding",
    isMany: true
  }, {
    name: "resourceAssignmentExpression",
    type: "ResourceAssignmentExpression"
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ResourceParameterBinding",
  properties: [{
    name: "expression",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }, {
    name: "parameterRef",
    type: "ResourceParameter",
    isAttr: true,
    isReference: true
  }],
  superClass: ["BaseElement"]
}, {
  name: "ResourceAssignmentExpression",
  properties: [{
    name: "expression",
    type: "Expression",
    xml: {
      serialize: "xsi:type"
    }
  }],
  superClass: ["BaseElement"]
}, {
  name: "Import",
  properties: [{
    name: "importType",
    isAttr: true,
    type: "String"
  }, {
    name: "location",
    isAttr: true,
    type: "String"
  }, {
    name: "namespace",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "Definitions",
  superClass: ["BaseElement"],
  properties: [{
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "targetNamespace",
    isAttr: true,
    type: "String"
  }, {
    name: "expressionLanguage",
    "default": "http://www.w3.org/1999/XPath",
    isAttr: true,
    type: "String"
  }, {
    name: "typeLanguage",
    "default": "http://www.w3.org/2001/XMLSchema",
    isAttr: true,
    type: "String"
  }, {
    name: "imports",
    type: "Import",
    isMany: true
  }, {
    name: "extensions",
    type: "Extension",
    isMany: true
  }, {
    name: "rootElements",
    type: "RootElement",
    isMany: true
  }, {
    name: "diagrams",
    isMany: true,
    type: "bpmndi:BPMNDiagram"
  }, {
    name: "exporter",
    isAttr: true,
    type: "String"
  }, {
    name: "relationships",
    type: "Relationship",
    isMany: true
  }, {
    name: "exporterVersion",
    isAttr: true,
    type: "String"
  }]
}];
var enumerations$3 = [{
  name: "ProcessType",
  literalValues: [{
    name: "None"
  }, {
    name: "Public"
  }, {
    name: "Private"
  }]
}, {
  name: "GatewayDirection",
  literalValues: [{
    name: "Unspecified"
  }, {
    name: "Converging"
  }, {
    name: "Diverging"
  }, {
    name: "Mixed"
  }]
}, {
  name: "EventBasedGatewayType",
  literalValues: [{
    name: "Parallel"
  }, {
    name: "Exclusive"
  }]
}, {
  name: "RelationshipDirection",
  literalValues: [{
    name: "None"
  }, {
    name: "Forward"
  }, {
    name: "Backward"
  }, {
    name: "Both"
  }]
}, {
  name: "ItemKind",
  literalValues: [{
    name: "Physical"
  }, {
    name: "Information"
  }]
}, {
  name: "ChoreographyLoopType",
  literalValues: [{
    name: "None"
  }, {
    name: "Standard"
  }, {
    name: "MultiInstanceSequential"
  }, {
    name: "MultiInstanceParallel"
  }]
}, {
  name: "AssociationDirection",
  literalValues: [{
    name: "None"
  }, {
    name: "One"
  }, {
    name: "Both"
  }]
}, {
  name: "MultiInstanceBehavior",
  literalValues: [{
    name: "None"
  }, {
    name: "One"
  }, {
    name: "All"
  }, {
    name: "Complex"
  }]
}, {
  name: "AdHocOrdering",
  literalValues: [{
    name: "Parallel"
  }, {
    name: "Sequential"
  }]
}];
var xml$1 = {
  tagAlias: "lowerCase",
  typePrefix: "t"
};
var BpmnPackage = {
  name: name$5,
  uri: uri$5,
  prefix: prefix$5,
  associations: associations$5,
  types: types$5,
  enumerations: enumerations$3,
  xml: xml$1
};
var name$4 = "BPMNDI";
var uri$4 = "http://www.omg.org/spec/BPMN/20100524/DI";
var prefix$4 = "bpmndi";
var types$4 = [{
  name: "BPMNDiagram",
  properties: [{
    name: "plane",
    type: "BPMNPlane",
    redefines: "di:Diagram#rootElement"
  }, {
    name: "labelStyle",
    type: "BPMNLabelStyle",
    isMany: true
  }],
  superClass: ["di:Diagram"]
}, {
  name: "BPMNPlane",
  properties: [{
    name: "bpmnElement",
    isAttr: true,
    isReference: true,
    type: "bpmn:BaseElement",
    redefines: "di:DiagramElement#modelElement"
  }],
  superClass: ["di:Plane"]
}, {
  name: "BPMNShape",
  properties: [{
    name: "bpmnElement",
    isAttr: true,
    isReference: true,
    type: "bpmn:BaseElement",
    redefines: "di:DiagramElement#modelElement"
  }, {
    name: "isHorizontal",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "isExpanded",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "isMarkerVisible",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "label",
    type: "BPMNLabel"
  }, {
    name: "isMessageVisible",
    isAttr: true,
    type: "Boolean"
  }, {
    name: "participantBandKind",
    type: "ParticipantBandKind",
    isAttr: true
  }, {
    name: "choreographyActivityShape",
    type: "BPMNShape",
    isAttr: true,
    isReference: true
  }],
  superClass: ["di:LabeledShape"]
}, {
  name: "BPMNEdge",
  properties: [{
    name: "label",
    type: "BPMNLabel"
  }, {
    name: "bpmnElement",
    isAttr: true,
    isReference: true,
    type: "bpmn:BaseElement",
    redefines: "di:DiagramElement#modelElement"
  }, {
    name: "sourceElement",
    isAttr: true,
    isReference: true,
    type: "di:DiagramElement",
    redefines: "di:Edge#source"
  }, {
    name: "targetElement",
    isAttr: true,
    isReference: true,
    type: "di:DiagramElement",
    redefines: "di:Edge#target"
  }, {
    name: "messageVisibleKind",
    type: "MessageVisibleKind",
    isAttr: true,
    "default": "initiating"
  }],
  superClass: ["di:LabeledEdge"]
}, {
  name: "BPMNLabel",
  properties: [{
    name: "labelStyle",
    type: "BPMNLabelStyle",
    isAttr: true,
    isReference: true,
    redefines: "di:DiagramElement#style"
  }],
  superClass: ["di:Label"]
}, {
  name: "BPMNLabelStyle",
  properties: [{
    name: "font",
    type: "dc:Font"
  }],
  superClass: ["di:Style"]
}];
var enumerations$2 = [{
  name: "ParticipantBandKind",
  literalValues: [{
    name: "top_initiating"
  }, {
    name: "middle_initiating"
  }, {
    name: "bottom_initiating"
  }, {
    name: "top_non_initiating"
  }, {
    name: "middle_non_initiating"
  }, {
    name: "bottom_non_initiating"
  }]
}, {
  name: "MessageVisibleKind",
  literalValues: [{
    name: "initiating"
  }, {
    name: "non_initiating"
  }]
}];
var associations$4 = [];
var BpmnDiPackage = {
  name: name$4,
  uri: uri$4,
  prefix: prefix$4,
  types: types$4,
  enumerations: enumerations$2,
  associations: associations$4
};
var name$3 = "DC";
var uri$3 = "http://www.omg.org/spec/DD/20100524/DC";
var prefix$3 = "dc";
var types$3 = [{
  name: "Boolean"
}, {
  name: "Integer"
}, {
  name: "Real"
}, {
  name: "String"
}, {
  name: "Font",
  properties: [{
    name: "name",
    type: "String",
    isAttr: true
  }, {
    name: "size",
    type: "Real",
    isAttr: true
  }, {
    name: "isBold",
    type: "Boolean",
    isAttr: true
  }, {
    name: "isItalic",
    type: "Boolean",
    isAttr: true
  }, {
    name: "isUnderline",
    type: "Boolean",
    isAttr: true
  }, {
    name: "isStrikeThrough",
    type: "Boolean",
    isAttr: true
  }]
}, {
  name: "Point",
  properties: [{
    name: "x",
    type: "Real",
    "default": "0",
    isAttr: true
  }, {
    name: "y",
    type: "Real",
    "default": "0",
    isAttr: true
  }]
}, {
  name: "Bounds",
  properties: [{
    name: "x",
    type: "Real",
    "default": "0",
    isAttr: true
  }, {
    name: "y",
    type: "Real",
    "default": "0",
    isAttr: true
  }, {
    name: "width",
    type: "Real",
    isAttr: true
  }, {
    name: "height",
    type: "Real",
    isAttr: true
  }]
}];
var associations$3 = [];
var DcPackage = {
  name: name$3,
  uri: uri$3,
  prefix: prefix$3,
  types: types$3,
  associations: associations$3
};
var name$2 = "DI";
var uri$2 = "http://www.omg.org/spec/DD/20100524/DI";
var prefix$2 = "di";
var types$2 = [{
  name: "DiagramElement",
  isAbstract: true,
  properties: [{
    name: "id",
    isAttr: true,
    isId: true,
    type: "String"
  }, {
    name: "extension",
    type: "Extension"
  }, {
    name: "owningDiagram",
    type: "Diagram",
    isReadOnly: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "owningElement",
    type: "DiagramElement",
    isReadOnly: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "modelElement",
    isReadOnly: true,
    isVirtual: true,
    isReference: true,
    type: "Element"
  }, {
    name: "style",
    type: "Style",
    isReadOnly: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "ownedElement",
    type: "DiagramElement",
    isReadOnly: true,
    isMany: true,
    isVirtual: true
  }]
}, {
  name: "Node",
  isAbstract: true,
  superClass: ["DiagramElement"]
}, {
  name: "Edge",
  isAbstract: true,
  superClass: ["DiagramElement"],
  properties: [{
    name: "source",
    type: "DiagramElement",
    isReadOnly: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "target",
    type: "DiagramElement",
    isReadOnly: true,
    isVirtual: true,
    isReference: true
  }, {
    name: "waypoint",
    isUnique: false,
    isMany: true,
    type: "dc:Point",
    xml: {
      serialize: "xsi:type"
    }
  }]
}, {
  name: "Diagram",
  isAbstract: true,
  properties: [{
    name: "id",
    isAttr: true,
    isId: true,
    type: "String"
  }, {
    name: "rootElement",
    type: "DiagramElement",
    isReadOnly: true,
    isVirtual: true
  }, {
    name: "name",
    isAttr: true,
    type: "String"
  }, {
    name: "documentation",
    isAttr: true,
    type: "String"
  }, {
    name: "resolution",
    isAttr: true,
    type: "Real"
  }, {
    name: "ownedStyle",
    type: "Style",
    isReadOnly: true,
    isMany: true,
    isVirtual: true
  }]
}, {
  name: "Shape",
  isAbstract: true,
  superClass: ["Node"],
  properties: [{
    name: "bounds",
    type: "dc:Bounds"
  }]
}, {
  name: "Plane",
  isAbstract: true,
  superClass: ["Node"],
  properties: [{
    name: "planeElement",
    type: "DiagramElement",
    subsettedProperty: "DiagramElement-ownedElement",
    isMany: true
  }]
}, {
  name: "LabeledEdge",
  isAbstract: true,
  superClass: ["Edge"],
  properties: [{
    name: "ownedLabel",
    type: "Label",
    isReadOnly: true,
    subsettedProperty: "DiagramElement-ownedElement",
    isMany: true,
    isVirtual: true
  }]
}, {
  name: "LabeledShape",
  isAbstract: true,
  superClass: ["Shape"],
  properties: [{
    name: "ownedLabel",
    type: "Label",
    isReadOnly: true,
    subsettedProperty: "DiagramElement-ownedElement",
    isMany: true,
    isVirtual: true
  }]
}, {
  name: "Label",
  isAbstract: true,
  superClass: ["Node"],
  properties: [{
    name: "bounds",
    type: "dc:Bounds"
  }]
}, {
  name: "Style",
  isAbstract: true,
  properties: [{
    name: "id",
    isAttr: true,
    isId: true,
    type: "String"
  }]
}, {
  name: "Extension",
  properties: [{
    name: "values",
    isMany: true,
    type: "Element"
  }]
}];
var associations$2 = [];
var xml$2 = {
  tagAlias: "lowerCase"
};
var DiPackage = {
  name: name$2,
  uri: uri$2,
  prefix: prefix$2,
  types: types$2,
  associations: associations$2,
  xml: xml$2
};
var name$1 = "bpmn.io colors for BPMN";
var uri$1 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
var prefix$1 = "bioc";
var types$1 = [{
  name: "ColoredShape",
  "extends": ["bpmndi:BPMNShape"],
  properties: [{
    name: "stroke",
    isAttr: true,
    type: "String"
  }, {
    name: "fill",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ColoredEdge",
  "extends": ["bpmndi:BPMNEdge"],
  properties: [{
    name: "stroke",
    isAttr: true,
    type: "String"
  }, {
    name: "fill",
    isAttr: true,
    type: "String"
  }]
}];
var enumerations$1 = [];
var associations$1 = [];
var BiocPackage = {
  name: name$1,
  uri: uri$1,
  prefix: prefix$1,
  types: types$1,
  enumerations: enumerations$1,
  associations: associations$1
};
var name$6 = "BPMN in Color";
var uri$6 = "http://www.omg.org/spec/BPMN/non-normative/color/1.0";
var prefix$6 = "color";
var types$6 = [{
  name: "ColoredLabel",
  "extends": ["bpmndi:BPMNLabel"],
  properties: [{
    name: "color",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ColoredShape",
  "extends": ["bpmndi:BPMNShape"],
  properties: [{
    name: "background-color",
    isAttr: true,
    type: "String"
  }, {
    name: "border-color",
    isAttr: true,
    type: "String"
  }]
}, {
  name: "ColoredEdge",
  "extends": ["bpmndi:BPMNEdge"],
  properties: [{
    name: "border-color",
    isAttr: true,
    type: "String"
  }]
}];
var enumerations = [];
var associations$6 = [];
var BpmnInColorPackage = {
  name: name$6,
  uri: uri$6,
  prefix: prefix$6,
  types: types$6,
  enumerations: enumerations,
  associations: associations$6
};
const packages = {
  bpmn: BpmnPackage,
  bpmndi: BpmnDiPackage,
  dc: DcPackage,
  di: DiPackage,
  bioc: BiocPackage,
  color: BpmnInColorPackage
};
function SimpleBpmnModdle(additionalPackages, options) {
  const pks = assign({}, packages, additionalPackages);
  return new BpmnModdle(pks, options);
}

var name = "zeebe";
var prefix = "zeebe";
var uri = "http://camunda.org/schema/zeebe/1.0";
var xml = {
	tagAlias: "lowerCase"
};
var associations = [
];
var types = [
	{
		name: "ZeebeServiceTask",
		"extends": [
			"bpmn:ServiceTask",
			"bpmn:BusinessRuleTask",
			"bpmn:ScriptTask",
			"bpmn:SendTask",
			"bpmn:EndEvent",
			"bpmn:IntermediateThrowEvent",
			"bpmn:AdHocSubProcess"
		],
		properties: [
			{
				name: "retryCounter",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "IoMapping",
		superClass: [
			"Element"
		],
		properties: [
			{
				name: "ioMapping",
				type: "IoMapping"
			},
			{
				name: "inputParameters",
				isMany: true,
				type: "Input"
			},
			{
				name: "outputParameters",
				isMany: true,
				type: "Output"
			}
		],
		meta: {
			allowedIn: [
				"bpmn:CallActivity",
				"bpmn:Event",
				"bpmn:ReceiveTask",
				"zeebe:ZeebeServiceTask",
				"bpmn:SubProcess",
				"bpmn:UserTask"
			]
		}
	},
	{
		name: "InputOutputParameter",
		properties: [
			{
				name: "source",
				isAttr: true,
				type: "String"
			},
			{
				name: "target",
				isAttr: true,
				type: "String"
			}
		]
	},
	{
		name: "Subscription",
		superClass: [
			"Element"
		],
		properties: [
			{
				name: "correlationKey",
				isAttr: true,
				type: "String"
			}
		]
	},
	{
		name: "Input",
		superClass: [
			"InputOutputParameter"
		],
		meta: {
			allowedIn: [
				"bpmn:CallActivity",
				"zeebe:ZeebeServiceTask",
				"bpmn:SubProcess",
				"bpmn:UserTask"
			]
		}
	},
	{
		name: "Output",
		superClass: [
			"InputOutputParameter"
		],
		meta: {
			allowedIn: [
				"bpmn:CallActivity",
				"bpmn:Event",
				"bpmn:ReceiveTask",
				"zeebe:ZeebeServiceTask",
				"bpmn:SubProcess",
				"bpmn:UserTask"
			]
		}
	},
	{
		name: "TaskHeaders",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"zeebe:ZeebeServiceTask",
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "values",
				type: "Header",
				isMany: true
			}
		]
	},
	{
		name: "Header",
		superClass: [
			"Element"
		],
		properties: [
			{
				name: "id",
				type: "String",
				isAttr: true
			},
			{
				name: "key",
				type: "String",
				isAttr: true
			},
			{
				name: "value",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "TaskDefinition",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"zeebe:ZeebeServiceTask"
			]
		},
		properties: [
			{
				name: "type",
				type: "String",
				isAttr: true
			},
			{
				name: "retries",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "LoopCharacteristics",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:MultiInstanceLoopCharacteristics"
			]
		},
		properties: [
			{
				name: "inputCollection",
				type: "String",
				isAttr: true
			},
			{
				name: "inputElement",
				type: "String",
				isAttr: true
			},
			{
				name: "outputCollection",
				type: "String",
				isAttr: true
			},
			{
				name: "outputElement",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "CalledElement",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:CallActivity"
			]
		},
		properties: [
			{
				name: "processId",
				type: "String",
				isAttr: true
			},
			{
				name: "processIdExpression",
				type: "String",
				isAttr: true
			},
			{
				name: "propagateAllChildVariables",
				isAttr: true,
				type: "Boolean"
			},
			{
				name: "propagateAllParentVariables",
				isAttr: true,
				type: "Boolean",
				"default": true
			}
		]
	},
	{
		name: "UserTaskForm",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:Process"
			]
		},
		properties: [
			{
				name: "id",
				type: "String",
				isAttr: true
			},
			{
				name: "body",
				type: "String",
				isBody: true
			}
		]
	},
	{
		name: "FormDefinition",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "formKey",
				type: "String",
				isAttr: true
			},
			{
				name: "formId",
				type: "String",
				isAttr: true
			},
			{
				name: "externalReference",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "LinkedResource",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:ServiceTask"
			]
		},
		properties: [
			{
				name: "resourceId",
				type: "String",
				isAttr: true
			},
			{
				name: "resourceType",
				type: "String",
				isAttr: true
			},
			{
				name: "linkName",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "LinkedResources",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:ServiceTask"
			]
		},
		properties: [
			{
				name: "values",
				type: "LinkedResource",
				isMany: true
			}
		]
	},
	{
		name: "UserTask",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
		]
	},
	{
		name: "CalledDecision",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:BusinessRuleTask"
			]
		},
		properties: [
			{
				name: "decisionId",
				type: "String",
				isAttr: true
			},
			{
				name: "resultVariable",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "AssignmentDefinition",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "assignee",
				type: "String",
				isAttr: true
			},
			{
				name: "candidateGroups",
				type: "String",
				isAttr: true
			},
			{
				name: "candidateUsers",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "PriorityDefinition",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "priority",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "TaskSchedule",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "dueDate",
				type: "String",
				isAttr: true
			},
			{
				name: "followUpDate",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "Properties",
		superClass: [
			"Element"
		],
		properties: [
			{
				name: "properties",
				type: "Property",
				isMany: true
			}
		]
	},
	{
		name: "Property",
		properties: [
			{
				name: "name",
				type: "String",
				isAttr: true
			},
			{
				name: "value",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "TemplateSupported",
		isAbstract: true,
		"extends": [
			"bpmn:Collaboration",
			"bpmn:Process",
			"bpmn:FlowElement"
		],
		properties: [
			{
				name: "modelerTemplate",
				isAttr: true,
				type: "String"
			},
			{
				name: "modelerTemplateVersion",
				isAttr: true,
				type: "Integer"
			},
			{
				name: "modelerTemplateIcon",
				isAttr: true,
				type: "String"
			}
		]
	},
	{
		name: "TemplatedRootElement",
		isAbstract: true,
		"extends": [
			"bpmn:Error",
			"bpmn:Escalation",
			"bpmn:Message",
			"bpmn:Signal"
		],
		properties: [
			{
				name: "modelerTemplate",
				isAttr: true,
				type: "String"
			}
		]
	},
	{
		name: "Script",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:ScriptTask"
			]
		},
		properties: [
			{
				name: "expression",
				type: "String",
				isAttr: true
			},
			{
				name: "resultVariable",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "ExecutionListeners",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:Event",
				"bpmn:Activity",
				"bpmn:Process",
				"bpmn:ExclusiveGateway",
				"bpmn:InclusiveGateway",
				"bpmn:ParallelGateway",
				"bpmn:EventBasedGateway"
			]
		},
		properties: [
			{
				name: "listeners",
				type: "ExecutionListener",
				isMany: true
			}
		]
	},
	{
		name: "ExecutionListener",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"zeebe:ExecutionListeners"
			]
		},
		properties: [
			{
				name: "eventType",
				type: "String",
				isAttr: true
			},
			{
				name: "retries",
				type: "String",
				isAttr: true
			},
			{
				name: "type",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "TaskListeners",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:UserTask"
			]
		},
		properties: [
			{
				name: "listeners",
				type: "TaskListener",
				isMany: true
			}
		]
	},
	{
		name: "TaskListener",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"zeebe:TaskListeners"
			]
		},
		properties: [
			{
				name: "eventType",
				type: "String",
				isAttr: true
			},
			{
				name: "retries",
				type: "String",
				isAttr: true
			},
			{
				name: "type",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "VersionTag",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:Process"
			]
		},
		properties: [
			{
				name: "value",
				type: "String",
				isAttr: true
			}
		]
	},
	{
		name: "BindingTypeSupported",
		isAbstract: true,
		"extends": [
			"zeebe:CalledDecision",
			"zeebe:CalledElement",
			"zeebe:FormDefinition",
			"zeebe:LinkedResource"
		],
		properties: [
			{
				name: "bindingType",
				isAttr: true,
				type: "String",
				"default": "latest"
			},
			{
				name: "versionTag",
				isAttr: true,
				type: "String"
			}
		]
	},
	{
		name: "AdHoc",
		superClass: [
			"Element"
		],
		meta: {
			allowedIn: [
				"bpmn:AdHocSubProcess"
			]
		},
		properties: [
			{
				name: "activeElementsCollection",
				isAttr: true,
				type: "String"
			},
			{
				name: "outputCollection",
				isAttr: true,
				type: "String"
			},
			{
				name: "outputElement",
				isAttr: true,
				type: "String"
			}
		]
	}
];
var zeebeModdle = {
	name: name,
	prefix: prefix,
	uri: uri,
	xml: xml,
	associations: associations,
	types: types
};

/**
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.
 *
 * Camunda licenses this file to you under the MIT; you may not use this file
 * except in compliance with the MIT License.
 */

let lastTemplates = [],
  lastValidTemplates = [];
const ElementTemplateLinterPlugin = function (templates) {
  if (!templatesEqual(lastTemplates, templates)) {
    const moddle = new SimpleBpmnModdle({
      zeebe: zeebeModdle
    });
    const validator = new Validator(moddle).addAll(templates);
    const validTemplates = validator.getValidTemplates();
    lastTemplates = templates;
    lastValidTemplates = validTemplates;
  }
  return {
    config: {
      rules: {
        'element-templates/validate': ['error', {
          templates: lastValidTemplates
        }],
        'element-templates/compatibility': ['warn', {
          templates: lastValidTemplates
        }]
      }
    },
    resolver: new StaticResolver({
      'rule:bpmnlint-plugin-element-templates/validate': validate,
      'rule:bpmnlint-plugin-element-templates/compatibility': compatibility
    })
  };
};
function templatesEqual(a, b) {
  if (a.length !== b.length) {
    return false;
  }
  return JSON.stringify(a) === JSON.stringify(b);
}

export { coreModule$1 as CloudElementTemplatesCoreModule, ElementTemplateLinterPlugin as CloudElementTemplatesLinterPlugin, index$1 as CloudElementTemplatesPropertiesProviderModule, Validator as CloudElementTemplatesValidator, coreModule as ElementTemplatesCoreModule, index as ElementTemplatesPropertiesProviderModule };
//# sourceMappingURL=index.esm.js.map