<template>
    <div class="my-process-designer">
        <div class="my-process-designer__header" style="z-index: 999; display: table-row-group">
            <slot name="control-header"></slot>
            <template v-if="!$slots['control-header']">
                <ElButtonGroup key="file-control">
                    <el-button icon="el-icon-FolderOpened" @click="refFile.click()">打开文件</el-button>
                    <el-tooltip effect="light" placement="bottom">
                        <template #content>
                            <div style="color: #409eff">
                                <el-button link @click="downloadProcessAsXml()">下载为XML文件</el-button>
                                <br />

                                <el-button link @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
                                <br />

                                <el-button link @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
                            </div>
                        </template>
                        <el-button icon="el-icon-Download">下载文件</el-button>
                    </el-tooltip>
                </ElButtonGroup>
                <ElButtonGroup key="align-control">
                    <el-tooltip effect="light" content="向左对齐">
                        <el-button icon="el-icon-CaretLeft" @click="elementsAlign('left')" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="向右对齐">
                        <el-button icon="el-icon-CaretRight" @click="elementsAlign('right')" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="向上对齐">
                        <el-button icon="el-icon-CaretTop" @click="elementsAlign('top')" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="向下对齐">
                        <el-button icon="el-icon-CaretBottom" @click="elementsAlign('bottom')" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="水平居中">
                        <el-button icon="el-icon-DCaret" @click="elementsAlign('center')" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="垂直居中">
                        <el-button class="align align-middle" icon="el-icon-DCaret" @click="elementsAlign('middle')" />
                    </el-tooltip>
                </ElButtonGroup>
                <ElButtonGroup key="scale-control">
                    <el-tooltip effect="light" content="缩小视图">
                        <el-button :disabled="defaultZoom < 0.2" icon="el-icon-ZoomOut" @click="processZoomOut()" />
                    </el-tooltip>
                    <el-button>{{ Math.floor(defaultZoom * 10 * 10) + '%' }}</el-button>
                    <el-tooltip effect="light" content="放大视图">
                        <el-button :disabled="defaultZoom > 4" icon="el-icon-ZoomIn" @click="processZoomIn()" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="重置视图并居中">
                        <el-button icon="el-icon-ScaleToOriginal" @click="processReZoom()" />
                    </el-tooltip>
                </ElButtonGroup>
                <ElButtonGroup key="stack-control">
                    <el-tooltip effect="light" content="撤销">
                        <el-button :disabled="!revocable" icon="el-icon-RefreshLeft" @click="processUndo()" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="恢复">
                        <el-button :disabled="!recoverable" icon="el-icon-RefreshRight" @click="processRedo()" />
                    </el-tooltip>
                    <el-tooltip effect="light" content="重新绘制">
                        <el-button icon="el-icon-Refresh" @click="processRestart" />
                    </el-tooltip>
                </ElButtonGroup>
                <el-button
                    icon="el-icon-Plus"
                    title="保存模型"
                    @click="preservation"
                    :type="props.headerButtonType"
                    :disabled="simulationStatus"
                >
                    保存模型
                </el-button>
            </template>
            <el-button class="quit" icon="el-icon-ArrowLeftBold" type="primary" @click="emit('quit')">返回</el-button>
            <!-- 用于打开本地文件-->
            <input
                type="file"
                id="files"
                ref="refFile"
                style="display: none"
                accept=".xml, .bpmn"
                @change="importLocalFile"
            />
        </div>
        <div class="my-process-designer__container">
            <div
                class="my-process-designer__canvas"
                ref="bpmnCanvas"
                id="bpmnCanvas"
                :style="`width: 100%; height: ${height}`"
            ></div>
            <!-- <div id="js-properties-panel" class="panel"></div> -->
            <!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
        </div>
        <el-dialog title="保存流程" v-model="previewModelVisible" width="500px" :scroll="true" max-height="600px">
            <el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="auto">
                <el-form-item label="流程名称" prop="name">
                    <el-input v-model="form.name" placeholder="请输入流程名称" />
                </el-form-item>
                <el-form-item label="流程分类" prop="category">
                    <el-select v-model="form.category" placeholder="请输入流程流程名分类">
                        <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
                    </el-select>
                </el-form-item>
            </el-form>

            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="previewModelVisible = false">取消</el-button>
                    <el-button type="primary" @click="processSave">保存</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>

