<template>
  <ResizeFixedLayout rightWidth="560px">
    <template #head>
      <Head
        @save="save"
        @close="close"
        @save-draft="saveDraft"
        :isCanDraft="editData.isCanDraft"
        :title="editData.id ? '编辑流程设计' : ''"
        :submitLoading="submitLoading"
      />
    </template>
    <template #left>
      <div class="containers">
        <div class="canvas" ref="canvas" id="canvas_node"></div>
        <div class="fixed-bottom">
          <ZoomInOrOut @in="zoomViewport(false)" @out="zoomViewport(true)" />
          <div class="fixed-refresh-box" @click="cleanDiagram">
            <div class="entry fixed-refresh">
              <IconFontSymbol icon="qingkong" />
            </div>
          </div>
        </div>
      </div>
    </template>
    <template #right>
      <component
        :key="bpmnCanvas.renderKey"
        :is="bpmnCanvas.componentName"
        class="property-panel"
        :categoryOptions="categoryOptions"
      />
    </template>
  </ResizeFixedLayout>
</template>
<script setup lang="ts">
  import { onMounted, provide, reactive, ref, shallowRef, defineAsyncComponent } from 'vue';
  import { message } from 'ant-design-vue';
  import { notification } from 'ant-design-vue';
  import CustomModeler from '/@bpmn/modeler';
  import { ResizeFixedLayout } from '/@/components/ModalPanel';
  import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
  import Head from '/@bpmn/layout/head/Index.vue';
  import { ZoomInOrOut } from '/@/components/ModalPanel';
  import {
    getInitializeXml,
    getProcessConfig,
    changeProcessProperties,
    initStartProperties,
    changePropertiesByParentId,
    initProperties,
    removeProperties,
    CanInitializeProperties,
    getLabelName,
    getBpmnJson,
    setChildNodesProperties,
    changeSequenceFlowSourceConfig,
    initStartingNodeProperties,
  } from '/@bpmn/config/property';
  import Start from '/@bpmn/panel/Start.vue';
  import Process from '/@bpmn/panel/Process.vue';
  import User from '/@bpmn/panel/User.vue';
  import { useBpmnStore } from '/@bpmn/store/bpmn';
  import { InfoId, InfoType } from '/@/model/workflow/bpmnConfig';
  import { cloneDeep } from 'lodash-es';
  import { uploadBlobApi } from '/@/api/sys/upload';
  import {
    BpmnNodeKey,
    MultipleInstancesType,
    ExecutionType,
    CallActivityType,
    FinishType,
  } from '/@/enums/workflowEnum';
  import { addDesign, editDesign, addDesignDraft } from '/@/api/workflow/design';
  import {
    ProcessJsonModel,
    RootElements,
    SequenceFlowSourceConfig,
    ValidateProcessConfigs,
  } from '/@/model/workflow/workflowConfig';
  import { validateRootElements } from './util/validate/verificationXml';
  import { validateJson } from './util/validate/verificationJson';
  import { randomStr } from '/@bpmn/util/random';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { TreeItem } from '/@/components/Tree';
  import { getDicDetailList } from '/@/api/system/dic';
  import { FlowCategory } from '/@/enums/workflowEnum';
  const { t } = useI18n();
  const Script = defineAsyncComponent(() => import('/@bpmn/panel/Script.vue'));
  const End = defineAsyncComponent(() => import('/@bpmn/panel/End.vue'));
  const Default = defineAsyncComponent(() => import('/@bpmn/panel/Default.vue'));
  const SequenceFlow = defineAsyncComponent(() => import('/@bpmn/panel/SequenceFlow.vue'));
  const CallActivity = defineAsyncComponent(() => import('/@bpmn/panel/CallActivity.vue'));
  let props = withDefaults(
    defineProps<{
      editData: {
        id: string;
        json: { processConfig: any; childNodeConfig: Array<any> };
        xml: string;
        isCanDraft: boolean;
      };
    }>(),
    {
      editData: () => {
        return {
          id: '',
          json: { processConfig: null, childNodeConfig: [] },
          xml: '',
          isCanDraft: false,
        };
      },
    },
  );
  const submitLoading = ref<boolean>(false);
  const emit = defineEmits(['close']);
  provide('updateElementName', updateElementName);
  provide('updateConditionExpression', updateConditionExpression);
  provide('updateCountersign', updateCountersign);
  provide('updateScriptTaskExpression', updateScriptTaskExpression);
  provide('updateCallActivityExpression', updateCallActivityExpression);
  provide('updateCallActivityMulti', updateCallActivityMulti);
  const store = useBpmnStore();
  const componentByType: Map<InfoType, any> = new Map([
    [BpmnNodeKey.PROCESS, Process],
    [BpmnNodeKey.SEQUENCEFLOW, SequenceFlow],
    [BpmnNodeKey.START, Start],
    [BpmnNodeKey.USER, User],
    [BpmnNodeKey.END, End],
    [BpmnNodeKey.SCRIPT, Script],
    [BpmnNodeKey.CALLACTIVITY, CallActivity],
  ]);

  let bpmnModeler: any;
  let bpmnElement: any;
  let canvas = ref(null);
  let bpmnCanvas = reactive({
    componentName: shallowRef(Process),
    zoom: 1,
    renderKey: 1,
    elementEventName: 'showPanel',
    removedId: '',
  });
  // 模板分类
  const categoryOptions = ref<TreeItem[]>([]);

  async function getCategoryList() {
    categoryOptions.value = (await getDicDetailList({
      itemId: FlowCategory.ID,
    })) as unknown as TreeItem[];
  }
  onMounted(async () => {
    await getCategoryList();
    init();
  });
  function init() {
    bpmnModeler = new (CustomModeler as any)({
      container: canvas.value,
      additionalModules: [
        {
          labelEditingProvider: ['value', ''], //禁用节点编辑
        },
      ],
    });
    if (props.editData?.id) {
      editDiagram();
    } else {
      addDiagram();
    }
  }
  async function createDiagram(xml: String) {
    try {
      await bpmnModeler.importXML(xml);
      // fitViewport();
      addModelerListener();
    } catch (error) {}
  }
  function addDiagram() {
    let resourceId = 'process_' + randomStr();
    let processConfig = cloneDeep(getProcessConfig);
    processConfig.processId = resourceId;
    changeProcessProperties(processConfig);
    initStartProperties();
    let xml = getInitializeXml(resourceId);
    createDiagram(xml);
  }
  function editDiagram() {
    changeProcessProperties({
      ...cloneDeep(getProcessConfig),
      ...props.editData.json.processConfig,
    });
    setChildNodesProperties(props.editData.json.childNodeConfig);
    createDiagram(props.editData.xml);
  }
  function cleanDiagram() {
    store.$reset();
    (document.querySelector('#canvas_node') as HTMLElement).innerHTML = '';
    init();
  }
  function changePanel(elementType?: InfoType, elementId?: InfoId) {
    changeComponentName(elementType);
    bpmnCanvas.elementEventName = 'showPanel';
    if (elementId) {
      let { setInfoId } = store;
      setInfoId(elementId);
    }
    bpmnCanvas.renderKey++;
  }

  function changeComponentName(elementType?: InfoType) {
    bpmnCanvas.componentName =
      elementType && componentByType.has(elementType) && componentByType.get(elementType)
        ? componentByType.get(elementType)
        : Default;
  }
  function addModelerListener() {
    bpmnModeler.on('shape.added', (e: any) => {
      let shape = e.element ? bpmnModeler.get('elementRegistry').get(e.element.id) : e.shape;
      changePanel(shape.type, shape.id);
      bpmnCanvas.elementEventName = 'added';
      updateSequenceFlowSourceConfig(e);
    });
    bpmnModeler.on('selection.changed', (e: any) => {
      bpmnCanvas.elementEventName = 'change';
      const NewElement = e.newSelection[0];
      if (NewElement) {
        bpmnElement = NewElement;
        if (NewElement.type == 'label') {
          // bpmnElement = NewElement.parent;
          if (NewElement.parent && NewElement.parent.type && NewElement.parent.id) {
            changePanel(NewElement.parent.type, NewElement.parent.id);
          }
        } else {
          if (CanInitializeProperties(NewElement.id) && NewElement.type !== 'label') {
            let name = getLabelName(NewElement.type);
            if (e.element.id.includes('Start_User_Task')) {
              name = '用户开始节点';
            }
            if (name) {
              initProperties(NewElement.id, NewElement.type, name, NewElement.parent.id);
              const modeling = bpmnModeler.get('modeling');
              modeling.updateProperties(NewElement, { name });
              changePanel(NewElement.type, NewElement.id);
            }
          } else {
            if (NewElement && NewElement.type && NewElement.id) {
              changePanel(NewElement.type, NewElement.id);
            }
          }
        }
      }
    });
    bpmnModeler.on('element.changed', (e: any) => {
      if (bpmnCanvas.elementEventName === 'removed') {
        removeProperties(e.element.id);
      } else {
        bpmnElement = e.element;
        if (
          bpmnCanvas.removedId != e.element.id &&
          CanInitializeProperties(e.element.id) &&
          e.element.type !== 'label'
        ) {
          let name = getLabelName(e.element.type);

          if (name) {
            if (e.element.name && e.element.name.includes('Start_User_Task_')) {
              name = '用户开始节点';
              initStartingNodeProperties(e.element.id, e.element.type, name, e.element.parent.id);
            } else {
              initProperties(e.element.id, e.element.type, name, e.element.parent.id);
            }
            const modeling = bpmnModeler.get('modeling');
            modeling.updateProperties(e.element, { name });
          }
        } else {
          if (e && e.element && e.element.parent && bpmnCanvas.removedId != e.element.id) {
            changePropertiesByParentId(e.element.id, e.element.parent.id);
          }
        }
        updateSequenceFlowSourceConfig(e);
      }
    });
    bpmnModeler.on('shape.removed', (e) => {
      if (e.element && e.element.id && e.element.id.includes('_label')) {
        let id = e.element.id.replace('_label', '');
        bpmnCanvas.removedId = id;
        removeProperties(id);
      }
      bpmnCanvas.componentName = Process; //重置到流程属性面板
      bpmnCanvas.elementEventName = 'removed';
    });
    bpmnModeler.on('element.click', (e: any) => {
      updateSequenceFlowSourceConfig(e);
      bpmnCanvas.elementEventName = 'click';
      if (e.element.type !== 'label') {
        let shape = e.element ? bpmnModeler.get('elementRegistry').get(e.element.id) : e.shape;
        changePanel(shape.type, shape.id);
      }
    });
  }
  // 更新流程线的上一节点信息
  function updateSequenceFlowSourceConfig(e) {
    if (e.element && e.element.type == 'bpmn:SequenceFlow') {
      if (e.element.source && e.element.source.type) {
        let sourceConfig: SequenceFlowSourceConfig = {
          id: e.element.source.id,
          type: e.element.source.type,
        };
        changeSequenceFlowSourceConfig(e.element.id, sourceConfig);
      }
    }
  }
  // 更新xml中节点名称
  function updateElementName() {
    const modeling = bpmnModeler.get('modeling');
    const { infoId, getProperties } = store;
    if (
      (getProperties(infoId)?.type == BpmnNodeKey.START ||
        getProperties(infoId)?.type == BpmnNodeKey.END ||
        getProperties(infoId)?.type == BpmnNodeKey.INCLUSIVE ||
        getProperties(infoId)?.type == BpmnNodeKey.PARALLEL ||
        getProperties(infoId)?.type == BpmnNodeKey.EXCLUSIVE ||
        getProperties(infoId)?.type == BpmnNodeKey.SEQUENCEFLOW) &&
      !getProperties(infoId)?.name
    ) {
      modeling.updateProperties(bpmnElement, { name: '_' });
    } else {
      modeling.updateProperties(bpmnElement, { name: getProperties(infoId)?.name });
    }
  }
  // 更新xml中流程线参数
  function updateConditionExpression(val: string) {
    let moddle = bpmnModeler.get('moddle');
    const modeling = bpmnModeler.get('modeling');
    let newCondition = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
      body: val ? '${' + val + '}' : '',
    });
    modeling.updateProperties(bpmnElement, { conditionExpression: newCondition });
  }

  /**
   * 修改脚本任务配置
   * @param type 脚本类型
   * @param scriptContent 脚本内容
   */
  function updateScriptTaskExpression(type: string, scriptContent: string) {
    const modeling = bpmnModeler.get('modeling');
    modeling.updateProperties(bpmnElement, {
      'bpmn:script': scriptContent,
      scriptFormat: type,
    });
  }

  /**
   * 修改外部任务配置
   * @param processId 关联的外部流程processId
   */
  function updateCallActivityExpression(processId: string) {
    const modeling = bpmnModeler.get('modeling');
    modeling.updateProperties(bpmnElement, {
      calledElement: processId,
    });
  }
  /**
   * 修改外部任务配置
   * @param executionType 外部流程 执行类型
   */
  function updateCallActivityMulti(
    callActivityType: CallActivityType,
    executionType: ExecutionType,
    finishType: FinishType | null,
    percentage: number | null,
  ) {
    const moddle = bpmnModeler.get('moddle');
    const modeling = bpmnModeler.get('modeling');
    if (callActivityType == CallActivityType.SINGLE) {
      bpmnElement && delete bpmnElement.businessObject.loopCharacteristics;
      modeling.updateProperties(bpmnElement, {});
    } else {
      let loopCharacteristics = moddle.create(BpmnNodeKey.COUNTERSIGN, {
        isSequential: executionType === ExecutionType.SEQUENCE ? true : false,
      });

      if (finishType !== FinishType.ALL) {
        let conditionElement;
        //单个
        if (finishType === FinishType.SINGLE) {
          conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
            body: '${nrOfCompletedInstances == 1}',
          });
        }
        //百分比
        if (finishType === FinishType.PERCENTAGE) {
          conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
            body: '${(nrOfCompletedInstances / nrOfInstances) * 100 >= ' + percentage + '}',
          });
        }
        loopCharacteristics['completionCondition'] = conditionElement;
      }
      const { infoId } = store;
      loopCharacteristics.$attrs['camunda:collection'] = '${assigneeList_' + infoId + '}';
      loopCharacteristics.$attrs['camunda:elementVariable'] = 'assignee';
      modeling.updateProperties(bpmnElement, { loopCharacteristics });
    }
    // getXMl();
  }
  /**
   * 更新会签节点
   * @param multipleInstancesType 会签类型
   * @param finishCondition 完成条件
   * @param percentage 完成条件百分比
   */
  function updateCountersign(
    multipleInstancesType: MultipleInstancesType,
    finishCondition: number | null,
    percentage: number | null,
  ) {
    const moddle = bpmnModeler.get('moddle');
    const modeling = bpmnModeler.get('modeling');
    if (multipleInstancesType == MultipleInstancesType.NONE) {
      bpmnElement && delete bpmnElement.businessObject.loopCharacteristics;
      modeling.updateProperties(bpmnElement, {});
    } else {
      let conditionElement;
      const { infoId } = store;

      // if (finishCondition) {
      //   //单个
      //   if (finishCondition === 1) {
      //     conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
      //       body: '${nrOfCompletedInstances == 1}',
      //     });
      //   }
      //   //百分比
      //   if (finishCondition === 2) {
      //     conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
      //       body: '${(nrOfCompletedInstances / nrOfInstances) * 100 >= ' + percentage + '}',
      //     });
      //   }
      // }

      //全部
      if (finishCondition === 0) {
        conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
          body:
            '${' +
            infoId +
            '___button___rejectCount > 0 || nrOfCompletedInstances == nrOfInstances}',
        });
      }
      //单个
      if (finishCondition === 1) {
        conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
          body:
            '${' +
            infoId +
            '___button___agreeCount > 0 || nrOfCompletedInstances == nrOfInstances}',
        });
      }
      //百分比
      if (finishCondition === 2) {
        // （同意人数 / 所有会签数量） * 100 >= 百分比数值
        conditionElement = moddle.create(BpmnNodeKey.CONDITION_EXPRESSION, {
          body:
            '${(' +
            infoId +
            '___button___agreeCount / nrOfInstances) * 100 >= ' +
            percentage +
            ' || nrOfCompletedInstances == nrOfInstances}',
          // body: '${(nrOfCompletedInstances / nrOfInstances) * 100 >= ' + percentage + '}',
        });
      }

      let loopCharacteristics = moddle.create(BpmnNodeKey.COUNTERSIGN, {
        isSequential: multipleInstancesType === MultipleInstancesType.ASYNC ? true : false,
      });

      loopCharacteristics['completionCondition'] = conditionElement;

      loopCharacteristics.$attrs['camunda:collection'] = '${assigneeList_' + infoId + '}';
      loopCharacteristics.$attrs['camunda:elementVariable'] = 'assignee';

      modeling.updateProperties(bpmnElement, { loopCharacteristics });
    }
  }

  function zoomViewport(zoomIn = true) {
    bpmnCanvas.zoom = bpmnModeler.get('canvas').zoom();
    bpmnCanvas.zoom += zoomIn ? 0.1 : -0.1;
    bpmnModeler.get('canvas').zoom(bpmnCanvas.zoom);
  }
  function validateProcessConfig(rootElements: RootElements, processJson: ProcessJsonModel) {
    const validateProcess: ValidateProcessConfigs = [];
    validateProcess.push(...validateRootElements(rootElements));
    validateProcess.push(...validateJson(processJson));

    if (validateProcess.length > 0) {
      let duration = 5;
      validateProcess.forEach((item) => {
        notification.open({
          type: 'error',
          message: item.nodeName,
          description: item.msg,
          duration,
        });
        duration = duration + 2;
      });
      return false;
    }
    return true;
  }
  async function uploadSign(svg) {
    try {
      const fileUrl = await uploadBlobApi(new Blob([svg]), 'thumb.svg');
      return fileUrl;
    } catch (error) {
      return '';
    }
  }

  async function save() {
    submitLoading.value = true;
    const json = getBpmnJson();
    const rootElementsJson = bpmnModeler.getDefinitions().rootElements;
    const rootElementsFlowElementsArr = rootElementsJson[0].flowElements;

    if (validateProcessConfig(rootElementsFlowElementsArr, json)) {
      try {
        const { svg } = await bpmnModeler.saveSVG({ format: true });
        let workflowChat = await uploadSign(svg);
        json.processConfig['workflowChat'] = workflowChat;
        let { xml } = await bpmnModeler.saveXML({ format: true });
        json.processConfig.xmlContent = xml;
        let val = false;
        if (props.editData.id) {
          json['id'] = props.editData.id;
          val = await editDesign(json);
        } else {
          val = await addDesign(json);
        }
        if (val) {
          message.success(t('保存成功'));
          close();
        } else {
          message.error(t('保存失败'));
        }
      } catch (error) {
        message.error(t('保存失败'));
      }
    }
    submitLoading.value = false;
  }

  async function saveDraft() {
    const json = getBpmnJson();

    try {
      const { svg } = await bpmnModeler.saveSVG({ format: true });
      let workflowChat = await uploadSign(svg);
      json.processConfig['workflowChat'] = workflowChat;
      let { xml } = await bpmnModeler.saveXML({ format: true });
      json.processConfig.xmlContent = xml;
      let val = false;
      if (props.editData.id) {
        json['id'] = props.editData.id;
      }
      val = await addDesignDraft(json);

      if (val) {
        message.success(t('保存成功'));
        close();
      } else {
        message.error(t('保存失败'));
      }
    } catch (error) {
      message.error(t('保存失败'));
    }
  }
  function close() {
    bpmnModeler = null;
    canvas.value = null;
    store.$reset();
    emit('close');
  }
  // async function getXMl() {
  //   let { xml } = await bpmnModeler.saveXML({ format: true });
  //   console.log('xml: ', xml);
  // }
  // function fitViewport() {
  //   bpmnCanvas.zoom = bpmnModeler.get('canvas').zoom('fit-viewport');
  //   const bbox = (document as any).querySelector('.flow-containers .viewport').getBBox();
  //   const currentViewbox = bpmnModeler.get('canvas').viewbox();
  //   const elementMid = {
  //     x: bbox.x + bbox.width / 2 - 65,
  //     y: bbox.y + bbox.height / 2,
  //   };
  //   bpmnModeler.get('canvas').viewbox({
  //     x: elementMid.x - currentViewbox.width / 2,
  //     y: elementMid.y - currentViewbox.height / 2,
  //     width: currentViewbox.width,
  //     height: currentViewbox.height,
  //   });
  //   bpmnCanvas.zoom = (bbox.width / currentViewbox.width) * 1.8;
  // }
