<template>
  <div style="width: 100%">
    <template v-for="(page,pageIndex) in pageConfig.page">
      <!--   拖拽时显示的临时容器   -->
      <template v-if="showDragSpan">
        <div style="display: flex" :key="pageIndex">
          <template v-for="item in dragConfig">
            <div class="container" style="font-size: 1.5em" :key="item.id" :style="'width:' + item.width + 'px'">
              宽度：{{ item.span }}
            </div>
          </template>
        </div>
      </template>
      <!--   渲染器主体   -->
      <template v-for="(row,rowIndex) in page.body">
        <a-row
          :key="pageIndex + '_' + rowIndex"
          type="flex"
          :justify="row.config.justify"
          :gutter="row.config.gutter"
          :align="row.config.align">
          <template v-if="!showDragSpan">
            <template v-for="(item, itemIndex) in row.body">
              <a-col :span="item.span" :key="item.id">
                <!--   显示容器   -->
                <div
                  v-if="item.type === 'container' && editMode"
                  style="display: flex;align-items: center"
                  @click="clickComponentInEditor(item)">
                  <div :class="item.selected ? 'selected_container' : 'container'">
                    <div>
                      <p style="font-size: 1.5em">宽度：{{ item.span }}</p>
                    </div>
                    <div>
                      <a-button @click.stop="handelContainerDelete(item.id, pageIndex)">删除</a-button>
                    </div>
                  </div>
                  <div
                    v-if="itemIndex !== row.body.length - 1"
                    style="width: 6px;font-size: 1em;color: rgb( 24,144,255)"
                    class="dragDiv"
                    @mousedown="dragStart($event, item.id, row)">
                    || <br> ||
                  </div>
                </div>
                <!--   显示Tab容器   -->
                <div
                  v-else-if="item.type === 'tabs'">
                  <a-tabs>
                    <template v-for="(tab) in item.body">
                      <a-tab-pane :key="tab.key" :tab="tab.label">
                        <LowCodeRender
                          ref="sub"
                          :is-root="false"
                          @showDraw="handelSubShowDraw"
                          @showModal="handelSubShowModal"
                          v-if="renderSub"
                          @spanDrag="handleDrag"
                          @containerDelete="handleContainerDelete"
                          @clickComponentInEditor="clickComponentInner"
                          :page-config="tab.body"
                          :edit-mode="editMode"/>
                      </a-tab-pane>
                    </template>
                  </a-tabs>
                </div>
                <!--   递归显示页面容器   -->
                <div
                  v-else-if="item.type === 'container_page'"
                  style="display: flex;align-items: center">
                  <LowCodeRender
                    ref="sub"
                    :is-root="false"
                    @showDraw="handelSubShowDraw"
                    @showModal="handelSubShowModal"
                    v-if="renderSub"
                    @spanDrag="handleDrag"
                    @containerDelete="handleContainerDelete"
                    @clickComponentInEditor="clickComponentInner"
                    :page-config="item"
                    :edit-mode="editMode"/>
                  <div
                    v-if="itemIndex !== row.body.length - 1"
                    style="width: 6px;font-size: 1em;color: rgb( 24,144,255);"
                    class="dragDiv"
                    @mousedown="dragStart($event, item.id, row)">
                    || <br> ||
                  </div>
                </div>
                <!--   页面渲染 / 编辑模式中，将弹框或抽屉中的内容渲染为页面   -->
                <div :ref="item.id" :id="item.id" v-else-if="editMode || (page.type !== 'modal' && page.type !== 'draw')">
                  <div
                    @click="clickComponentInEditor(item)"
                    :class=" editMode ? 'componentInEditor' : '' ">
                    <component
                      v-if="checkComponentRequiredProps(item.type, item.props)"
                      :lifeCycleFunction="resolveLifeCycleEvent(item)"
                      :is="resolveComponentType(item.type, item.id)"
                      :propsData="{...item.props}"
                      @componentDidMounted="componentDidMounted"
                      @onEvent="handleEvent"
                      :mixinComponentId="item.id"/>
                    <div v-else class="propsNotFixedComponent">
                      <p style="line-height: 200px; text-align: center; font-size: 2em; margin: 0">{{ item.type }}</p>
                      <p style="text-align: center; font-size: 1.2em">必填参数未设置完全！暂停渲染</p>
                    </div>
                  </div>
                </div>
              </a-col>
            </template>
          </template>
        </a-row>
      </template>
    </template>
    <!--   弹框渲染   -->
    <template v-for="(modal, modalIndex) in pageConfig.page.filter(page => page.type === 'modal')">
      <a-modal
        :key="'modalIndex_' + modalIndex"
        :title="modal.title"
        :width="modal.width"
        :dialog-style="{ top: '5rem' }"
        :visible="modalVisible[modal.id]"
        :z-index="1003"
        :maskClosable="false"
        @cancel="closeModalOrDraw(modal.id, 'modal')"
        :destroyOnClose="true">
        <template v-for="(row, rowIndex) in modal.body">
          <template v-for="(item, itemIndex) in row.body">
            <div v-if="item.type === 'tabs'" :key="'modalItem_' + rowIndex + '_' + itemIndex">
              <h1>tabssssss</h1>
            </div>
            <div v-else :key="'modalItem_' + rowIndex + '_' + itemIndex">
              <component
                v-if="checkComponentRequiredProps(item.type, item.props)"
                :lifeCycleFunction="resolveLifeCycleEvent(item)"
                :is="resolveComponentType(item.type)"
                :propsData="{...item.props, showModalOrDraw: false}"
                @componentDidMounted="componentDidMounted"
                @onEvent="handleEvent"
                :mixinComponentId="item.id"/>
              <div v-else class="propsNotFixedComponent">
                <p style="line-height: 200px; text-align: center; font-size: 2em; margin: 0">{{ item.type }}</p>
                <p style="text-align: center; font-size: 1.2em">必填参数未设置完全！暂停渲染</p>
              </div>
            </div>
          </template>
        </template>
      </a-modal>
    </template>
    <!--   抽屉渲染   -->
    <template v-for="(draw, drawIndex) in pageConfig.page.filter(page => page.type === 'draw')">
      <a-drawer
        :key="'drawIndex_' + drawIndex"
        :title="draw.title"
        :width="draw.width"
        :visible="drawVisible[draw.id]"
        :z-index="1003"
        @close="closeModalOrDraw(draw.id, 'draw')"
        :destroyOnClose="true">
        <template v-for="(row, rowIndex) in draw.body">
          <template v-for="(item, itemIndex) in row.body">
            <div v-if="item.type === 'tabs'" :key="'drawItem_' + rowIndex + '_' + itemIndex">
              <h1>tabssssss</h1>
            </div>
            <div v-else :key="'drawItem_' + rowIndex + '_' + itemIndex">
              <component
                v-if="checkComponentRequiredProps(item.type, item.props)"
                :lifeCycleFunction="resolveLifeCycleEvent(item)"
                :is="resolveComponentType(item.type)"
                :propsData="{...item.props, showModalOrDraw: false}"
                @componentDidMounted="componentDidMounted"
                @onEvent="handleEvent"
                :mixinComponentId="item.id"/>
              <div v-else class="propsNotFixedComponent">
                <p style="line-height: 200px; text-align: center; font-size: 2em; margin: 0">{{ item.type }}</p>
                <p style="text-align: center; font-size: 1.2em">必填参数未设置完全！暂停渲染</p>
              </div>
            </div>
          </template>
        </template>
      </a-drawer>
    </template>
  </div>