<script lang="ts" setup>
// import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
import { onMounted, provide, ref, computed, onBeforeUnmount, onBeforeMount } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import BpmnModeler from 'bpmn-js/lib/Modeler'
import DefaultEmptyXML from './plugins/defaultEmpty'
import { mainHeight } from '@/utils/layout'
// 翻译方法
import customTranslate from './plugins/translate/customTranslate'
import translationsCN from './plugins/translate/zh'
// 模拟流转流程
import tokenSimulation from 'bpmn-js-token-simulation'
// 标签解析构建器
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
// import propertiesPanelModule from 'bpmn-js-properties-panel'
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
// 标签解析 Moddle
import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json'
import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json'
import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json'
// 标签解析 Extension
import camundaModdleExtension from './plugins/extension-moddle/camunda'
import activitiModdleExtension from './plugins/extension-moddle/activiti'
import flowableModdleExtension from './plugins/extension-moddle/flowable'
// 引入json转换与高亮
// import xml2js from 'xml-js'
// import xml2js from 'fast-xml-parser'
import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
// 代码高亮插件
// import hljs from 'highlight.js/lib/highlight'
// import 'highlight.js/styles/github-gist.css'
// hljs.registerLanguage('xml', 'highlight.js/lib/languages/xml')
// hljs.registerLanguage('json', 'highlight.js/lib/languages/json')
// const eventName = reactive({
//   name: ''
// })

defineOptions({ name: 'MyProcessDesigner' })

const bpmnCanvas = ref()
const refFile = ref()
const emit = defineEmits([
    'destroy',
    'init-finished',
    'save',
    'commandStack-changed',
    'input',
    'change',
    'canvas-viewbox-changed',
    // eventName.name
    'element-click',
    'quit'
])
const height = mainHeight(60).height

const props = defineProps({
    value: String, // xml 字符串
    // valueWatch: true, // xml 字符串的 watch 状态
    processId: String, // 流程 key 标识
    processName: String, // 流程 name 名字
    formId: Number, // 流程 form 表单编号
    translations: {
        // 自定义的翻译文件
        type: Object,
        default: () => {}
    },
    additionalModel: [Object, Array], // 自定义model
    moddleExtension: {
        // 自定义moddle
        type: Object,
        default: () => {}
    },
    onlyCustomizeAddi: {
        type: Boolean,
        default: false
    },
    onlyCustomizeModdle: {
        type: Boolean,
        default: false
    },
    simulation: {
        type: Boolean,
        default: true
    },
    keyboard: {
        type: Boolean,
        default: true
    },
    prefix: {
        type: String,
        default: 'camunda'
    },
    events: {
        type: Array,
        default: () => ['element.click']
    },
    headerButtonSize: {
        type: String,
        default: 'small',
        validator: (value: string) => ['default', 'medium', 'small', 'mini'].indexOf(value) !== -1
    },
    headerButtonType: {
        type: String,
        default: 'primary',
        validator: (value: string) =>
            ['default', 'primary', 'success', 'warning', 'danger', 'info'].indexOf(value) !== -1
    }
})

