import type { Point } from './shapeCalculate';
import { computeTwoBendPoints, getTransformedEdgeEndPoint, getTransformedEdgeStartPoint, getTransformedNodePointForExport, shapeMap } from './shapeCalculate';

// 顶部统一定义
const AMPERSAND_REGEX = /&/g;
const LT_REGEX = /</g;
const GT_REGEX = />/g;
const QUOTE_REGEX = /"/g;
const SINGLE_QUOTE_REGEX = /'/g;

// 进行某些特殊字符转换
function esc(s: any): string {
  if (s === undefined || s === null) {
    return '';
  }
  return String(s)
    .replace(AMPERSAND_REGEX, '&amp;')
    .replace(LT_REGEX, '&lt;')
    .replace(GT_REGEX, '&gt;')
    .replace(QUOTE_REGEX, '&quot;')
    .replace(SINGLE_QUOTE_REGEX, '&apos;');
}

// 获取文档元素的xml
function getDocumentationXml(documentation: string): string {
  return `<bpmn2:documentation>${esc(documentation)}</bpmn2:documentation>`;
}

// 获取扩展属性的xml
function getExtensionPropertiesXml(properties: any[]): string {
  let xml = '<flowable:properties>';
  for (const p of properties) {
    xml += `<flowable:property name="${esc(p.name)}" value="${esc(p.value)}" />`;
  }
  xml += '</flowable:properties>';
  return xml;
}

// 获取执行监听器字段的xml
function getExecutionListenerFieldsXml(fields: any[]): string {
  let xml = '';
  for (const f of fields) {
    xml += `<flowable:field name="${esc(f.name)}">`;
    const type = f.type === '字符串' ? 'string' : 'expression';
    xml += `<flowable:${type}>${esc(f.value)}</flowable:${type}>`;
    xml += `</flowable:field>`;
  }
  return xml;
}
// 获取执行监听器的xml
function getExecutionListenersXml(listeners: any[]): string {
  let xml = '';
  for (const listen of listeners) {
    xml += `<flowable:executionListener event="${esc(listen.event)}" `;
    if (listen.lType === 'scriptFormat') {
      xml += '>';
      if (listen.scriptType === 'inline') {
        xml += `<flowable:script scriptFormat="${listen.scriptFormat}">${esc(listen.scriptContent)}</flowable:script>`;
      }
      else if (listen.scriptType === 'external') {
        xml += `<flowable:script scriptFormat="${listen.scriptFormat}" resource="${esc(listen.resource)}" />`;
      }
      if (listen.field && listen.field.length > 0) {
        xml += getExecutionListenerFieldsXml(listen.field);
      }
      xml += `</flowable:executionListener>`;
    }
    else {
      xml += `${listen.lType}="${esc(listen.value)}" `;
      if (listen.field && listen.field.length > 0) {
        xml += '>';
        xml += getExecutionListenerFieldsXml(listen.field);
        xml += `</flowable:executionListener>`;
      }
      else {
        xml += '/>';
      }
    }
  }

  return xml;
}

// 获取任务监听器的xml
function getTaskListenersXml(listeners: any[]): string {
  let xml = '';
  for (const listen of listeners) {
    xml += `<flowable:taskListener event="${esc(listen.event)}" `;
    if (listen.id && listen.id.trim() !== '') {
      xml += `id="${esc(listen.id)}" `;
    }
    if (listen.lType === 'scriptFormat') {
      xml += '>';
      if (listen.scriptType === 'inline') {
        xml += `<flowable:script scriptFormat="${listen.scriptFormat}">${esc(listen.scriptContent)}"</flowable:script>`;
      }
      else if (listen.scriptType === 'external') {
        xml += `<flowable:script scriptFormat="${listen.scriptFormat}" resource="${esc(listen.resource)}" />`;
      }
      if (listen.field && listen.field.length > 0) {
        xml += getExecutionListenerFieldsXml(listen.field);
      }
      xml += `</flowable:taskListener>`;
    }
    else {
      xml += `${listen.lType}="${esc(listen.value)}" `;
      if (listen.field && listen.field.length > 0) {
        xml += '>';
        xml += getExecutionListenerFieldsXml(listen.field);
        xml += `</flowable:taskListener>`;
      }
      else {
        xml += '/>';
      }
    }
  }

  return xml;
}