</template>

<script>
// eslint-disable-next-line no-unused-vars
import Vue from 'vue'
import * as allComponents from '@vue2-client/utils/lowcode/registerComponentForRender'
import lowcodeLog from '@vue2-client/utils/lowcode/lowcodeLog'
import * as lowcodeUtils from '@vue2-client/utils/lowcode/lowcodeUtils'
import { runLogic } from '@vue2-client/services/api/common'

export default {
  name: 'LowCodeRender',
  props: {
    // 页面配置
    pageConfig: {
      type: Object,
      required: true
    },
    // 是否为编辑模式
    editMode: {
      type: Boolean,
      default: true
    },
    // 用于标识渲染器是否为顶级渲染器
    // 非顶级渲染器在控制弹框、抽屉显隐的时候需要emit而不是修改值
    isRoot: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      // 传递给组件的Props合集
      propsData: {},
      // 是否为拖拽模式
      showDragSpan: false,
      // 拖拽的缓存
      dragConfig: [],
      // 记录拖拽位移单位量
      beforeSpanThrough: -1,
      // 记录拖拽之前的span
      originalSpan: -1,
      // 记录拖拽目标下一个元素的原始span
      nextOriginalSpan: -1,
      // 弹框显示集合
      modalVisible: {},
      // 抽屉显示集合
      drawVisible: {},
      // 控制递归渲染
      renderSub: true,
      // 用于控制组件外框闪烁
      colClassName: '',
      // 控制容器渲染，用于递归时，刷新递归中容器的状态
      showContainer: true,
      // 控制闪烁的定时器
      twinkTimer: undefined,
      // 缓存正在闪烁的组件，用于还原状态
      twinkBefore: undefined
    }
  },
  inject: [
    'componentsMap'
  ],
  mounted () {
    // 初始化控制弹框和抽屉的变量合集
    this.pageConfig.page.forEach(page => {
      if (page.type === 'modal') {
        this.modalVisible[page.id] = false
      } else if (page.type === 'draw') {
        this.drawVisible[page.id] = false
      }
    })
  },
  methods: {
    // 检查组件必要的props是否全部满足
    checkComponentRequiredProps (type, props) {
      if (type === 'container') {
        return true
      }
      const propertiesDef = lowcodeUtils.getEditorConfigByType(type).properties
      const defKeys = Object.keys(propertiesDef)
      const requiredProps = []
      defKeys.forEach(key => {
        if (propertiesDef[key].required) {
          requiredProps.push(key)
        }
      })
      for (let i = 0; i < requiredProps.length; i++) {
        const eachRequired = requiredProps[i]
        if (props[eachRequired] === undefined || props[eachRequired] === '' || props[eachRequired] === null) {
          return false
        }
      }
      return true
    },
    // 用于控制递归渲染器中的修改抽屉显隐
    handelSubShowDraw (id, visible) {
      this.drawVisible[id] = visible === 'open'
      this.$forceUpdate()
      lowcodeLog(
        id,
        '修改抽屉可见性',
        visible === 'open' ? '可见' : '隐藏',
        false,
        true
      )
    },
    // 用于控制递归渲染器中的修改弹框显隐
    handelSubShowModal (id, visible) {
      this.modalVisible[id] = visible === 'open'
      this.$forceUpdate()
      lowcodeLog(
        id,
        '修改弹窗可见性',
        visible === 'open' ? '可见' : '隐藏',
        false,
        true
      )
    },
    // 将钩子函数格式化成mixin的格式并传入
    resolveLifeCycleEvent (config) {
      if (!config.onEvent || !config.onEvent.lifeCycle) {
        return undefined
      } else {
        const result = []
        const lifeCycle = config.onEvent.lifeCycle
        lifeCycle.forEach(item => {
          result.push({
            lifeCycleName: item.lifeCycle,
            functionStr: item.LifeCycleFunction
          })
        })
        return result
      }
    },
    // 关闭弹窗或抽屉
    closeModalOrDraw (id, type) {
      if (type === 'modal') {
        this.modalVisible[id] = false
      } else if (type === 'draw') {
        this.drawVisible[id] = false
      }
      this.$forceUpdate()
    },
    // 递归拖拽完成后的回调
    handleDrag (targetEle, nextEle) {
      this.$emit('spanDrag', targetEle, nextEle)
    },
    // 递归容器删除后的回调
    handleContainerDelete (id) {
      this.$emit('containerDelete', id)
    },
    // 刷新递归的渲染器
    refreshSubRender () {
      this.renderSub = false
      this.$nextTick(() => {
        this.renderSub = true
      })
    },
    // 刷新递归容器
    refreshSubContainer () {
      this.showContainer = false
      this.$nextTick(() => {
        this.showContainer = true
      })
    },
    // 递归点击组件后的回调
    clickComponentInner (configItem) {
      this.$emit('clickComponentInEditor', configItem, this)
    },
    // 拖拽改变Span
    dragStart (event, id, row) {
      // 获取目标html元素
      const targetElement = event.srcElement
      // 获取外层元素用于计算总宽度
      const outerElement = targetElement.parentNode.parentNode.parentNode
      const outerWidth = outerElement.offsetWidth
      // 计算每一个span对应的像素
      const outerSpanWidth = outerWidth / 24

      // 记录初始X
      const startX = event.clientX

      // 记录兄弟元素
      const targetElementBros = outerElement.childNodes

      let targetComponent
      let targetNextComponent

      // 缓存拖拽组件，用于显示拖拽时的实时Span，拖拽完成后再统一赋值
      this.dragConfig = []
      for (let i = 0; i < row.body.length; i++) {
        this.dragConfig.push({
          id: row.body[i].id,
          span: row.body[i].span,
          width: targetElementBros[i].offsetWidth
        })
      }

      this.showDragSpan = true

      // 保存目标容器，和目标的下一个容器，用于改变宽度
      for (let i = 0; i < this.dragConfig.length; i++) {
        const item = this.dragConfig[i]
        if (item.id === id) {
          targetComponent = this.dragConfig[i]
          targetNextComponent = this.dragConfig[i + 1]
        }
      }

      // 如果鼠标之后没有组件，禁止移动
      if (!targetComponent || !targetNextComponent) {
        this.$message.error('单个组件无法移动！')
        this.showDragSpan = false
        return
      }

      // 保存原始的像素宽度
      const orignalTargetWidth = targetComponent.width
      const orignalTargetNextWidth = targetNextComponent.width

      // 保存原始的span
      this.originalSpan = targetComponent.span
      this.nextOriginalSpan = targetNextComponent.span

      // 拖拽事件
      const onDrag = (e) => {
        // 计算出移动的像素数
        let deltaX = e.clientX - startX
        // 判断移动方向
        let direction = 1
        if (deltaX < 0) {
          direction = -1
          deltaX = 0 - deltaX
        }

        // 根据移动像素数计算出移动了多少个span
        const throughSpanNum = Math.floor(deltaX / outerSpanWidth)
        if (this.beforeSpanThrough === -1) {
          this.beforeSpanThrough = throughSpanNum
        }

        // 将移动的距离更新到临时显示的span中
        if (direction > 0) {
          targetComponent.width = orignalTargetWidth + deltaX
          targetNextComponent.width = orignalTargetNextWidth - deltaX
          if (this.beforeSpanThrough !== throughSpanNum) {
            this.beforeSpanThrough = throughSpanNum
            targetComponent.span = this.originalSpan + throughSpanNum
            targetNextComponent.span = this.nextOriginalSpan - throughSpanNum
          }
        } else {
          targetComponent.width = orignalTargetWidth - deltaX
          targetNextComponent.width = orignalTargetNextWidth + deltaX
          if (this.beforeSpanThrough !== throughSpanNum) {
            this.beforeSpanThrough = throughSpanNum
            targetComponent.span = this.originalSpan - throughSpanNum
            targetNextComponent.span = this.nextOriginalSpan + throughSpanNum
          }
        }
      }

      // 拖拽结束
      const dragEnd = (e) => {
        // 解绑事件，更新数据
        this.showDragSpan = false
        document.removeEventListener('mouseup', dragEnd)
        document.removeEventListener('mousemove', onDrag)
        this.$emit('spanDrag', targetComponent, targetNextComponent)
      }

      // 绑定拖拽事件和结束事件
      document.addEventListener('mouseup', dragEnd)
      document.addEventListener('mousemove', onDrag)
    },
    // 解析配置中的type，找出对应的渲染器
    resolveComponentType (type, componentId) {
      // 从组件注册中拿到所有注册的组件
      const allComponentKeys = Object.keys(allComponents)
      for (let i = 0; i < allComponentKeys.length; i++) {
        const keyStr = allComponentKeys[i].toString()
        if (type === keyStr) {
          return allComponents[keyStr]
        }
      }
    },
    // mixin中组件mounted后的回调
    componentDidMounted (vm, id) {
      // 将组件注册到全局的Map中
      if (vm && id) {
        this.componentsMap[id] = vm
      }
      // 如果组件中有props，将其挨个set到vm中
      const config = lowcodeUtils.getComponentConfig(id, this.pageConfig)
      const keys = Object.keys(config.props)
      keys.forEach(key => {
        if (config.props[key] !== undefined && config.props[key] !== '') {
          Vue.set(vm, key, config.props[key])
        }
      })
    },
    // 点击组件
    clickComponentInEditor (configItem) {
      this.$emit('clickComponentInEditor', configItem, this)
    },
    // 删除容器
    handelContainerDelete (id, pageIndex) {
      this.$emit('containerDelete', id, pageIndex)
    },
    // 处理组件的事件
    handleEvent (data, eventTiggerType, id) {
      // 找到目标对象的配置
      const targetConfig = lowcodeUtils.getComponentConfig(id, this.pageConfig.page)
      // 找到目标对象对应触发事件的配置
      const eventConfigs = targetConfig.onEvent[eventTiggerType]

      if (!eventConfigs) {
        return
      }

      const vm = this.componentsMap[id]
      lowcodeLog(
        ` 事件数据：${data} \n 源组件: ${id}`,
        '触发事件',
        eventTiggerType,
        true
      )

      let targetComponentId = ''

      // 遍历该类型触发事件，挨个执行
      eventConfigs.forEach(eachEvent => {
        lowcodeLog(
          ` 事件数据：${data} \n 目标组件: ${targetComponentId}`,
          '开始遍历执行事件',
          eachEvent.eventType,
          false
        )
        // 修改弹窗显隐
        if (eachEvent.eventType === 'showDraw') {
          // 如果不是顶级渲染器，则向外抛出事件
          if (!this.isRoot) {
            this.$emit('showDraw', eachEvent.containerId, eachEvent.visible)
          } else {
            this.drawVisible[eachEvent.containerId] = eachEvent.visible === 'open'
            this.$forceUpdate()
            lowcodeLog(
              eachEvent.containerId,
              '修改抽屉可见性',
              eachEvent.visible === 'open' ? '可见' : '隐藏',
              false,
              true
            )
          }
        } else if (eachEvent.eventType === 'showModal') {
          // 修改抽屉显隐
          // 如果不是顶级渲染器，则向外抛出事件
          if (!this.isRoot) {
            this.$emit('showModal', eachEvent.containerId, eachEvent.visible)
          } else {
            this.modalVisible[eachEvent.containerId] = eachEvent.visible === 'open'
            this.$forceUpdate()
            lowcodeLog(
              eachEvent.containerId,
              '修改弹窗可见性',
              eachEvent.visible === 'open' ? '可见' : '隐藏',
              false,
              true
            )
          }
        } else if (eachEvent.eventType === 'customJSFunction') {
          const targetVM = this.componentsMap[eachEvent.target]
          // eslint-disable-next-line no-eval
          const customFunction = eval('(' + eachEvent.JSFunction + ')')
          customFunction(eachEvent.eventType, data, targetVM, vm)
          lowcodeLog(
            undefined,
            '执行自定义函数完成',
            undefined,
            false,
            true
          )
        } else if (eachEvent.eventType === 'runLogic') {
          // 运行Logic
          // eslint-disable-next-line no-eval
          const logicCallBackFunction = eval('(' + eachEvent.logicCallBackFunction + ')')
          // eslint-disable-next-line no-eval
          const logicParamFunction = eval('(' + eachEvent.logicParamFunction + ')')
          // 将需要的组件变量组织好，并传递给函数
          const componentData = {}
          const allComponents = Object.keys(this.componentsMap)
          allComponents.forEach(component => {
            componentData[component] = this.componentsMap[component].dataForLowCode
          })
          const param = logicParamFunction(componentData, data)
          lowcodeLog(
            JSON.stringify(param),
            '执行Logic',
            eachEvent.logicName,
            false,
            true
          )
          runLogic(eachEvent.logicName, param, eachEvent.serviceName)
            .then(res => { logicCallBackFunction(this.componentsMap, res) })
        } else if (eachEvent.eventType === 'changeProps') {
          // 修改组件props
          const targetConfig = lowcodeUtils.getComponentConfig(eachEvent.target, this.pageConfig.page)
          targetConfig.props[eachEvent.targetKey] = data
          lowcodeLog(
            `目标key：${eachEvent.targetKey}，新的值：${JSON.stringify(data)}`,
            '修改Props',
            targetConfig.id,
            false,
            true
          )
        } else if (eachEvent.eventType === 'log') {
          // 打印日志
          lowcodeLog(
            JSON.stringify(data),
            '打印日志',
            targetConfig.id,
            false,
            true
          )
        } else if (eachEvent.eventType === 'runFunction') {
          // 修改组件的值
          // 寻找target
          targetComponentId = eachEvent.target
          const targetVM = this.componentsMap[targetComponentId]
          // 调用函数获取参数
          // eslint-disable-next-line no-eval
          const paramFunction = eval('(' + eachEvent.runFunctionParam + ')')
          const params = paramFunction(vm, data)
          // 执行
          targetVM[eachEvent.eventType](...params)
          lowcodeLog(
            JSON.stringify(data),
            '执行目标方法完成',
            eachEvent.eventType,
            false,
            true
          )
        }
      })
    },
    // 组件闪烁
    twink (id) {
      // 如果组件不在本层渲染器，则向下递归
      if (this.$refs[id] === undefined || this.$refs[id].length === 0) {
        if (this.$refs.sub) {
          this.$refs.sub[0].twink(id)
        }
      } else {
        const _this = this
        // 将之前闪烁的组件还原，避免在classname赋值以后打断动画，样式被保留
        if (this.twinkBefore) {
          this.twinkBefore.className = ''
        }
        this.twinkBefore = this.$refs[id][0]
        // 如果存在定时器则将之前的动画打断
        if (!this.twinkTimer) {
          this.twinkTimer = []
        } else {
          this.twinkTimer.forEach(timer => {
            clearTimeout(timer)
          })
        }
        // 定义关键帧间隔
        const frame = 400
        this.$refs[id][0].className = 'colWithBorder'
        for (let i = 1; i <= 3; i++) {
          const timer = setTimeout(() => {
            if (i % 2 !== 0) {
              _this.$refs[id][0].className = ''
            } else {
              _this.$refs[id][0].className = 'colWithBorder'
            }
          }, frame * i)
          this.twinkTimer.push(timer)
        }
      }
    }
  },
  watch: {
    pageConfig: {
      handler (newVal) {
      },
      deep: true
    }
  }
}
</script>

<style scoped>
.componentInEditor{
  border: none;
}
.componentInEditor:hover{
  border: 2px rgb( 24,144,255) solid;
  box-shadow: 3px 3px 5px 1px rgba(0,0,0,0.2);
}
.colWithBorder{
  border: 2px rgb( 24,144,255) solid;
  box-shadow: 3px 3px 5px 1px rgba(0,0,0,0.2);
}
.container{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  width: calc(100% - 6px);
  height: 50vh;
  border: rgba( 24,144,255, 0.2) 2px solid;
  background-color: rgba(24, 144, 255, 0.05);
}
.selected_container{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  width: calc(100% - 6px);
  height: 50vh;
  border: rgba( 24,144,255, 0.2) 2px solid;
  background-color: rgba( 24,144,255, 0.2);
}
.colSpanSuit{
  color: green;
  font-size: 1.5em;
}
.colSpanNotSuit{
  color: red;
  font-size: 1.5em;
}
.dragDiv:hover{
  cursor: w-resize;
  z-index: 99999;
}

.propsNotFixedComponent {
  background-color: rgba(24, 144, 255, 0.05);
  border: rgba( 24,144,255, 0.2) 2px dashed;
  width: 100%;
  height: 300px;
}
</style>
