import {
  append as svgAppend,
  attr as svgAttr,
  classes as svgClasses,
  create as svgCreate,
  select as svgSelect
} from 'tiny-svg'
import Ids from 'ids'
import { assign, forEach, isObject } from 'min-dash'
import { query as domQuery } from 'min-dom'

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'
import { createLine } from 'diagram-js/lib/util/RenderUtil'
import { rotate, transform, translate } from 'diagram-js/lib/util/SvgTransformUtil'
import {
  getCirclePath,
  getDi,
  getDiamondPath,
  getFillColor,
  getLabelColor,
  getRectPath,
  getRoundRectPath,
  getSemantic,
  getStrokeColor,
  isCollection,
  isThrowEvent,
  isTypedEvent
} from 'bpmn-js/lib/draw/BpmnRenderUtil.js'
import { is } from 'bpmn-js/lib/util/ModelUtil'
import { getLabel } from 'bpmn-js/lib/features/label-editing/LabelUtil'
import { isEventSubProcess, isExpanded } from 'bpmn-js/lib/util/DiUtil'

import mysqlIcon from '@/assets/img/bpmn/mysql.png'

const RENDERER_IDS = new Ids()
const TASK_BORDER_RADIUS = 10
const INNER_OUTER_DIST = 3
const DEFAULT_FILL_OPACITY = 0.95
const HIGH_FILL_OPACITY = 0.35
const ELEMENT_LABEL_DISTANCE = 10