// 获取开始事件的xml
function parseStartEvent(startEvent: any, edgeBySource: Record<string, any[]>): any {
  let xml = '';
  const props: any = (startEvent && startEvent.properties) || {};
  const id: string = props.id || (startEvent && startEvent.id) || '';
  const title: string = props.title || '';
  const doc: string = props.documentation || '';

  xml += `<bpmn2:startEvent id="${esc(id)}" name="${esc(title)}"`;
  if (props.formKey) {
    xml += ` flowable:formKey="${esc(props.formKey)}"`;
  }
  xml += '>';
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  xml += '<bpmn2:extensionElements>';
  xml += '<flowable:formData />';
  if (Array.isArray(props.properties) && props.properties.length) {
    xml += getExtensionPropertiesXml(props.properties);
  }
  if (Array.isArray(props.executionListener) && props.executionListener.length) {
    xml += getExecutionListenersXml(props.executionListener);
  }
  xml += '</bpmn2:extensionElements>';
  // outgoing flows
  const outs: any[] = edgeBySource[id] || [];
  for (const oe of outs) {
    xml += `      <bpmn2:outgoing>${esc(oe && oe.id)}</bpmn2:outgoing>`;
  }
  xml += '</bpmn2:startEvent>';

  return xml;
}

// 获取结束事件的xml
function parseEndEvent(endEvent: any, edgeByTarget: Record<string, any[]>): any {
  let xml = '';
  const props: any = (endEvent && endEvent.properties) || {};
  const id: string = props.id || (endEvent && endEvent.id) || '';
  const title: string = props.title || '';
  const doc: string = props.documentation || '';

  xml += `<bpmn2:endEvent id="${esc(id)}" name="${esc(title)}">`;
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  if ((Array.isArray(props.properties) && props.properties.length) || (Array.isArray(props.executionListener) && props.executionListener.length)) {
    xml += '<bpmn2:extensionElements>';
    if (Array.isArray(props.properties) && props.properties.length) {
      xml += getExtensionPropertiesXml(props.properties);
    }
    if (Array.isArray(props.executionListener) && props.executionListener.length) {
      xml += getExecutionListenersXml(props.executionListener);
    }
    xml += '</bpmn2:extensionElements>';
  }
  // incoming flows
  const ins: any[] = edgeByTarget[id] || [];
  for (const ie of ins) {
    xml += `<bpmn2:incoming>${esc(ie && ie.id)}</bpmn2:incoming>`;
  }
  xml += '</bpmn2:endEvent>';

  return xml;
}

// 获取网关的xml(互斥网关、并行网关)
function parseGatewayNode(node: any, edgeBySource: Record<string, any[]>, edgeByTarget: Record<string, any[]>): any {
  let xml = '';
  const props: any = (node && node.properties) || {};
  const type: string = props.type || '';
  const id: string = props.id || (node && node.id) || '';
  const title: string = props.title || '';
  const defaultFlow: string = props.default || '';
  const doc: string = props.documentation || '';

  const nodeType = type === 'exclusive-gateway-node' ? 'exclusiveGateway' : 'parallelGateway';
  xml += `<bpmn2:${nodeType} id="${esc(id)}" name="${esc(title)}"`;
  // 默认流转路径
  if (defaultFlow) {
    xml += ` default="${esc(defaultFlow)}"`;
  }
  xml += '>';
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  if ((Array.isArray(props.properties) && props.properties.length) || (Array.isArray(props.executionListener) && props.executionListener.length)) {
    xml += '<bpmn2:extensionElements>';
    if (Array.isArray(props.properties) && props.properties.length) {
      xml += getExtensionPropertiesXml(props.properties);
    }
    if (Array.isArray(props.executionListener) && props.executionListener.length) {
      xml += getExecutionListenersXml(props.executionListener);
    }
    xml += '</bpmn2:extensionElements>';
  }
  // incoming flows
  const ins: any[] = edgeByTarget[id] || [];
  for (const ie of ins) {
    xml += `<bpmn2:incoming>${esc(ie && ie.id)}</bpmn2:incoming>`;
  }

  // outgoing flows
  const outs: any[] = edgeBySource[id] || [];
  for (const oe of outs) {
    xml += `<bpmn2:outgoing>${esc(oe && oe.id)}</bpmn2:outgoing>`;
  }

  xml += `</bpmn2:${nodeType}>`;

  return xml;
}