</script>
<style>
  @import url('/@/assets/style/bpmn-js/diagram-js.css');
  @import url('/@/assets/style/bpmn-js/bpmn-font/css/bpmn.css');
  @import url('/@/assets/style/bpmn-js/bpmn-font/css/bpmn-codes.css');
  @import url('/@/assets/style/bpmn-js/bpmn-font/css/bpmn-embedded.css');
</style>
<style lang="less" scoped>
  .containers {
    width: 100%;
    height: 100%;
    position: relative;
  }

  /* 画布 */
  .canvas {
    width: 100%;
    height: 100%;
  }

  .property-panel {
    width: 100%;
    height: 100%;
  }

  svg.new-parent {
    background: #000 !important;
  }

  :deep(.djs-palette-entries) {
    display: flex;
    margin-left: 18vw;
  }

  :deep(.group[data-group='none']) {
    display: none;
  }

  :deep(.group[data-group='tools']),
  :deep(.group[data-group='event']) {
    display: flex;
    box-shadow: 0 2px 2px 2px rgb(0 0 0 / 4%);
    border-radius: 4px;
    padding: 4px;
    margin-right: 10px;
  }

  :deep(.djs-palette .entry, .djs-palette .djs-palette-toggle) {
    width: 36px;
    height: 36px;
    line-height: 30px;
    font-size: 26px;
  }

  :deep(.djs-selection-outline) {
    opacity: 0.1;
  }

  :deep(.djs-context-pad.open) {
    box-shadow: 0 2px 2px 2px rgb(0 0 0 / 4%);
    border-radius: 4px;
    background-color: #fff;
  }

  :deep(.group[data-group='event1']) {
    display: flex;
    flex-wrap: wrap;
    width: 102px;
    margin-top: 6px;
    margin-left: 6px;
    margin-right: 6px;
  }

  :deep(.group[data-group='event2']) {
    margin: 6px;
  }

  :deep(.djs-context-pad .entry) {
    border: none;
    margin: 6px;
  }

  :deep(.djs-context-pad .entry.bpmn-icon-trash) {
    color: @clear-color;
  }

  :deep(.bjs-align-elements-menu-entry img) {
    width: 10px;
    height: 10px;
  }

  :deep(.group[data-group='align']),
  :deep(.group[data-group='distribute']) {
    width: 75px;
    display: flex;
    flex-wrap: wrap;
  }

  :deep(.icon-custom-context-pad),
  :deep(.djs-overlay-context-pad) {
    color: #000;
  }

  :deep(.bjs-powered-by) {
    display: none !important;
  }

  :deep(.djs-direct-editing-content) {
    color: #000;
  }

  :deep(.djs-palette) {
    border: none;
    width: 0;
    height: 0;
  }

  /* 按钮（放大 发小 清除） */
  .fixed-bottom {
    position: absolute;
    top: 22px;
    left: calc(20vw + 420px);
    display: flex;
  }

  .fixed-refresh-box {
    background-color: @clear-color;
    box-shadow: 0 2px 2px 2px @clear-color;
    border-radius: 4px;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 36px;
    height: 36px;
  }

  .fixed-refresh {
    font-size: 20px;
    color: #fff;
    cursor: pointer;
  }

  :deep(.ant-table-cell) {
    padding: 0;
  }

  :deep(.ant-table.ant-table-middle .ant-table-thead > tr > th) {
    &:first-child {
      border-top-left-radius: 0 !important;
    }

    &:last-child {
      border-top-right-radius: 0 !important;
    }
  }
</style>