class RewriteRenderer extends BaseRenderer {
  constructor(config, eventBus, styles, pathMap, canvas, textRenderer, elementRegistry, interactionEvents, priority) {
    super(eventBus, priority)
    this._elementRegistry = elementRegistry
    const presetColor = {
      defaultFillColor: '#ffffff',
      defaultStartEventColor: '#61c071',
      defaultEndEventColor: '#d03050',
      defaultIntermediateEventColor: '#e9a28d',
      defaultIntermediateThrowEventColor: '#e9a28d',
      defaultIntermediateCatchEventColor: '#e9a28d',
      defaultTaskColor: '#9cafcf',
      defaultLabelColor: '#000000',
      defaultGatewayColor: '#fb863c',
      defaultSequenceColor: '#9cafcf'
    }
    const presetOpacity = {
      defaultStartEventOpacity: 0,
      defaultEndEventOpacity: 0,
      defaultIntermediateThrowEventOpacity: 0,
      defaultIntermediateCatchEventOpacity: 1,
      defaultTaskOpacity: 0,
      defaultLabelOpacity: 1,
      defaultGatewayOpacity: 0.2,
      defaultSequenceOpacity: 1
    }
    const {
      defaultFillColor,
      defaultStartEventColor,
      defaultEndEventColor,
      defaultIntermediateEventColor,
      defaultIntermediateThrowEventColor,
      defaultIntermediateCatchEventColor,
      defaultTaskColor,
      defaultLabelColor,
      defaultGatewayColor,
      defaultSequenceColor
    } = { ...presetColor, ...config }
    const {
      defaultStartEventOpacity,
      defaultEndEventOpacity,
      defaultIntermediateThrowEventOpacity,
      defaultIntermediateCatchEventOpacity,
      defaultTaskOpacity,
      defaultGatewayOpacity
    } = { ...presetOpacity, ...config }

    const useCurve = config?.useCurve || false

    const computeStyle = styles.computeStyle
    const rendererId = RENDERER_IDS.next()
    const markers = {}

    function addMarker(id, options) {
      const attrs = assign(
        {
          fill: 'black',
          strokeWidth: 1,
          strokeLinecap: 'round',
          strokeDasharray: 'none'
        },
        options.attrs
      )
      const ref = options.ref || { x: 0, y: 0 }
      const scale = options.scale || 1
      if (attrs.strokeDasharray === 'none') {
        attrs.strokeDasharray = [10000, 1]
      }
      const marker = svgCreate('marker')
      svgAttr(options.element, attrs)
      svgAppend(marker, options.element)
      svgAttr(marker, {
        id: id,
        viewBox: '0 0 20 20',
        refX: ref.x,
        refY: ref.y,
        markerWidth: 20 * scale,
        markerHeight: 20 * scale,
        orient: 'auto'
      })
      let defs = domQuery('defs', canvas._svg)
      if (!defs) {
        defs = svgCreate('defs')
        svgAppend(canvas._svg, defs)
      }
      svgAppend(defs, marker)
      markers[id] = marker
    }

    function colorEscape(str) {
      return str.replace(/[^\da-zA-z]+/g, '_')
    }

    function marker(type, fill, stroke) {
      const id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId
      if (!markers[id]) {
        createMarker(id, type, fill, stroke)
      }
      return 'url(#' + id + ')'
    }

    function createMarker(id, type, fill, stroke) {
      if (type === 'sequenceflow-end') {
        const sequenceFlowEnd = svgCreate('path')
        svgAttr(sequenceFlowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' })
        addMarker(id, {
          element: sequenceFlowEnd,
          ref: { x: 11, y: 10 },
          scale: 0.5,
          attrs: {
            fill: stroke,
            stroke: stroke
          }
        })
      }
      if (type === 'messageflow-start') {
        const messageflowStart = svgCreate('circle')
        svgAttr(messageflowStart, { cx: 6, cy: 6, r: 3.5 })
        addMarker(id, {
          element: messageflowStart,
          attrs: {
            fill: fill,
            stroke: stroke
          },
          ref: { x: 6, y: 6 }
        })
      }
      if (type === 'messageflow-end') {
        const messageflowEnd = svgCreate('path')
        svgAttr(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' })
        addMarker(id, {
          element: messageflowEnd,
          attrs: {
            fill: fill,
            stroke: stroke,
            strokeLinecap: 'butt'
          },
          ref: { x: 8.5, y: 5 }
        })
      }
      if (type === 'association-start') {
        const associationStart = svgCreate('path')
        svgAttr(associationStart, { d: 'M 11 5 L 1 10 L 11 15' })
        addMarker(id, {
          element: associationStart,
          attrs: {
            fill: 'none',
            stroke: stroke,
            strokeWidth: 1.5
          },
          ref: { x: 1, y: 10 },
          scale: 0.5
        })
      }
      if (type === 'association-end') {
        const associationEnd = svgCreate('path')
        svgAttr(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' })
        addMarker(id, {
          element: associationEnd,
          attrs: {
            fill: 'none',
            stroke: stroke,
            strokeWidth: 1.5
          },
          ref: { x: 12, y: 10 },
          scale: 0.5
        })
      }
      if (type === 'conditional-flow-marker') {
        const conditionalflowMarker = svgCreate('path')
        svgAttr(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' })
        addMarker(id, {
          element: conditionalflowMarker,
          attrs: {
            fill: fill,
            stroke: stroke
          },
          ref: { x: -1, y: 10 },
          scale: 0.5
        })
      }
      if (type === 'conditional-default-flow-marker') {
        const conditionaldefaultflowMarker = svgCreate('path')
        svgAttr(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' })
        addMarker(id, {
          element: conditionaldefaultflowMarker,
          attrs: {
            stroke: stroke
          },
          ref: { x: 0, y: 10 },
          scale: 0.5
        })
      }
    }

    function drawCircle(parentGfx, width, height, offset, attrs) {
      if (isObject(offset)) {
        attrs = offset
        offset = 0
      }

      offset = offset || 0

      attrs = computeStyle(attrs, {
        stroke: 'black',
        strokeWidth: 2,
        fill: 'white'
      })

      if (attrs.fill === 'none') {
        delete attrs.fillOpacity
      }

      const cx = width / 2
      const cy = height / 2

      const circle = svgCreate('circle')
      svgAttr(circle, {
        cx: cx,
        cy: cy,
        r: Math.round((width + height) / 4 - offset)
      })
      svgAttr(circle, attrs)

      svgAppend(parentGfx, circle)

      return circle
    }

    function drawRect(parentGfx, width, height, r, offset, attrs) {
      if (isObject(offset)) {
        attrs = offset
        offset = 0
      }

      offset = offset || 0

      attrs = computeStyle(attrs, {
        stroke: 'black',
        strokeWidth: 2,
        fill: 'white'
      })

      const rect = svgCreate('rect')
      svgAttr(rect, {
        x: offset,
        y: offset,
        width: width - offset * 2,
        height: height - offset * 2,
        rx: r,
        ry: r
      })
      svgAttr(rect, attrs)

      svgAppend(parentGfx, rect)

      return rect
    }

    function drawDiamond(parentGfx, width, height, attrs) {
      const x_2 = width / 2
      const y_2 = height / 2

      const points = [
        { x: x_2, y: 0 },
        { x: width, y: y_2 },
        { x: x_2, y: height },
        { x: 0, y: y_2 }
      ]

      const pointsString = points
        .map(function(point) {
          return point.x + ',' + point.y
        })
        .join(' ')

      attrs = computeStyle(attrs, {
        stroke: 'black',
        strokeWidth: 2,
        fill: 'white'
      })

      const polygon = svgCreate('polygon')
      svgAttr(polygon, {
        points: pointsString
      })
      svgAttr(polygon, attrs)

      svgAppend(parentGfx, polygon)

      return polygon
    }

    function drawLine(parentGfx, waypoints, attrs) {
      attrs = computeStyle(attrs, ['no-fill'], {
        stroke: 'black',
        strokeWidth: 2,
        fill: 'none'
      })
      const line = createLine(waypoints, attrs)
      svgAppend(parentGfx, line)
      return line
    }

    function drawPath(parentGfx, d, attrs) {
      attrs = computeStyle(attrs, ['no-fill'], {
        strokeWidth: 2,
        stroke: 'black'
      })

      const path = svgCreate('path')
      svgAttr(path, { d: d })
      svgAttr(path, attrs)

      svgAppend(parentGfx, path)

      return path
    }

    function drawMarker(type, parentGfx, path, attrs) {
      return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs))
    }

    function as(type) {
      return function(parentGfx, element) {
        return renderer(type)(parentGfx, element)
      }
    }

    function renderEventContent(element, parentGfx) {
      const event = getSemantic(element)
      const isThrowing = isThrowEvent(event)
      const colorOptions = {
        'bpmn:StartEvent': { color: defaultStartEventColor, opacity: defaultStartEventOpacity },
        'bpmn:EndEvent': { color: defaultEndEventColor, opacity: defaultEndEventOpacity },
        'bpmn:BoundaryEvent': { color: defaultTaskColor, opacity: defaultTaskOpacity },
        'bpmn:IntermediateThrowEvent': {
          color: defaultIntermediateThrowEventColor,
          opacity: defaultIntermediateThrowEventOpacity
        },
        'bpmn:IntermediateCatchEvent': {
          color: defaultIntermediateCatchEventColor,
          opacity: defaultIntermediateCatchEventOpacity
        }
      }
      const type = element.type

      if (event.eventDefinitions && event.eventDefinitions.length > 1) {
        if (event.parallelMultiple) {
          return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, {
            isThrowing,
            attrs: colorOptions[type]
          })
        } else {
          return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, {
            isThrowing,
            attrs: colorOptions[type]
          })
        }
      }

      if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) {
        return renderer('bpmn:MessageEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) {
        return renderer('bpmn:TimerEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) {
        return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element, {
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) {
        return renderer('bpmn:SignalEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) {
        return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) {
        return renderer('bpmn:LinkEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) {
        return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) {
        return renderer('bpmn:CancelEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) {
        return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) {
        return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, {
          isThrowing,
          attrs: colorOptions[type]
        })
      }

      return null
    }

    function renderLabel(parentGfx, label, options) {
      options = assign(
        {
          size: {
            width: 100
          }
        },
        options
      )

      const text = textRenderer.createText(label || '', options)

      svgClasses(text).add('djs-label')

      svgAppend(parentGfx, text)

      return text
    }

    function renderEmbeddedLabel(parentGfx, element, align) {
      const semantic = getSemantic(element)

      return renderLabel(parentGfx, semantic.name, {
        box: element,
        align: align,
        padding: 5,
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultTaskColor)
        }
      })
    }

    function renderExternalLabel(parentGfx, element) {
      const box = {
        width: 90,
        height: 30,
        x: element.width / 2 + element.x,
        y: element.height / 2 + element.y
      }

      return renderLabel(parentGfx, getLabel(element), {
        box: box,
        fitBox: true,
        style: assign({}, textRenderer.getExternalStyle(), {
          fill: getLabelColor(element, defaultLabelColor, defaultTaskColor)
        })
      })
    }

    function renderLaneLabel(parentGfx, text, element) {
      const textBox = renderLabel(parentGfx, text, {
        box: {
          height: 30,
          width: element.height
        },
        align: 'center-middle',
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultTaskColor)
        }
      })

      const top = -1 * element.height

      transform(textBox, 0, -top, 270)
    }

    function createPathFromConnection(connection) {
      const waypoints = connection.waypoints

      // 起始点
      let pathData = 'm ' + waypoints[0].x + ',' + waypoints[0].y

      // 原始折线
      if (!useCurve) {
        for (let i = 1; i < waypoints.length; i++) {
          pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' '
        }
        return pathData
      }

      // 曲线绘制部分
      // 不同 length 对应的 svg path关键字，两个点时使用直线，三个点用Q，其余数量使用三次贝塞尔曲线
      const curveCodeMap = {
        2: ' L ',
        3: ' Q ',
        0: ' C ' // 置空作用
      }
      // 点数总数
      const waypointCount = waypoints.length
      // 路径组装
      if (waypointCount === 2) {
        for (let i = 1; i < waypointCount; i++) {
          pathData += curveCodeMap[waypointCount] + waypoints[i].x + ',' + waypoints[i].y + ' '
        }
      } else {
        pathData += curveCodeMap[waypointCount] || 'C'
        for (let i = 1; i < waypoints.length; i++) {
          pathData += waypoints[i].x + ',' + waypoints[i].y + ' '
        }
      }

      // console.log('pathData', pathData)
      return pathData
    }

    function attachTaskMarkers(parentGfx, element, taskMarkers) {
      const obj = getSemantic(element)

      const subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1
      let position

      if (subprocess) {
        position = {
          seq: -21,
          parallel: -22,
          compensation: -42,
          loop: -18,
          adhoc: 10
        }
      } else {
        position = {
          seq: -3,
          parallel: -6,
          compensation: -27,
          loop: 0,
          adhoc: 10
        }
      }

      forEach(taskMarkers, function(marker) {
        renderer(marker)(parentGfx, element, position)
      })

      if (obj.isForCompensation) {
        renderer('CompensationMarker')(parentGfx, element, position)
      }

      if (obj.$type === 'bpmn:AdHocSubProcess') {
        renderer('AdhocMarker')(parentGfx, element, position)
      }

      const loopCharacteristics = obj.loopCharacteristics
      const isSequential = loopCharacteristics && loopCharacteristics.isSequential

      if (loopCharacteristics) {
        if (isSequential === undefined) {
          renderer('LoopMarker')(parentGfx, element, position)
        }

        if (isSequential === false) {
          renderer('ParallelMarker')(parentGfx, element, position)
        }

        if (isSequential === true) {
          renderer('SequentialMarker')(parentGfx, element, position)
        }
      }
    }

    function renderDataItemCollection(parentGfx, element) {
      const yPosition = (element.height - 18) / element.height

      const pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
        xScaleFactor: 1,
        yScaleFactor: 1,
        containerWidth: element.width,
        containerHeight: element.height,
        position: {
          mx: 0.33,
          my: yPosition
        }
      })

      /* collection path */ drawPath(parentGfx, pathData, {
        strokeWidth: 2
      })
    }