// 获取任务节点的xml
function parseNormalTaskNode(node: any, edgeBySource: Record<string, any[]>, edgeByTarget: Record<string, any[]>): any {
  let xml = '';
  const props: any = (node && node.properties) || {};
  // const type: string = props.type || '';
  const id: string = props.id || (node && node.id) || '';
  const title: string = props.title || '';
  const defaultFlow: string = props.default || '';
  const doc: string = props.documentation || '';

  xml += `<bpmn2:task id="${esc(id)}" name="${esc(title)}"`;
  // 默认流转路径
  if (defaultFlow) {
    xml += ` default="${esc(defaultFlow)}"`;
  }
  xml += '>';
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  xml += '<bpmn2:extensionElements>';
  xml += `<flowable:approveMethod>${props.approveMethod}</flowable:approveMethod>`;
  if (props.customConfiguration) {
    xml += `<flowable:assignStartUserHandlerType>${props.customConfiguration.assignStartUserHandlerType}</flowable:assignStartUserHandlerType>`;
    xml += `<flowable:rejectHandlerType>${props.customConfiguration.rejectHandlerType}</flowable:rejectHandlerType>`;
    xml += `<flowable:approveType>${props.customConfiguration.approveType}</flowable:approveType>`;
    xml += `<flowable:rejectReturnTaskId>${esc(props.customConfiguration.rejectReturnTaskId)}</flowable:rejectReturnTaskId>`;
    xml += `<flowable:assignEmptyHandlerType>${props.customConfiguration.assignEmptyHandlerType}</flowable:assignEmptyHandlerType>`;
    if (Array.isArray(props.customConfiguration.assignEmptyUserIds)) {
      xml += `<flowable:assignEmptyUserIds>${esc(props.customConfiguration.assignEmptyUserIds.join(','))}</flowable:assignEmptyUserIds>`;
    }
    if (Array.isArray(props.customConfiguration.buttonsSetting)) {
      for (const button of props.customConfiguration.buttonsSetting) {
        xml += `<flowable:buttonsSetting id="${esc(button.id)}" displayName="${esc(button.displayName)}" enable="${esc(button.enable)}" />`;
      }
    }
    if (Array.isArray(props.customConfiguration.fieldsPermission)) {
      for (const field of props.customConfiguration.fieldsPermission) {
        xml += `<flowable:fieldsPermission field="${esc(field.key)}" titlke="${field.name}" permission="${esc(field.permission)}" />`;
      }
    }
    xml += `<flowable:signEnable>${props.customConfiguration.signEnable}</flowable:signEnable>`;
    xml += `<flowable:reasonRequire>${props.customConfiguration.reasonRequire}</flowable:reasonRequire>`;
  }
  if (Array.isArray(props.properties) && props.properties.length) {
    xml += getExtensionPropertiesXml(props.properties);
  }
  if (Array.isArray(props.executionListener) && props.executionListener.length) {
    xml += getExecutionListenersXml(props.executionListener);
  }
  xml += '</bpmn2:extensionElements>';
  // incoming flows
  const ins: any[] = edgeByTarget[id] || [];
  for (const ie of ins) {
    xml += `<bpmn2:incoming>${esc(ie && ie.id)}</bpmn2:incoming>`;
  }

  // outgoing flows
  const outs: any[] = edgeBySource[id] || [];
  for (const oe of outs) {
    xml += `<bpmn2:outgoing>${esc(oe && oe.id)}</bpmn2:outgoing>`;
  }

  xml += `</bpmn2:task>`;

  return xml;
}