provide('configGlobal', props)
let bpmnModeler: any = null
const defaultZoom = ref(1)
const previewModelVisible = ref(false)
const simulationStatus = ref(false)
const previewResult = ref('')
const ruleFormRef = ref()
const previewType = ref('xml')
const recoverable = ref(false)
const revocable = ref(false)
const rules = {
    name: [{ required: true, message: '请输入流程名称', trigger: 'blur' }],
    category: [{ required: true, message: '请输入流程分类', trigger: 'blur' }]
}
const options = [
    {
        name: '默认分类',
        id: '默认分类'
    },
    {
        name: '业务分类',
        id: '业务分类'
    }
]
const form = ref({
    name: '',
    category: ''
})
const additionalModules = computed(() => {
    console.log(props.additionalModel, 'additionalModel')
    const Modules: any[] = []
    // 仅保留用户自定义扩展模块
    if (props.onlyCustomizeAddi) {
        if (Object.prototype.toString.call(props.additionalModel) == '[object Array]') {
            return props.additionalModel || []
        }
        return [props.additionalModel]
    }

    // 插入用户自定义扩展模块
    if (Object.prototype.toString.call(props.additionalModel) == '[object Array]') {
        Modules.push(...(props.additionalModel as any[]))
    } else {
        props.additionalModel && Modules.push(props.additionalModel)
    }

    // 翻译模块
    const TranslateModule = {
        translate: ['value', customTranslate(props.translations || translationsCN)]
    }
    Modules.push(TranslateModule)

    // 模拟流转模块
    if (props.simulation) {
        Modules.push(tokenSimulation)
    }

    // 根据需要的流程类型设置扩展元素构建模块
    // if (this.prefix === "bpmn") {
    //   Modules.push(bpmnModdleExtension);
    // }
    console.log(props.prefix, 'props.prefix ')
    if (props.prefix === 'camunda') {
        Modules.push(camundaModdleExtension)
    }
    if (props.prefix === 'flowable') {
        Modules.push(flowableModdleExtension)
    }
    if (props.prefix === 'activiti') {
        Modules.push(activitiModdleExtension)
    }

    return Modules
})
const moddleExtensions = computed(() => {
    console.log(props.onlyCustomizeModdle, 'props.onlyCustomizeModdle')
    console.log(props.moddleExtension, 'props.moddleExtension')
    console.log(props.prefix, 'props.prefix')
    const Extensions: any = {}
    // 仅使用用户自定义模块
    if (props.onlyCustomizeModdle) {
        return props.moddleExtension || null
    }

    // 插入用户自定义模块
    if (props.moddleExtension) {
        for (let key in props.moddleExtension) {
            Extensions[key] = props.moddleExtension[key]
        }
    }

    // 根据需要的 "流程类型" 设置 对应的解析文件
    if (props.prefix === 'activiti') {
        Extensions.activiti = activitiModdleDescriptor
    }
    if (props.prefix === 'flowable') {
        Extensions.flowable = flowableModdleDescriptor
    }
    if (props.prefix === 'camunda') {
        Extensions.camunda = camundaModdleDescriptor
    }
    return Extensions
})
console.log(additionalModules, 'additionalModules()')
console.log(moddleExtensions, 'moddleExtensions()')
const initBpmnModeler = () => {
    if (bpmnModeler) return
    let data = document.getElementById('bpmnCanvas')
    console.log(data, 'data')
    console.log(props.keyboard, 'props.keyboard')
    console.log(additionalModules, 'additionalModules()')
    console.log(moddleExtensions, 'moddleExtensions()')

    bpmnModeler = new BpmnModeler({
        // container: this.$refs['bpmn-canvas'],
        // container: getCurrentInstance(),
        // container: needClass,
        // container: bpmnCanvas.value,
        container: data,
        // width: '100%',
        // 添加控制板
        // propertiesPanel: {
        // parent: '#js-properties-panel'
        // },
        keyboard: props.keyboard ? { bindTo: document } : null,
        // additionalModules: additionalModules.value,
        additionalModules: additionalModules.value,
        moddleExtensions: moddleExtensions.value

        // additionalModules: [
        // additionalModules.value
        // propertiesPanelModule,
        // propertiesProviderModule
        // propertiesProviderModule
        // ],
        // moddleExtensions: { camunda: moddleExtensions.value }
    })

    // bpmnModeler.createDiagram()

    // console.log(bpmnModeler, 'bpmnModeler111111')
    emit('init-finished', bpmnModeler)
    initModelListeners()
}

const initModelListeners = () => {
    const EventBus = bpmnModeler.get('eventBus')
    console.log(EventBus, 'EventBus')
    // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
    props.events.forEach((event: any) => {
        EventBus.on(event, function (eventObj) {
            let eventName = event.replace(/\./g, '-')
            // eventName.name = eventName
            let element = eventObj ? eventObj.element : null
            console.log(eventName, 'eventName')
            console.log(element, 'element')
            emit('element-click', element, eventObj)
            // emit(eventName, element, eventObj)
        })
    })
    // 监听图形改变返回xml
    EventBus.on('commandStack.changed', async event => {
        try {
            recoverable.value = bpmnModeler.get('commandStack').canRedo()
            revocable.value = bpmnModeler.get('commandStack').canUndo()
            let { xml } = await bpmnModeler.saveXML({ format: true })
            emit('commandStack-changed', event)
            emit('input', xml)
            emit('change', xml)
        } catch (e: any) {
            console.error(`[Process Designer Warn]: ${e.message || e}`)
        }
    })
    // 监听视图缩放变化
    bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
        emit('canvas-viewbox-changed', { viewbox })
        const { scale } = viewbox
        defaultZoom.value = Math.floor(scale * 100) / 100
    })
}
/* 创建新的流程图 */
const createNewDiagram = async xml => {
    console.log(xml, 'xml')
    // 将字符串转换成图显示出来
    let newId = props.processId || `Process_${new Date().getTime()}`
    let newName = props.processName || `业务流程_${new Date().getTime()}`
    let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix)
    try {
        // console.log(xmlString, 'xmlString')
        // console.log(this.bpmnModeler.importXML);
        let { warnings } = await bpmnModeler.importXML(xmlString)
        console.log(warnings, 'warnings')
        if (warnings && warnings.length) {
            warnings.forEach(warn => console.warn(warn))
        }
    } catch (e: any) {
        console.error(`[Process Designer Warn]: ${e.message || e}`)
    }
}