    this._drawPath = function drawPath(parentGfx, d, attrs) {
      attrs = computeStyle(attrs, ['no-fill'], {
        strokeWidth: 2,
        stroke: 'black'
      })
      const path = svgCreate('path')
      svgAttr(path, { d: d })
      svgAttr(path, attrs)
      svgAppend(parentGfx, path)
      return path
    }
    const renderer = (this._renderer = function(type) {
      return handlers[type]
    })
    const handlers = (this.handlers = {
      'bpmn:Event': function(parentGfx, element, attrs = {}) {
        if (!('fillOpacity' in attrs)) {
          attrs['fillOpacity'] = DEFAULT_FILL_OPACITY
        }
        return drawCircle(parentGfx, element.width, element.height, attrs)
      },
      'bpmn:StartEvent': function(parentGfx, element) {
        let attrs = {
          fill: getFillColor(element, defaultStartEventColor),
          fillOpacity: defaultStartEventOpacity,
          stroke: getStrokeColor(element, defaultStartEventColor)
        }
        const semantic = getSemantic(element)
        if (!semantic.isInterrupting) {
          attrs = {
            strokeDasharray: '6',
            strokeLinecap: 'round',
            fill: getFillColor(element, defaultStartEventColor),
            stroke: getStrokeColor(element, defaultStartEventColor),
            fillOpacity: defaultStartEventOpacity
          }
        }
        const circle = renderer('bpmn:Event')(parentGfx, element, attrs)
        renderEventContent(element, parentGfx)
        return circle
      },
      'bpmn:MessageEventDefinition': function(parentGfx, element, options) {
        const pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.235,
            my: 0.315
          }
        })
        const fill = options.isThrowing ? getFillColor(element, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(element, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:TimerEventDefinition': function(parentGfx, element, options) {
        const fill = options.isThrowing ? getFillColor(element, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(element, options?.attrs?.color)
        const circle = drawCircle(parentGfx, element.width, element.height, element.height * 0.2, {
          strokeWidth: 2,
          fill,
          stroke
        })
        const pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
          xScaleFactor: 0.75,
          yScaleFactor: 0.75,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.5,
            my: 0.5
          }
        })
        drawPath(parentGfx, pathData, {
          strokeWidth: 2,
          strokeLinecap: 'square',
          stroke
        })
        for (let i = 0; i < 12; i++) {
          const linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
            xScaleFactor: 0.75,
            yScaleFactor: 0.75,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.5,
              my: 0.5
            }
          })

          const width = element.width / 2
          const height = element.height / 2

          drawPath(parentGfx, linePathData, {
            strokeWidth: 1,
            strokeLinecap: 'square',
            transform: 'rotate(' + i * 30 + ',' + height + ',' + width + ')',
            stroke
          })
        }
        return circle
      },
      'bpmn:EscalationEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:ConditionalEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.222
          }
        })
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          stroke
        })
      },
      'bpmn:LinkEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_LINK', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.57,
            my: 0.263
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:ErrorEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_ERROR', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.2,
            my: 0.722
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:CancelEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
          xScaleFactor: 1.0,
          yScaleFactor: 1.0,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.638,
            my: -0.055
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        const path = drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })

        rotate(path, 45)

        return path
      },
      'bpmn:CompensateEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.22,
            my: 0.5
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:SignalEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:MultipleEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.222,
            my: 0.36
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event, options) {
        const pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
          xScaleFactor: 1.2,
          yScaleFactor: 1.2,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.458,
            my: 0.194
          }
        })
        const fill = options.isThrowing ? getFillColor(event, options?.attrs?.color) : defaultFillColor
        const stroke = options.isThrowing ? defaultFillColor : getStrokeColor(event, options?.attrs?.color)
        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke
        })
      },
      'bpmn:EndEvent': function(parentGfx, element) {
        const circle = renderer('bpmn:Event')(parentGfx, element, {
          strokeWidth: 4,
          fill: getFillColor(element, defaultEndEventColor),
          fillOpacity: defaultEndEventOpacity,
          stroke: getStrokeColor(element, defaultEndEventColor)
        })
        renderEventContent(element, parentGfx)
        return circle
      },
      'bpmn:TerminateEventDefinition': function(parentGfx, element) {
        return drawCircle(parentGfx, element.width, element.height, 8, {
          strokeWidth: 4,
          fill: getStrokeColor(element, defaultEndEventColor),
          stroke: getStrokeColor(element, defaultEndEventColor)
        })
      },
      'bpmn:IntermediateEvent': function(parentGfx, element) {
        const outer = renderer('bpmn:Event')(parentGfx, element, {
          strokeWidth: 1,
          stroke: getStrokeColor(element, defaultIntermediateEventColor)
        })
        drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
          strokeWidth: 1,
          stroke: getStrokeColor(element, defaultIntermediateEventColor)
        })
        renderEventContent(element, parentGfx)
        return outer
      },
      'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
      'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),
      'bpmn:Activity': function(parentGfx, element, attrs) {
        attrs = attrs || {}

        if (!('fillOpacity' in attrs)) {
          attrs.fillOpacity = DEFAULT_FILL_OPACITY
        }

        return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs)
      },
      'bpmn:Task': function(parentGfx, element) {
        const attrs = {
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: defaultTaskOpacity,
          stroke: getStrokeColor(element, defaultTaskColor)
        }

        const rect = renderer('bpmn:Activity')(parentGfx, element, attrs)

        renderEmbeddedLabel(parentGfx, element, 'center-middle')
        attachTaskMarkers(parentGfx, element)

        return rect
      },
      'bpmn:ServiceTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 12,
            y: 18
          }
        })

        /* service bg */ drawPath(parentGfx, pathDataBG, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', {
          abspos: {
            x: 17.2,
            y: 18
          }
        })

        /* service fill */ drawPath(parentGfx, fillPathData, {
          strokeWidth: 0,
          fill: getFillColor(element, defaultFillColor)
        })

        const pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 17,
            y: 22
          }
        })

        /* service */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:UserTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const x = 15
        const y = 12

        const pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
          abspos: {
            x: x,
            y: y
          }
        })

        /* user path */ drawPath(parentGfx, pathData, {
          strokeWidth: 0.5,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
          abspos: {
            x: x,
            y: y
          }
        })

        /* user2 path */ drawPath(parentGfx, pathData2, {
          strokeWidth: 0.5,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
          abspos: {
            x: x,
            y: y
          }
        })

        /* user3 path */ drawPath(parentGfx, pathData3, {
          strokeWidth: 0.5,
          fill: getStrokeColor(element, defaultTaskColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:ManualTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
          abspos: {
            x: 17,
            y: 15
          }
        })

        /* manual path */ drawPath(parentGfx, pathData, {
          strokeWidth: 0.5, // 0.25,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:SendTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: 21,
          containerHeight: 14,
          position: {
            mx: 0.285,
            my: 0.357
          }
        })

        /* send path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor(element, defaultTaskColor),
          stroke: getFillColor(element, defaultFillColor)
        })

        return task
      },
      'bpmn:ReceiveTask': function(parentGfx, element) {
        const semantic = getSemantic(element)

        const task = renderer('bpmn:Task')(parentGfx, element)
        let pathData

        if (semantic.instantiate) {
          drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 })

          pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
            abspos: {
              x: 7.77,
              y: 9.52
            }
          })
        } else {
          pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
            xScaleFactor: 0.9,
            yScaleFactor: 0.9,
            containerWidth: 21,
            containerHeight: 14,
            position: {
              mx: 0.3,
              my: 0.4
            }
          })
        }

        /* receive path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:ScriptTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
          abspos: {
            x: 15,
            y: 20
          }
        })

        /* script path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:BusinessRuleTask': function(parentGfx, element) {
        const task = renderer('bpmn:Task')(parentGfx, element)

        const headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
          abspos: {
            x: 8,
            y: 8
          }
        })

        const businessHeaderPath = drawPath(parentGfx, headerPathData)
        svgAttr(businessHeaderPath, {
          strokeWidth: 1,
          fill: getFillColor(element, '#aaaaaa'),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
          abspos: {
            x: 8,
            y: 8
          }
        })

        const businessPath = drawPath(parentGfx, headerData)
        svgAttr(businessPath, {
          strokeWidth: 1,
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        return task
      },
      'bpmn:SubProcess': function(parentGfx, element, attrs) {
        attrs = assign(
          {
            fill: getFillColor(element, defaultFillColor),
            stroke: getStrokeColor(element, defaultTaskColor)
          },
          attrs
        )

        const rect = renderer('bpmn:Activity')(parentGfx, element, attrs)

        const expanded = isExpanded(element)

        if (isEventSubProcess(element)) {
          svgAttr(rect, {
            strokeDasharray: '1,2'
          })
        }

        renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle')

        if (expanded) {
          attachTaskMarkers(parentGfx, element)
        } else {
          attachTaskMarkers(parentGfx, element, ['SubProcessMarker'])
        }

        return rect
      },
      'bpmn:AdHocSubProcess': function(parentGfx, element) {
        return renderer('bpmn:SubProcess')(parentGfx, element)
      },
      'bpmn:Transaction': function(parentGfx, element) {
        const outer = renderer('bpmn:SubProcess')(parentGfx, element)

        const innerAttrs = styles.style(['no-fill', 'no-events'], {
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        /* inner path */ drawRect(
          parentGfx,
          element.width,
          element.height,
          TASK_BORDER_RADIUS - 2,
          INNER_OUTER_DIST,
          innerAttrs
        )

        return outer
      },
      'bpmn:CallActivity': function(parentGfx, element) {
        return renderer('bpmn:SubProcess')(parentGfx, element, {
          strokeWidth: 5
        })
      },
      'bpmn:Participant': function(parentGfx, element) {
        const attrs = {
          fillOpacity: DEFAULT_FILL_OPACITY,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        }

        const lane = renderer('bpmn:Lane')(parentGfx, element, attrs)

        const expandedPool = isExpanded(element)

        if (expandedPool) {
          drawLine(
            parentGfx,
            [
              { x: 30, y: 0 },
              { x: 30, y: element.height }
            ],
            {
              stroke: getStrokeColor(element, defaultTaskColor)
            }
          )
          const text = getSemantic(element).name
          renderLaneLabel(parentGfx, text, element)
        } else {
          // Collapsed pool draw text inline
          const text2 = getSemantic(element).name
          renderLabel(parentGfx, text2, {
            box: element,
            align: 'center-middle',
            style: {
              fill: getLabelColor(element, defaultLabelColor, defaultTaskColor)
            }
          })
        }

        const participantMultiplicity = !!getSemantic(element).participantMultiplicity

        if (participantMultiplicity) {
          renderer('ParticipantMultiplicityMarker')(parentGfx, element)
        }

        return lane
      },
      'bpmn:Lane': function(parentGfx, element, attrs) {
        const rect = drawRect(
          parentGfx,
          element.width,
          element.height,
          0,
          assign(
            {
              fill: getFillColor(element, defaultFillColor),
              fillOpacity: HIGH_FILL_OPACITY,
              stroke: getStrokeColor(element, defaultTaskColor)
            },
            attrs
          )
        )

        const semantic = getSemantic(element)

        if (semantic.$type === 'bpmn:Lane') {
          const text = semantic.name
          renderLaneLabel(parentGfx, text, element)
        }

        return rect
      },
      'bpmn:InclusiveGateway': function(parentGfx, element) {
        const diamond = renderer('bpmn:Gateway')(parentGfx, element)

        /* circle path */
        drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
          strokeWidth: 2.5,
          fill: getStrokeColor(element, defaultGatewayColor),
          stroke: getStrokeColor(element, defaultGatewayColor)
        })

        return diamond
      },
      'bpmn:ExclusiveGateway': function(parentGfx, element) {
        const diamond = renderer('bpmn:Gateway')(parentGfx, element)

        const pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
          xScaleFactor: 0.4,
          yScaleFactor: 0.4,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.32,
            my: 0.3
          }
        })

        if (getDi(element).isMarkerVisible) {
          drawPath(parentGfx, pathData, {
            strokeWidth: 1,
            fill: getStrokeColor(element, defaultGatewayColor),
            stroke: getStrokeColor(element, defaultGatewayColor)
          })
        }

        return diamond
      },
      'bpmn:ComplexGateway': function(parentGfx, element) {
        const diamond = renderer('bpmn:Gateway')(parentGfx, element)

        const pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
          xScaleFactor: 0.5,
          yScaleFactor: 0.5,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.46,
            my: 0.26
          }
        })

        /* complex path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor(element, defaultGatewayColor),
          stroke: getStrokeColor(element, defaultGatewayColor)
        })

        return diamond
      },
      'bpmn:ParallelGateway': function(parentGfx, element) {
        const diamond = renderer('bpmn:Gateway')(parentGfx, element)

        const pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
          xScaleFactor: 0.6,
          yScaleFactor: 0.6,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.46,
            my: 0.2
          }
        })

        /* parallel path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor(element, defaultGatewayColor),
          stroke: getStrokeColor(element, defaultGatewayColor)
        })

        return diamond
      },
      'bpmn:EventBasedGateway': function(parentGfx, element) {
        const semantic = getSemantic(element)

        const diamond = renderer('bpmn:Gateway')(parentGfx, element)

        /* outer circle path */
        drawCircle(parentGfx, element.width, element.height, element.height * 0.2, {
          strokeWidth: 1,
          fill: 'none',
          stroke: getStrokeColor(element, defaultGatewayColor)
        })

        const type = semantic.eventGatewayType
        const instantiate = !!semantic.instantiate

        function drawEvent() {
          const pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
            xScaleFactor: 0.18,
            yScaleFactor: 0.18,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.36,
              my: 0.44
            }
          })

          const attrs = {
            strokeWidth: 2,
            fill: getFillColor(element, 'none'),
            stroke: getStrokeColor(element, defaultGatewayColor)
          }
          drawPath(parentGfx, pathData, attrs)
        }

        if (type === 'Parallel') {
          const pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
            xScaleFactor: 0.4,
            yScaleFactor: 0.4,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.474,
              my: 0.296
            }
          })

          const parallelPath = drawPath(parentGfx, pathData)
          svgAttr(parallelPath, {
            strokeWidth: 1,
            fill: 'none'
          })
        } else if (type === 'Exclusive') {
          if (!instantiate) {
            const innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26)
            svgAttr(innerCircle, {
              strokeWidth: 1,
              fill: 'none',
              stroke: getStrokeColor(element, defaultGatewayColor)
            })
          }

          drawEvent()
        }

        return diamond
      },
      'bpmn:Gateway': function(parentGfx, element) {
        const attrs = {
          fill: getFillColor(element, defaultGatewayColor),
          fillOpacity: defaultGatewayOpacity,
          stroke: getStrokeColor(element, defaultGatewayColor)
        }

        return drawDiamond(parentGfx, element.width, element.height, attrs)
      },
      'bpmn:SequenceFlow': function(parentGfx, element) {
        const pathData = createPathFromConnection(element)
        const fill = getFillColor(element, defaultFillColor)
        const stroke = getStrokeColor(element, defaultSequenceColor)
        const attrs = {
          strokeLinejoin: 'round',
          markerEnd: marker('sequenceflow-end', fill, stroke),
          stroke: getStrokeColor(element, defaultSequenceColor)
        }
        const path = drawPath(parentGfx, pathData, attrs)
        const sequenceflow = getSemantic(element)
        let source
        if (element.source) {
          source = element.source.businessObject
          if (sequenceflow.conditionExpression && source.$instanceOf('bpmn:Activity')) {
            svgAttr(path, {
              markerStart: marker('conditional-flow-marker', fill, stroke)
            })
          }
          if (
            source.default &&
            (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) &&
            source.default === sequenceflow
          ) {
            svgAttr(path, {
              markerStart: marker('conditional-default-flow-marker', fill, stroke)
            })
          }
        }

        return path
      },
      'bpmn:Association': function(parentGfx, element, attrs) {
        const semantic = getSemantic(element)

        const fill = getFillColor(element, defaultFillColor)
        const stroke = getStrokeColor(element, defaultTaskColor)

        attrs = assign(
          {
            strokeDasharray: '0.5, 5',
            strokeLinecap: 'round',
            strokeLinejoin: 'round',
            stroke: getStrokeColor(element, defaultTaskColor)
          },
          attrs || {}
        )

        if (semantic.associationDirection === 'One' || semantic.associationDirection === 'Both') {
          attrs.markerEnd = marker('association-end', fill, stroke)
        }

        if (semantic.associationDirection === 'Both') {
          attrs.markerStart = marker('association-start', fill, stroke)
        }

        return drawLine(parentGfx, element.waypoints, attrs)
      },
      'bpmn:DataInputAssociation': function(parentGfx, element) {
        const fill = getFillColor(element, defaultFillColor)
        const stroke = getStrokeColor(element, defaultTaskColor)

        return renderer('bpmn:Association')(parentGfx, element, {
          markerEnd: marker('association-end', fill, stroke)
        })
      },
      'bpmn:DataOutputAssociation': function(parentGfx, element) {
        const fill = getFillColor(element, defaultFillColor)
        const stroke = getStrokeColor(element, defaultTaskColor)

        return renderer('bpmn:Association')(parentGfx, element, {
          markerEnd: marker('association-end', fill, stroke)
        })
      },
      'bpmn:MessageFlow': function(parentGfx, element) {
        const semantic = getSemantic(element)
        const di = getDi(element)

        const fill = getFillColor(element, defaultFillColor)
        const stroke = getStrokeColor(element, defaultTaskColor)

        const pathData = createPathFromConnection(element)

        const attrs = {
          markerEnd: marker('messageflow-end', fill, stroke),
          markerStart: marker('messageflow-start', fill, stroke),
          strokeDasharray: '10, 12',
          strokeLinecap: 'round',
          strokeLinejoin: 'round',
          strokeWidth: '1.5px',
          stroke: getStrokeColor(element, defaultTaskColor)
        }

        const path = drawPath(parentGfx, pathData, attrs)

        if (semantic.messageRef) {
          const midPoint = path.getPointAtLength(path.getTotalLength() / 2)

          const markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
            abspos: {
              x: midPoint.x,
              y: midPoint.y
            }
          })

          const messageAttrs = { strokeWidth: 1 }

          if (di.messageVisibleKind === 'initiating') {
            messageAttrs.fill = 'white'
            messageAttrs.stroke = 'black'
          } else {
            messageAttrs.fill = '#888'
            messageAttrs.stroke = 'white'
          }

          const message = drawPath(parentGfx, markerPathData, messageAttrs)

          const labelText = semantic.messageRef.name
          const label = renderLabel(parentGfx, labelText, {
            align: 'center-top',
            fitBox: true,
            style: {
              fill: getStrokeColor(element, defaultLabelColor, defaultTaskColor)
            }
          })

          const messageBounds = message.getBBox()
          const labelBounds = label.getBBox()

          const translateX = midPoint.x - labelBounds.width / 2
          const translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE

          transform(label, translateX, translateY, 0)
        }

        return path
      },
      'bpmn:DataObject': function(parentGfx, element) {
        const pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.474,
            my: 0.296
          }
        })

        const elementObject = drawPath(parentGfx, pathData, {
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: DEFAULT_FILL_OPACITY,
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const semantic = getSemantic(element)

        if (isCollection(semantic)) {
          renderDataItemCollection(parentGfx, element)
        }

        return elementObject
      },
      'bpmn:DataObjectReference': as('bpmn:DataObject'),
      'bpmn:DataInput': function(parentGfx, element) {
        const arrowPathData = pathMap.getRawPath('DATA_ARROW')

        // page
        const elementObject = renderer('bpmn:DataObject')(parentGfx, element)

        /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 })

        return elementObject
      },
      'bpmn:DataOutput': function(parentGfx, element) {
        const arrowPathData = pathMap.getRawPath('DATA_ARROW')

        // page
        const elementObject = renderer('bpmn:DataObject')(parentGfx, element)

        /* output arrow path */ drawPath(parentGfx, arrowPathData, {
          strokeWidth: 1,
          fill: 'black'
        })

        return elementObject
      },
      'bpmn:DataStoreReference': function(parentGfx, element) {
        const DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0,
            my: 0.133
          }
        })

        return drawPath(parentGfx, DATA_STORE_PATH, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: DEFAULT_FILL_OPACITY,
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      'bpmn:BoundaryEvent': function(parentGfx, element) {
        const semantic = getSemantic(element)
        const cancel = semantic.cancelActivity

        const attrs = {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        }

        if (!cancel) {
          attrs.strokeDasharray = '6'
          attrs.strokeLinecap = 'round'
        }

        // apply fillOpacity
        const outerAttrs = assign({}, attrs, {
          fillOpacity: 1
        })

        // apply no-fill
        const innerAttrs = assign({}, attrs, {
          fill: 'none'
        })

        const outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs)

        /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs)

        renderEventContent(element, parentGfx)

        return outer
      },
      'bpmn:Group': function(parentGfx, element) {
        return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
          stroke: getStrokeColor(element, defaultTaskColor),
          strokeWidth: 1,
          strokeDasharray: '8,3,1,3',
          fill: 'none',
          pointerEvents: 'none'
        })
      },
      label: function(parentGfx, element) {
        return renderExternalLabel(parentGfx, element)
      },
      'bpmn:TextAnnotation': function(parentGfx, element) {
        const style = {
          fill: 'none',
          stroke: 'none'
        }

        const textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style)

        const textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.0,
            my: 0.0
          }
        })

        drawPath(parentGfx, textPathData, {
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        const text = getSemantic(element).text || ''
        renderLabel(parentGfx, text, {
          box: element,
          align: 'left-top',
          padding: 5,
          style: {
            fill: getLabelColor(element, defaultLabelColor, defaultTaskColor)
          }
        })

        return textElement
      },
      ParticipantMultiplicityMarker: function(parentGfx, element) {
        const markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: element.width / 2 / element.width,
            my: (element.height - 15) / element.height
          }
        })

        return drawMarker('participant-multiplicity', parentGfx, markerPath, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      SubProcessMarker: function(parentGfx, element) {
        const markerRect = drawRect(parentGfx, 14, 14, 0, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })

        // Process marker is placed in the middle of the box
        // therefore fixed values can be used here
        translate(markerRect, element.width / 2 - 7.5, element.height - 20)

        const markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
          xScaleFactor: 1.5,
          yScaleFactor: 1.5,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 - 7.5) / element.width,
            my: (element.height - 20) / element.height
          }
        })

        return drawMarker('sub-process', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      ParallelMarker: function(parentGfx, element, position) {
        const markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 + position.parallel) / element.width,
            my: (element.height - 20) / element.height
          }
        })

        return drawMarker('parallel', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      SequentialMarker: function(parentGfx, element, position) {
        const markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 + position.seq) / element.width,
            my: (element.height - 19) / element.height
          }
        })

        return drawMarker('sequential', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      CompensationMarker: function(parentGfx, element, position) {
        const markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 + position.compensation) / element.width,
            my: (element.height - 13) / element.height
          }
        })

        return drawMarker('compensation', parentGfx, markerMath, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },
      LoopMarker: function(parentGfx, element, position) {
        const markerPath = pathMap.getScaledPath('MARKER_LOOP', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 + position.loop) / element.width,
            my: (element.height - 7) / element.height
          }
        })

        return drawMarker('loop', parentGfx, markerPath, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor(element, defaultTaskColor),
          strokeLinecap: 'round',
          strokeMiterlimit: 0.5
        })
      },
      AdhocMarker: function(parentGfx, element, position) {
        const markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 + position.adhoc) / element.width,
            my: (element.height - 15) / element.height
          }
        })

        return drawMarker('adhoc', parentGfx, markerPath, {
          strokeWidth: 1,
          fill: getStrokeColor(element, defaultTaskColor),
          stroke: getStrokeColor(element, defaultTaskColor)
        })
      },

      // 自定义节点的绘制
      'miyue:SqlTask': function(parentGfx, element, attr) {
        // 渲染外层边框
        const attrs = {
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: defaultTaskOpacity,
          stroke: getStrokeColor(element, defaultTaskColor)
        }
        renderer('bpmn:Activity')(parentGfx, element, attrs)
        // 自定义节点
        const customIcon = svgCreate('image')
        svgAttr(customIcon, {
          ...(attr || {}),
          width: element.width,
          height: element.height,
          href: mysqlIcon
        })
        svgAppend(parentGfx, customIcon)
        return customIcon
      }
    })
  }

  canRender(element) {
    return is(element, 'bpmn:BaseElement')
  }
  drawConnection(parentGfx, connection) {
    const type = connection.type
    const h = this._renderer(type)
    return h(parentGfx, connection)
  }
  drawShape(parentGfx, element) {
    const type = element.type
    const h = this._renderer(type)
    return h(parentGfx, element)
  }
  getConnectionPath(connection) {
    return undefined
  }
  getShapePath(shape) {
    if (is(shape, 'bpmn:Event')) {
      return getCirclePath(shape)
    }

    if (is(shape, 'bpmn:Activity')) {
      return getRoundRectPath(shape, TASK_BORDER_RADIUS)
    }

    if (is(shape, 'bpmn:Gateway')) {
      return getDiamondPath(shape)
    }

    return getRectPath(shape)
  }

  setElementColors(element, colors) {
    const svg = this._elementRegistry?.getGraphics(element)
    if (!svg) return
    const paths = svgSelect(svg, '.djs-visual')
    // @ts-ignore
    paths && paths.childNodes.forEach((el) => svgAttr(el, colors))
  }
}

RewriteRenderer.$inject = [
  'config.bpmnRenderer',
  'eventBus',
  'styles',
  'pathMap',
  'canvas',
  'textRenderer',
  'elementRegistry',
  'interactionEvents'
]

export default RewriteRenderer