// 多人审批方式 不同选择 不同xml
function parseApproveMethodXml(approveMethod: number, approvalModePrecent: number): string {
  let xml = '';
  switch (approveMethod) {
    case 2:
      xml += `<bpmn2:multiInstanceLoopCharacteristics flowable:collection="\${coll_userList}">`;
      xml += `<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">\${ nrOfCompletedInstances/nrOfInstances &gt;= ${approvalModePrecent / 100} }</bpmn2:completionCondition>`;
      xml += `</bpmn2:multiInstanceLoopCharacteristics>`;
      break;
    case 3:
      xml += `<bpmn2:multiInstanceLoopCharacteristics flowable:collection="\${coll_userList}">`;
      xml += `<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">\${ nrOfCompletedInstances/nrOfInstances &gt; 0 }</bpmn2:completionCondition>`;
      xml += `</bpmn2:multiInstanceLoopCharacteristics>`;
      break;
    case 4:
      xml += `<bpmn2:multiInstanceLoopCharacteristics isSequential="true" flowable:collection="\${coll_userList}">`;
      xml += `<bpmn2:loopCardinality xsi:type="bpmn2:tFormalExpression">1</bpmn2:loopCardinality>`;
      xml += `<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">\${ nrOfCompletedInstances &gt;= nrOfInstances }</bpmn2:completionCondition>`;
      xml += `</bpmn2:multiInstanceLoopCharacteristics>`;
      break;
    default:
      break;
  }
  return xml;
}