// 下载流程图到本地
const downloadProcess = async type => {
    try {
        // 按需要类型创建文件并下载
        if (type === 'xml' || type === 'bpmn') {
            const { err, xml } = await bpmnModeler.saveXML()
            // 读取异常时抛出异常
            if (err) {
                console.error(`[Process Designer Warn ]: ${err.message || err}`)
            }
            let { href, filename } = setEncoded(type.toUpperCase(), xml)
            downloadFunc(href, filename)
        } else {
            const { err, svg } = await bpmnModeler.saveSVG()
            // 读取异常时抛出异常
            if (err) {
                return console.error(err)
            }
            let { href, filename } = setEncoded('SVG', svg)
            downloadFunc(href, filename)
        }
    } catch (e: any) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`)
    }
    // 文件下载方法
    function downloadFunc(href, filename) {
        if (href && filename) {
            let a = document.createElement('a')
            a.download = filename //指定下载的文件名
            a.href = href //  URL对象
            a.click() // 模拟点击
            URL.revokeObjectURL(a.href) // 释放URL 对象
        }
    }
}

// 根据所需类型进行转码并返回下载地址
const setEncoded = (type, data) => {
    const filename = 'diagram'
    const encodedData = encodeURIComponent(data)
    return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
    }
}

// 加载本地文件
const importLocalFile = () => {
    const file = refFile.value.files[0]
    const reader = new FileReader()
    reader.readAsText(file)
    reader.onload = function () {
        let xmlStr = this.result
        createNewDiagram(xmlStr)
    }
}
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
const downloadProcessAsXml = () => {
    downloadProcess('xml')
}
const downloadProcessAsBpmn = () => {
    downloadProcess('bpmn')
}
const downloadProcessAsSvg = () => {
    downloadProcess('svg')
}
const processSimulation = () => {
    simulationStatus.value = !simulationStatus.value
    console.log(bpmnModeler.get('toggleMode', 'strict'), "bpmnModeler.get('toggleMode')")
    props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode()
}
const processRedo = () => {
    bpmnModeler.get('commandStack').redo()
}
const processUndo = () => {
    bpmnModeler.get('commandStack').undo()
}
const processZoomIn = (zoomStep = 0.1) => {
    let newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100
    if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
    }
    defaultZoom.value = newZoom
    bpmnModeler.get('canvas').zoom(defaultZoom.value)
}
const processZoomOut = (zoomStep = 0.1) => {
    let newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100
    if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
    }
    defaultZoom.value = newZoom
    bpmnModeler.get('canvas').zoom(defaultZoom.value)
}
// const processZoomTo = (newZoom = 1) => {
//   if (newZoom < 0.2) {
//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
//   }
//   if (newZoom > 4) {
//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
//   }
//   defaultZoom = newZoom
//   bpmnModeler.get('canvas').zoom(newZoom)
// }
const processReZoom = () => {
    defaultZoom.value = 1
    bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
}
const processRestart = () => {
    recoverable.value = false
    revocable.value = false
    createNewDiagram(null)
}
const elementsAlign = align => {
    const Align = bpmnModeler.get('alignElements')
    const Selection = bpmnModeler.get('selection')
    const SelectedElements = Selection.get()
    if (!SelectedElements || SelectedElements.length <= 1) {
        ElMessage.warning('请按住 Shift 键选择多个元素对齐')
        // alert('请按住 Ctrl 键选择多个元素对齐
        return
    }
    ElMessageBox.confirm('自动对齐可能造成图形变形，是否继续？', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        Align.trigger(SelectedElements, align)
    })
}
// 保存
const preservation = () => {
    previewModelVisible.value = true
}
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
const processSave = async () => {
    ruleFormRef.value.validate(async valid => {
        if (valid) {
            const { err, xml } = await bpmnModeler.saveXML()

            // 读取异常时抛出异常
            if (err) {
                // this.$modal.msgError('保存模型失败，请重试！')
                alert('保存模型失败，请重试！')
                return
            }
            // 触发 save 事件
            emit('save', xml, form.value)
        }
    })
}
/** 高亮显示 */
// const highlightedCode = (previewType, previewResult) => {
//   console.log(previewType, 'previewType, previewResult')
//   console.log(previewResult, 'previewType, previewResult')
//   console.log(hljs.highlight, 'hljs.highlight')
//   const result = hljs.highlight(previewType, previewResult.value || '', true)
//   return result.value || '&nbsp;'
// }
onBeforeMount(() => {

})
onMounted(() => {
    initBpmnModeler()
    createNewDiagram(props.value)
})
onBeforeUnmount(() => {
    // this.$once('hook:beforeDestroy', () => {
    // })
    if (bpmnModeler) bpmnModeler.destroy()
    emit('destroy', bpmnModeler)
    bpmnModeler = null
})
</script>
<style lang="scss" scoped>
.quit {
    position: absolute;
    top: 5px;
    right: 10px;
}
</style>