// 获取用户任务的xml
function parseUserTaskNode(node: any, edgeBySource: Record<string, any[]>, edgeByTarget: Record<string, any[]>): any {
  let xml = '';
  const props: any = (node && node.properties) || {};
  const type: string = props.type || '';
  const id: string = props.id || (node && node.id) || '';
  const title: string = props.title || '';
  const defaultFlow: string = props.default || '';
  const skipExpression: string = props.userTask?.skipExpression || '';
  const candidateStrategy: string = props.userTask?.candidateStrategy || '';
  const doc: string = props.documentation || '';

  xml += `<bpmn2:userTask id="${esc(id)}" name="${esc(title)}"`;
  // 默认流转路径
  if (defaultFlow) {
    xml += ` default="${esc(defaultFlow)}"`;
  }
  if (props.formKey) {
    xml += ` flowable:formKey="${esc(props.formKey)}"`;
  }
  // 用户任务 跳过表达式
  if (skipExpression) {
    xml += ` flowable:skipExpression="${esc(skipExpression)}"`;
  }

  xml += '>';
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  xml += '<bpmn2:extensionElements>';
  xml += '<flowable:formData />';
  // 用户任务
  if (candidateStrategy) {
    xml += `<flowable:candidateStrategy>${esc(candidateStrategy)}</flowable:candidateStrategy>`;
    xml += `<flowable:candidateParam>${esc(props.userTask.candidateParam || '')}</flowable:candidateParam>`;
  }
  xml += `<flowable:approveMethod>${props.approveMethod}</flowable:approveMethod>`;
  if (Array.isArray(props.properties) && props.properties.length) {
    xml += getExtensionPropertiesXml(props.properties);
  }
  if (Array.isArray(props.executionListener) && props.executionListener.length) {
    xml += getExecutionListenersXml(props.executionListener);
  }
  if (Array.isArray(props.taskListener) && props.taskListener.length) {
    xml += getTaskListenersXml(props.taskListener);
  }
  if (props.customConfiguration) {
    xml += `<flowable:assignStartUserHandlerType>${props.customConfiguration.assignStartUserHandlerType}</flowable:assignStartUserHandlerType>`;
    xml += `<flowable:rejectHandlerType>${props.customConfiguration.rejectHandlerType}</flowable:rejectHandlerType>`;
    xml += `<flowable:approveType>${props.customConfiguration.approveType}</flowable:approveType>`;
    xml += `<flowable:rejectReturnTaskId>${esc(props.customConfiguration.rejectReturnTaskId)}</flowable:rejectReturnTaskId>`;
    xml += `<flowable:assignEmptyHandlerType>${props.customConfiguration.assignEmptyHandlerType}</flowable:assignEmptyHandlerType>`;
    if (Array.isArray(props.customConfiguration.assignEmptyUserIds)) {
      xml += `<flowable:assignEmptyUserIds>${esc(props.customConfiguration.assignEmptyUserIds.join(','))}</flowable:assignEmptyUserIds>`;
    }
    if (Array.isArray(props.customConfiguration.buttonsSetting)) {
      for (const button of props.customConfiguration.buttonsSetting) {
        xml += `<flowable:buttonsSetting id="${esc(button.id)}" displayName="${esc(button.displayName)}" enable="${esc(button.enable)}" />`;
      }
    }
    if (Array.isArray(props.customConfiguration.fieldsPermission)) {
      for (const field of props.customConfiguration.fieldsPermission) {
        xml += `<flowable:fieldsPermission field="${esc(field.key)}" titlke="${field.name}" permission="${esc(field.permission)}" />`;
      }
    }
    xml += `<flowable:signEnable>${props.customConfiguration.signEnable}</flowable:signEnable>`;
    xml += `<flowable:reasonRequire>${props.customConfiguration.reasonRequire}</flowable:reasonRequire>`;
  }

  xml += '</bpmn2:extensionElements>';

  // incoming flows
  const ins: any[] = edgeByTarget[id] || [];
  for (const ie of ins) {
    xml += `<bpmn2:incoming>${esc(ie && ie.id)}</bpmn2:incoming>`;
  }

  // outgoing flows
  const outs: any[] = edgeBySource[id] || [];
  for (const oe of outs) {
    xml += `<bpmn2:outgoing>${esc(oe && oe.id)}</bpmn2:outgoing>`;
  }
  xml += parseApproveMethodXml(props.approveMethod, props.approvalModePrecent);
  xml += `</bpmn2:userTask>`;

  return xml;
}

// 获取连线的xml
function parseSequenceFlow(sequenceFlow: any): any {
  // console.log('parseSequenceFlow', sequenceFlow);
  let xml = '';
  const props: any = (sequenceFlow && sequenceFlow.properties) || {};
  const id: string = props.id || (sequenceFlow && sequenceFlow.id) || '';
  const title: string = props.title || '';
  const doc: string = props.documentation || '';
  const sourceRef: string = props.sourceRef || (sequenceFlow && sequenceFlow.sourceNodeId) || '';
  const targetRef: string = props.targetRef || (sequenceFlow && sequenceFlow.targetNodeId) || '';

  xml += `<bpmn2:sequenceFlow id="${esc(id)}" name="${esc(title)}" sourceRef="${esc(sourceRef)}" targetRef="${esc(targetRef)}">`;
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  if ((Array.isArray(props.properties) && props.properties.length) || (Array.isArray(props.executionListener) && props.executionListener.length)) {
    xml += '<bpmn2:extensionElements>';
    if (Array.isArray(props.properties) && props.properties.length) {
      xml += getExtensionPropertiesXml(props.properties);
    }
    if (Array.isArray(props.executionListener) && props.executionListener.length) {
      xml += getExecutionListenersXml(props.executionListener);
    }

    xml += '</bpmn2:extensionElements>';
  }
  // 默认流转路径（在节点上添加 default="Flow_0jg4vd4"）在节点处添加
  // 普通流转路径 不做额外处理
  // 条件流转路径
  // 表达式
  if (props.arrowType === 'empty' && props.edgeTransfer) {
    if (props.edgeTransfer.lType === 'expression') {
      xml += `<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${esc(props.edgeTransfer.expression || '')}</bpmn2:conditionExpression>`;
    }
    else if (props.edgeTransfer.lType === 'scriptFormat') {
      if (props.edgeTransfer.scriptType === 'inline') {
        xml += `<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" language="${esc(props.edgeTransfer.scriptFormat || '')}">${esc(props.edgeTransfer.scriptContent || '')}</bpmn2:conditionExpression>`;
      }
      else if (props.edgeTransfer.scriptType === 'external') {
        xml += `<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" language="${esc(props.edgeTransfer.scriptFormat || '')}" flowable:resource="${esc(props.edgeTransfer.resource || '')}" />`;
      }
    }
  }
  xml += '</bpmn2:sequenceFlow>';

  return xml;
}

// 获取流程的xml
function parseProcess(nodes: any[], edges: any[], flowInfo: any): any {
  const edgeBySource: Record<string, any[]> = {};
  const edgeByTarget: Record<string, any[]> = {};
  for (const e of edges) {
    const srcId = e && (e.sourceNodeId || e.source);
    const tgtId = e && (e.targetNodeId || e.target);
    if (srcId) {
      edgeBySource[srcId] = edgeBySource[srcId] || [];
      edgeBySource[srcId].push(e);
    }
    if (tgtId) {
      edgeByTarget[tgtId] = edgeByTarget[tgtId] || [];
      edgeByTarget[tgtId].push(e);
    }
  }

  const doc = flowInfo && (flowInfo.otherInfo?.documentation || '');
  const processId = (flowInfo && flowInfo.id) || 'Process_1';
  const processName = (flowInfo && flowInfo.title) || 'New_Process';
  let xml = `<bpmn2:process id="${processId}" name="${processName}" isExecutable="true">`;
  if (doc) {
    xml += getDocumentationXml(doc);
  }
  if ((Array.isArray(flowInfo.properties) && flowInfo.properties.length) || (Array.isArray(flowInfo.executionListenerList) && flowInfo.executionListenerList.length)) {
    xml += '<bpmn2:extensionElements>';

    if (Array.isArray(flowInfo.properties) && flowInfo.properties.length) {
      xml += getExtensionPropertiesXml(flowInfo.properties);
    }
    if (Array.isArray(flowInfo.executionListenerList) && flowInfo.executionListenerList.length) {
      xml += getExecutionListenersXml(flowInfo.executionListenerList);
    }
    xml += '</bpmn2:extensionElements>';
  }

  // Nodes
  for (const n of nodes) {
    if (n.type === 'start-node') {
      xml += parseStartEvent(n, edgeBySource);
    }
    else if (n.type === 'end-node') {
      xml += parseEndEvent(n, edgeByTarget);
    }
    else if (['exclusive-gateway-node', 'parallel-gateway-node'].includes(n.type)) {
      xml += parseGatewayNode(n, edgeBySource, edgeByTarget);
    }
    else if (n.type === 'normal-task-node') {
      xml += parseNormalTaskNode(n, edgeBySource, edgeByTarget);
    }
    else if (n.type === 'user-task-node') {
      xml += parseUserTaskNode(n, edgeBySource, edgeByTarget);
    }
  }

  // Edges
  for (const e of edges) {
    xml += parseSequenceFlow(e);
  }

  xml += '</bpmn2:process>';
  return xml;
}

// 获取flow的message的xml
function parseMessage(messageList: any[]): string {
  let xml = '';
  for (const m of messageList) {
    xml += `<bpmn2:message id="${esc(m.messageId)}" name="${esc(m.messageName)}" />`;
  }
  return xml;
}

// 获取flow的signal的xml
function parseSignal(signalList: any[]): string {
  let xml = '';
  for (const s of signalList) {
    xml += `<bpmn2:signal id="${esc(s.signalId)}" name="${esc(s.signalName)}" />`;
  }
  return xml;
}

// bpmn 2.0标准 默认节点：开始结束36*36 任务/用户任务：100*80 网关：50*50
// 获取BPMNDiagram的xml
function parseBPMNDiagram(nodes: any[], edges: any[], processId: string): any {
  let xml = '';
  xml += `<bpmndi:BPMNDiagram id="BPMNDiagram_1">`;
  xml += `<bpmndi:BPMNPlane id="${esc(processId)}_di" bpmnElement="${esc(processId)}">`;

  for (const n of nodes) {
    const props: any = (n && n.properties) || {};
    const id: string = props.id || (n && n.id) || '';
    const width = shapeMap[n.type]?.width || 200;
    const height = shapeMap[n.type]?.height || 40;
    const newPoint = getTransformedNodePointForExport(n.x, n.y, n.type);
    const x = newPoint.x;
    const y = newPoint.y;
    xml += `<bpmndi:BPMNShape id="${esc(id)}_di" bpmnElement="${esc(id)}">`;
    xml += `<dc:Bounds x="${esc(x)}" y="${esc(y)}" width="${esc(width)}" height="${esc(height)}" />`;
    xml += '</bpmndi:BPMNShape>';
  }

  for (const e of edges) {
    xml += `<bpmndi:BPMNEdge id="${esc(e && e.id)}_di" bpmnElement="${esc(e && e.id)}">`;
    const pts = (e && Array.isArray(e.pointsList) && e.pointsList.length) ? e.pointsList : ((e && [e.startPoint, e.endPoint].filter(Boolean)) || []);
    const sourceNode = nodes.find(node => node.id === e.sourceNodeId);
    const targetNode = nodes.find(node => node.id === e.targetNodeId);
    const transformedStartPoint = getTransformedEdgeStartPoint(sourceNode, sourceNode.type) as Point;
    const transformedEndPoint = getTransformedEdgeEndPoint(targetNode, targetNode.type) as Point;
    const bendPoints = computeTwoBendPoints(transformedStartPoint, transformedEndPoint, { strategy: 'h-first' });
    pts[0] = transformedStartPoint || pts[0];
    pts[1] = bendPoints[0];
    pts[2] = bendPoints[1];
    pts[pts.length - 1] = transformedEndPoint || pts.at(-1);

    for (const p of pts) {
      if (!p) {
        continue;
      }
      xml += `<di:waypoint x="${esc(p.x)}" y="${esc(p.y)}" />`;
    }
    // xml += `<di:waypoint x="${esc(p.x)}" y="${esc(p.y)}" />`;
    xml += '</bpmndi:BPMNEdge>';
  }

  xml += '</bpmndi:BPMNPlane>';
  xml += '</bpmndi:BPMNDiagram>';
  return xml;
}

// 获取完整的BPMN xml
export default function generateBpmnXml(data: any, flowInfo: any): string {
  const nodes: any[] = Array.isArray(data && data.nodes) ? data.nodes : [];
  const edges: any[] = Array.isArray(data && data.edges) ? data.edges : [];
  const processId = (flowInfo && flowInfo.id) || 'Process_1';

  let xml = '<?xml version="1.0" encoding="UTF-8"?>';
  xml += '<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
  xml += '  xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"';
  xml += '  xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"';
  xml += '  xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"';
  xml += '  xmlns:di="http://www.omg.org/spec/DD/20100524/DI"';
  xml += '  xmlns:flowable="http://flowable.org/bpmn"';
  xml += `  targetNamespace="http://flowable.org/bpmn"`;
  xml += `  id="diagram_${processId}">`;
  // Process
  xml += parseProcess(nodes, edges, flowInfo);
  // message
  if (Array.isArray(flowInfo.messageList) && flowInfo.messageList.length) {
    xml += parseMessage(flowInfo.messageList);
  }
  // signal
  if (Array.isArray(flowInfo.signalList) && flowInfo.signalList.length) {
    xml += parseSignal(flowInfo.signalList);
  }

  // BPMN Diagram
  xml += parseBPMNDiagram(nodes, edges, processId);
  xml += '</bpmn2:definitions>';

  return xml;
}
