<template>
  <div>
    <!--  骨架屏  -->
    <a-card v-if="showSkeleton">
      <a-skeleton active />
    </a-card>
    <!--   主体表格   -->
    <XReportDesign
      @selectRow="selectRow"
      @slotRendered="slotRendered"
      @allSlotsConfigEnded="onAllSlotsConfigEnded"
      v-if="scanFinish"
      :display-only="displayOnly"
      :config="originalConfig"
      :slot-config-name="undefined"
      :for-display="true"
      ref="XReportDesign"
      :server-name="serverName"
      :env="env"
      :show-title="showTitle"
      @listClick="listClick"
    >
    </XReportDesign>
    <a-row
      type="flex"
      justify="end"
      v-if="showSaveButton"
    >
      <a-space>
        <a-button @click="saveConfig">
          提交
        </a-button>
        <a-button @click="cancelConfig">
          取消
        </a-button>
      </a-space>
    </a-row>
    <!-- 弹出框 -->
    <x-add-report
      :env="env"
      ref="xAddReport"
    />
    <!-- 弹出框 -->
    <x-report-drawer
      :env="env"
      ref="xReportDrawer"
    />
  </div>
</template>
<script>
/* eslint-disable */
// 转PDF用
import { mapState } from 'vuex'
import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
import XReportDesign from './XReportDesign.vue'
import { executeStrFunctionByContext } from '@vue2-client/utils/runEvalFunction'
import { shortcutManager } from '@vue2-client/base-client/utils/shortcutManager'

// import XAddReport from '@vue2-client/base-client/components/common/XAddReport'

export default {
  name: 'XReport',
  props: {
    files: {
      type: Array,
      default: () => {
        return []
      },
    },
    // 控制用户权限，user和admin
    authority: {
      type: String,
      default: 'user',
    },
    // 是否为编辑模式
    editMode: {
      type: Boolean,
      default: true,
    },
    // 配置名
    configName: {
      type: String,
      required: true,
    },
    // 插槽名
    activatedSlotName: {
      type: String,
      default: undefined,
    },
    // 本地配置，调试用
    localConfig: {
      type: Object,
      default: undefined,
    },
    // 兼容老版本配置
    dontFormat: {
      type: Boolean,
      default: true,
    },
    showImgInCell: {
      type: Boolean,
      default: false,
    },
    // 数据
    configData: {
      type: Object,
      default: undefined,
    },
    // 命名空间
    serverName: {
      type: String,
      default: process.env.VUE_APP_SYSTEM_NAME,
    },
    // 环境
    env: {
      type: String,
      default: 'prod',
    },
    // 只做展示
    displayOnly: {
      type: Boolean,
      default: true,
    },
    // 表格没有边距
    noPadding: {
      type: Boolean,
      default: true,
    },
    // 表格没有上边框，与noPadding搭配可以实现连续表格
    noTopBorder: {
      type: Boolean,
      default: false,
    },
    // 是否展示标题
    showTitle: {
      type: Boolean,
      default: true,
    },
    // 是否展示保存按钮
    showSaveButton: {
      type: Boolean,
      default: false,
    },
    // 是否将组件注册到外层提供的容器中，方便外侧统一保存
    registerMap: {
      type: Array,
      default: undefined,
    },
    // TAB 页签时由 XTab 传入，仅接收不参与逻辑（注册由 XTab onComponentMounted 完成）
    slotRef: {
      type: String,
      default: undefined,
    },
    // 是否小插件模式，小插件模式不会有各种边境
    isWidget: {
      type: Boolean,
      default: false,
    },
    // 图片是否使用OSS来保存
    useOssForImg: {
      type: Boolean,
      default: true,
    },
    // 图片上传后添加前缀
    imgPrefix: {
      type: String,
      default: undefined,
    },
  },
  components: {
    XAddReport: () =>
      import('@vue2-client/base-client/components/common/XAddReport'),
    XReportDrawer: () =>
      import('@vue2-client/base-client/components/common/XReportDrawer'),
    XReportDesign,
  },
  data () {
    return {
      // 控制骨架屏显隐
      showSkeleton: true,
      // 配置
      config: undefined,
      // 当前显示模式，编辑模式，预览模式
      type: 'design',
      // 仅供展示，不可编辑
      onlyDisplay: false,
      // 每行最大列数，非必要请勿更改，现在的设计器完全是基于每行12列来设计的
      maxColSpan: 12,
      // 定义是否完成配置的扫描，未完成不要渲染子组件
      scanFinish: false,
      // 当前激活的配置文件
      activeConfig: null,
      // 原始配置文件
      // 用于展示。某些情况下“设计页”中的内容仅为“预览页”表格其中的一部分
      originalConfig: null,
      // 扫描到的配置
      configFromWeb: {},
      // 用于获取配置的锁
      timer: undefined,
      // 是否包含图片
      hasImages: false,
      // 图片列表
      imageList: [],
      // 保存最原始的数据，用于判断哪些数据被更改了
      dataCache: undefined,
      // 判断哪些数据被更改了，存储对应的key
      diff: [],
      globalData: {},
      // setData 在 config 未就绪时传入的 data，配置彻底结束后再执行
      // eslint-disable-next-line
      _pendingSetData: undefined,
      // 配置是否彻底结束：本报表配置就绪且所有 slot 子组件配置也结束（由 allSlotsConfigEnded 事件获知）
      configEnded: false,
      // 是否已收到 Design 的 allSlotsConfigEnded（所有 slot 子组件配置结束）
      allSlotsConfigEnded: false,
      // 本报表注册到全局组件注册表的名称（仅当配置了 globalRegistryName 时才有），destroy 时统一注销
      // eslint-disable-next-line
      _globalRegistrySlotNames: [],
      // 等待配置结束的 Promise，供 waitConfigEnd() 使用
      _configEndPromise: null,
      _configEndResolve: null,
    }
  },
  inject: {
    // 从祖先获取 getGridScopePath（可能是外层 XReport 或 XTab 提供的）
    upstreamGetGridScopePath: {
      from: 'getGridScopePath',
      default: null
    }
  },
  beforeDestroy () {
    clearInterval(this.timer)
    if (this.$componentRegistry && this._globalRegistrySlotNames.length) {
      this._globalRegistrySlotNames.forEach((name) => {
        this.$componentRegistry.unregister(name)
      })
      this._globalRegistrySlotNames = []
    }
  },
  watch: {
    // 如果配置名更改了，重新获取配置
    configName (val) {
      if (val) {
        // 先清空配置，避免异步期间使用旧配置注册快捷键
        this.originalConfig = null
        this.scanFinish = false
        getConfigByName(
          this.configName,
          undefined,
          (res) => {
            this.config = res
            this.configInit()
            this.$logger('report.configLoad').log('configName 加载完成', { configName: this.configName })
          },
          this.env === 'dev'
        )
      }
    },
    // 如果本地配置更改了，重新初始化
    localConfig: {
      deep: true,
      immediate: true,
      handler (val) {
        if (val) {
          this.config = val
          this.configInit()
          this.$logger('report.configLoad').log('localConfig 变更完成', { configName: this.configName })
        }
      },
    },
  },
  provide () {
    const report = this
    return {
      runLogic: runLogic,
      openDialog: report.openDialog.bind(report),
      openDrawer: report.openDrawer.bind(report),
      registerComponent: report.registerComponent.bind(report),
      getComponentByName: report.getComponentByName.bind(report),
      getParentComponentByName: report.getComponentByName.bind(report),
      getConfigByName: getConfigByName,
      isWidget: report.widget,
      currUser: report.currUser,
      getGlobalData: report.getGlobalData.bind(report),
      setGlobalData: report.setGlobalData.bind(report),
      findComponentByName: report.findComponentByName.bind(report),
      closeAddReport: report.closeAddReport.bind(report),
      setData: report.setData.bind(report),
      getRootReport: () => report,
      getGridScopePath: () => {
        // 1. 优先从注入的上游函数获取父路径
        let parent = ''
        if (typeof report.upstreamGetGridScopePath === 'function') {
          parent = report.upstreamGetGridScopePath()
        }
        // 2. 如果没有注入（根实例），回退到 prop（兼容旧写法）
        if (!parent && report.parentScopePath) {
          parent = report.parentScopePath
        }
        // 3. 拼上自己的 configName
        const current = report.configName
        return parent ? `${parent}/${current}` : current
      }
    }
  },
  methods: {
    findComponentByName (instance, componentName, maxDepth, findType) {
      // 基础判断：如果实例不存在或者已经超过最大深度，返回null
      if (!instance || maxDepth <= 0) {
        return null
      }

      // 检查当前实例是否有目标组件
      try {
        if (instance.getComponentByName && instance.getComponentByName(componentName)) {
          return instance.getComponentByName(componentName)
        }
      } catch (e) {
        this.$logger('report.findComponent').warn('查找组件时发生错误:', e)
      }

      // 递归查找子组件
      if (findType === 'parent') {
        // 向上查找父组件
        if (instance.$parent) {
          return this.findComponentByName(instance.$parent, componentName, maxDepth - 1, findType)
        }
      } else {
        if (instance.$children && instance.$children.length) {
          for (const child of instance.$children) {
            const result = this.findComponentByName(child, componentName, maxDepth - 1, findType)
            if (result) {
              return result
            }
          }
        }
      }
      return null
    },
    listClick (data) {
      this.$emit('listClick', data)
    },
    closeAddReport () {
      if (this.$refs.xAddReport) {
        this.$refs.xAddReport.close()
      }
      let parent = this.$parent
      while (parent) {
        if (parent.$refs && parent.$refs.xAddReport) {
          const addReportRefs = parent.$refs.xAddReport
          if (Array.isArray(addReportRefs)) {
            addReportRefs.forEach(ref => {
              if (ref && typeof ref.close === 'function') {
                ref.close()
              }
            })
          } else if (typeof addReportRefs.close === 'function') {
            addReportRefs.close()
          }
        }
        parent = parent.$parent
      }
    },
    slotRendered () {
      this.$logger('report.slotRendered').log('slotRendered 进入', { configName: this.configName, hasPendingSetData: this._pendingSetData !== undefined })
      if (this.config?.mountedFunction) {
        let func = this.config.mountedFunction
        if (func && func.startsWith('function')) {
          func = func.replace('function', 'async function')
          executeStrFunctionByContext(this, func, [this])
        }
      }
    },
    onAllSlotsConfigEnded () {
      this.allSlotsConfigEnded = true
      this._markConfigEnded()
    },
    // 把组件注册到refs中，方便调用；若传入 options.globalRegistryName 则同时注册到全局组件注册表（向后兼容：无该字段则不注册）
    registerComponent (componentName, component, options = {}) {
      this.$refs[componentName] = component
      const globalName = options.globalRegistryName
      if (globalName && this.$componentRegistry) {
        try {
          this.$componentRegistry.register(globalName, component)
          this._globalRegistrySlotNames.push(globalName)
        } catch (e) {
          this.$logger('report.registerComponent').warn('全局组件注册表注册失败:', e?.message || e)
        }
      }
      // 如果配置了 mountedLogicName，则运行该逻辑
      if (options.mountedLogicName) {
        this.runMountedLogic(component, options)
      }
    },

    // 运行组件挂载后的逻辑
    async runMountedLogic (component, options = {}) {
      const { mountedLogicName, mountedLogicMode, mountedLogicParam } = options
      if (!mountedLogicName) return
      try {
        const param = {
          proxy: component,
          ...(mountedLogicParam || {})
        }
        await this.$runFrontLogic(mountedLogicName, param, mountedLogicMode)
      } catch (e) {
        const cause = e?.cause || e
        if (cause?.name === 'AbortError' || cause?.name === 'CanceledError' || cause?.code === 'ERR_CANCELED') {
          return
        }
        this.$logger('report.runMountedLogic').warn('mountedLogicName 执行失败:', e?.message || e)
      }
    },

    // 把设计的table布局转换成可显示的珊格布局
    transformArray (inputList) {
      let operationIndex = 0
      let operationList = []
      const outputList = []
      for (const lst of inputList) {
        // 如果列表为空或只有一个元素，则所有元素相等。比较列表中每个元素是否与第一个元素相等
        if (
          lst.length >= 1 &&
          !lst.every(
            (x) =>
              Array.isArray(x) ||
              Array.isArray(lst[0]) ||
              x.rowSpan === lst[0].rowSpan
          )
        ) {
          operationList = lst
          break // 使用 break 退出整个循环
        } else {
          // 被操作的行
          operationIndex += 1
        }
      }

      let maxMergeRow = 0

      // 没有需要合并的行,直接返回
      if (operationList.length === 0) {
        return inputList
      } else {
        // 当前行的最大值
        const maxRow = Math.max(...operationList.map((item) => item.rowSpan))
        let mergeIndexCol = 0
        for (let index = 0; index < operationList.length; index++) {
          const row = operationList[index]
          let rowSpan = row.rowSpan
          // 需要合并的行
          let mergeIndexRow = operationIndex + 1
          // 存放合并后的行
          const rows = []
          // 添加当前行
          if (rowSpan < maxRow && mergeIndexRow < inputList.length) {
            rows.push([row])
          }
          // 当前需要被操作并且操作行没有超出列表的高度
          while (rowSpan < maxRow && mergeIndexRow < inputList.length) {
            rowSpan += inputList[mergeIndexRow][mergeIndexCol].rowSpan
            // 放一行到合并结果
            rows.push([inputList[mergeIndexRow][mergeIndexCol]])
            if (mergeIndexRow > maxMergeRow) {
              // 记录最多操作到了哪行
              maxMergeRow = mergeIndexRow
            }
            mergeIndexRow++
          }
          // operation_list赋值, 没有变化的，不处理
          if (rows.length !== 0) {
            operationList[index] = rows
          }
          if (row.rowSpan !== maxRow) {
            mergeIndexCol++ // 操作列转为下一列
          }
        }
      }

      // 组成outputlist, operation_list前部填入
      let putindex = 0
      while (operationIndex > 0) {
        outputList.push(inputList[putindex])
        putindex++
        operationIndex -= 1
      }

      outputList.push(operationList)

      // 组成outputlist, operation_list后部填入
      while (maxMergeRow < inputList.length - 1) {
        outputList.push(inputList[maxMergeRow + 1])
        maxMergeRow += 1
      }

      return this.transformArray(outputList)
    },
    // 根据名字从注册到组件中获取组件
    getComponentByName (componentName) {
      return this.$refs[componentName]
    },
    getGlobalData () {
      return this.globalData
    },
    setGlobalData (obj) {
      this.globalData = obj
    },
    /**
     * 配置彻底结束：本报表配置就绪且所有 slot 子组件配置也结束。打标、对外发 configEnd，再执行缓存的 setData。
     * 由 onAllSlotsConfigEnded（有 slot 且收到 allSlotsConfigEnded）或 configInit 的 nextTick（无 slot）调用，仅执行一次。
     */
    _markConfigEnded () {
      if (this.configEnded) return
      const hasSlots = (this.config?.slotsDeclare?.length || 0) > 0
      if (hasSlots && !this.allSlotsConfigEnded) return
      this.configEnded = true
      this.$logger('report.configEnd').log('配置彻底结束，发送 configEnd', { configName: this.configName })
      this.$emit('configEnd')
      if (this._configEndResolve) {
        this._configEndResolve()
        this._configEndResolve = null
      }
      this.$nextTick(() => this._flushPendingSetData())
    },
    /**
     * 等待配置结束的异步方法。配置已结束则立即 resolve，否则在 configEnd 时 resolve。
     * @returns {Promise<void>}
     */
    waitConfigEnd () {
      if (this.configEnded) return Promise.resolve()
      return this._configEndPromise || Promise.resolve()
    },
    /**
     * 内部：直接执行 dataProcessScript。供 setData（configEnded 时）和 _flushPendingSetData 调用。
     */
    async _runDataProcessScript (data) {
      const script = this.config?.dataProcessScript
      if (!script || typeof script !== 'string') return
      try {
        const result = executeStrFunctionByContext(this, script, [data])
        if (result instanceof Promise) await result
      } catch (e) {
        this.$logger('report.dataProcessScript').error('dataProcessScript 执行异常:', e)
      }
    },
    /**
     * 对外数据设置：仅当配置中存在 dataProcessScript 时执行该脚本，传入 data；不修改组件自身任何数据。
     * 无脚本时缓存 data 并 return。有脚本但配置未彻底结束（configEnded 为 false）时也缓存，等 configEnd 后再执行。
     * 只有配置彻底结束后才执行 dataProcessScript。脚本内 this 为当前 XReport 实例。
     * @param {*} data 外部传入的数据，供脚本使用
     * @returns {Promise<void>}
     */
    async setData (data) {
      this.$logger('report.setData').log('setData 进入', { configName: this.configName, configEnded: this.configEnded, hasData: data !== undefined, dataKeys: data && typeof data === 'object' ? Object.keys(data) : [] })
      const script = this.config?.dataProcessScript
      if (!script || typeof script !== 'string') {
        this._pendingSetData = data
        this.$logger('report.setData').warn('分支: 无脚本，已缓存 data', { hasConfig: !!this.config, hasScript: !!(this.config && this.config.dataProcessScript), configName: this.configName })
        return
      }
      if (!this.configEnded) {
        this._pendingSetData = data
        this.$logger('report.setData').log('分支: 配置未结束，已缓存 data，等 configEnd 后执行', { configName: this.configName })
        return
      }
      this._pendingSetData = undefined
      this.$logger('report.setData').log('分支: 配置已结束，立即执行 dataProcessScript', { configName: this.configName })
      await this._runDataProcessScript(data)
    },
    /**
     * 执行缓存的 setData：取 _pendingSetData 并直接执行脚本，不经过 setData，避免 setData 再次判断 defer 导致脚本不执行。
     */
    _flushPendingSetData () {
      const hasPending = this._pendingSetData !== undefined
      this.$logger('report.flushPendingSetData').log('_flushPendingSetData 进入', { configName: this.configName, hasPending })
      if (!hasPending) return
      const d = this._pendingSetData
      this._pendingSetData = undefined
      this.$logger('report.flushPendingSetData').log('_flushPendingSetData 直接执行 dataProcessScript', { configName: this.configName })
      this._runDataProcessScript(d)
    },
    /**
     * @param configName 栅格配置名称
     * @param selectedId 选中得id
     * @param mixinData  需要混入得数据
     * @param outEnv 其他传递给打开窗口的数据
     * @param attr 传递给Modal弹框用的信息
     * @param manageScope 管理快捷键作用域
     */
    openDialog(configName, selectedId, mixinData, outEnv = {}, attr = {}, showButtons = true, manageScope = false) {
      if (manageScope) {
        this.openDialogWithScope(configName, selectedId, mixinData, outEnv, attr, showButtons)
        return
      }

      this.$refs.xAddReport.init({
        configName: configName,
        selectedId: selectedId,
        mixinData: mixinData,
        outEnv: outEnv,
        attr,
        showButtons: showButtons
      })
    },
    // 打开弹窗（带快捷键作用域管理）
    openDialogWithScope(configName, selectedId, mixinData, outEnv = {}, attr = {}, showButtons = true) {
      const savedScopes = new Map()
      if (shortcutManager.isEnabled) {
        shortcutManager.scopeActive.forEach((active, scopeId) => {
          savedScopes.set(scopeId, active)
          shortcutManager.setScopeActive(scopeId, false)
        })
      }

      const xAddReport = this.$refs.xAddReport
      if (xAddReport && !xAddReport._shortcutPatched) {
        xAddReport._shortcutPatched = true
        const originalClose = xAddReport.close.bind(xAddReport)
        xAddReport.close = () => {
          if (shortcutManager.isEnabled && savedScopes.size > 0) {
            savedScopes.forEach((wasActive, scopeId) => {
              if (wasActive) shortcutManager.setScopeActive(scopeId, true)
            })
          }
          originalClose()
        }
      }

      xAddReport.init({ configName, selectedId, mixinData, outEnv, attr, showButtons })
    },

    openDrawer (configName, selectedId, mixinData, outEnv = {}, attr = {}) {
      this.$refs.xReportDrawer.init({
        configName,
        selectedId,
        mixinData,
        outEnv,
        attr,
      })
    },
    // 向外暴露图片修改后的数据，某些外部需要自己管理图片的保存与修改
    updateImg (data) {
      this.$emit('updateImg', data)
    },
    // 导出数据，某些外部需要统一控制数据的变动
    exportData () {
      // 获取当前修改后的数据
      let tempData
      if (this.activeConfig === undefined || this.activeConfig === null) {
        tempData = this.originalConfig.data
      } else {
        const tempDataKeys = Object.keys(this.activeConfig.tempData)
        tempDataKeys.forEach((key) => {
          this.changeDeepObject(
            this.activeConfig.data,
            key,
            this.activeConfig.tempData[key]
          )
        })
        tempData = this.activeConfig.data
      }
      // 对比数据的差异
      this.diff = []
      this.compareProps(tempData, this.dataCache)
      this.diff.forEach((eachDiff) => {
        const arr = eachDiff.split('.')
        let targetData = tempData[arr[0]]
        if (arr.length !== 1) {
          for (let i = 1; i < arr.length - 1; i++) {
            const path = arr[i]
            targetData = targetData[path]
          }
        }
        // 将修改的数据，添加update = true属性
        targetData.update = true
      })
      return tempData
    },
    // 对比两个obj有哪里不同
    compareProps (obj1, obj2, path = '') {
      for (const key in obj1) {
        // 如果一个是undefined
        if (typeof obj2[key] === 'undefined') {
          this.diff.push(path + key)
          // 如果是数组长度不一样
        } else if (Array.isArray(obj1) && Array.isArray(obj2)) {
          if (obj1[key].length !== obj2[key].length) {
            this.diff.push(path + key)
          }
          // 如果都是对象，并且存在同样的key，递归子key
        } else if (
          typeof obj1[key] === 'object' &&
          typeof obj2[key] === 'object'
        ) {
          this.compareProps(obj1[key], obj2[key], path + key + '.')
          // 如果不是obj，对比其数据
        } else if (obj1[key] !== obj2[key]) {
          this.diff.push(path + key)
        }
      }
    },
    selectRow (selectedRowKeys, selectedRows) {
      this.table_selectedRowKeys = selectedRowKeys
      this.table_selectedRows = selectedRows
      this.$emit('selectRow', selectedRowKeys, selectedRows)
    },
    // 注册组件到$refs中；若传入 options.globalRegistryName 则同时注册到全局组件注册表（向后兼容：无该字段则不注册）
    registerComponentToRefs (componentName, component, options = {}) {
      this.$refs[componentName] = component
      const globalName = options.globalRegistryName
      if (globalName && this.$componentRegistry) {
        try {
          this.$componentRegistry.register(globalName, component)
          this._globalRegistrySlotNames.push(globalName)
        } catch (e) {
          this.$logger('report.registerComponentToRefs').warn('全局组件注册表注册失败:', e?.message || e)
        }
      }
      // 如果配置了 mountedLogicName，则运行该逻辑
      if (options.mountedLogicName) {
        this.runMountedLogic(component, options)
      }
    },

    // 提交处理，调用配置中的函数
    saveConfig () {
      const funcStr = this.config.confirmFunction
      executeStrFunctionByContext(this, funcStr, [this])
    },

    // 取消处理
    cancelConfig () {
      this.$emit('cancel')
    },

    // 通过@@@分割临时变量，找到对应的key，并修改它的值
    changeDeepObject (obj, strPath, newVal) {
      const arr = strPath.split('@@@')
      if (obj[arr[0]] === undefined) {
        obj = obj.images
      }
      if (arr.length === 1) {
        obj[arr[0]] = newVal
      } else {
        let result = obj[arr[0]]
        arr.shift()
        while (arr.length > 1) {
          result = result[arr[0]]
          arr.shift()
        }
        if (result) {
          result[arr[0]] = newVal
        }
      }
    },
    // 检查slot是否在配置文件中包含，如果没有包含，则视为非法获取
    checkSlotDefine (config) {
      const slotsDeclare = config.slotsDeclare
      const total = slotsDeclare.length
      let count = 0
      slotsDeclare.forEach((declare) => {
        config.columns.forEach((row) => {
          row.forEach((cell) => {
            if (cell.slotConfig === declare) {
              count++
            }
          })
        })
      })

      return count === total
    },
    // 用于分割配置中的colums，将需要处理的数组提取出来
    formatConfigRow () {
      for (let i = 0; i < this.config.columns.length; i++) {
        // 对原始数组进行递归，依次将该位置拆分为三个部分，当前处理位置之前的，当前处理位置，当前处理位置之后的
        const before = this.config.columns.slice(0, i)
        const after = this.config.columns.slice(
          i + 1,
          this.config.columns.length
        )

        // 将当前处理的数组交给处理的方法
        const x = this.checkRow(this.config.columns[i])

        const newArr = []

        // 拼接之前的数组
        if (before.length > 0) {
          if (before.length >= 1) {
            before.forEach((item) => {
              newArr.push(item)
            })
          } else {
            newArr.push(before)
          }
        }

        // 拼接不需要更改当前节点处理完成的数组
        newArr.push(x.old)

        // 如果处理了新加的数据，拼接
        if (x.add.length > 0) {
          for (let j = 0; j < x.add.length; j++) {
            if (x.add[j]) {
              newArr.push(x.add[j])
              i++
            }
          }
        }

        // 拼接之后的数组
        if (after.length > 0) {
          if (after.length >= 1) {
            after.forEach((item) => {
              newArr.push(item)
            })
          } else {
            newArr.push(after)
          }
        }

        this.config.columns = newArr
      }
    },
    // 路径中含有@@@的key，将其解析，并返回其数据
    getDeepObject (obj, strPath) {
      const arr = strPath.split('@@@')
      let result = obj[arr[0]]
      arr.shift()
      try {
        while (arr.length > 0) {
          result = result[arr[0]]
          arr.shift()
        }
      } catch (e) {
        result = undefined
      }
      return result
    },
    // 处理colums数组，为声明了rowspan的单元格，自动匹配格式
    checkRow (rowArr) {
      // 不需要更改的数据
      const original = []
      // 需要更改新加的数据
      const addArr = []
      // 统计rowspan出现的总和
      let count = 0
      // 统计声明列需要的rowspan总数
      let total = 0
      // 是否为声明行
      let titleCellFlag = false
      // 是否为声明行后第一行
      let firstSubLine = false
      // 需要处理的行，新的index值
      let subRowIndex = 0
      // 新生成的行，临时存储
      const waitForAddArr = []
      // 用于记录声明行的colspan避免格式错误
      let preColSpan = 0
      // 用于统计循环次数，判断是否是最后一次
      let forEachCount = 0

      // 标记所有数据
      rowArr.forEach((cell) => {
        forEachCount++
        // 如果该行没有rowspan则默认其为1，不要影响统计结果
        if (!cell.rowSpan) {
          cell.rowSpan = 0
        }

        if (cell.text && total !== 0) {
          // 如果遇到了下一个声明行，证明rowspan少了一行，需要补充一个占位格
          const nullObj = {
            type: 'placeHolderColumn',
            order: subRowIndex,
            noBoarder: true,
            needSplit: true,
            colSpan: preColSpan,
            dontShowRow: true,
          }
          subRowIndex++
          waitForAddArr.push(nullObj)
          total = 0
          count = 0
          titleCellFlag = false
          firstSubLine = false
        } else if (
          total !== count + cell.rowSpan &&
          forEachCount === rowArr.length
        ) {
          // 如果没有遇到了下一个声明行，但已经是当前行最后一个数据，也证明rowspan少了一行，需要补充一个占位格
          const nullObj = {
            type: 'placeHolderColumn',
            order: subRowIndex,
            noBoarder: true,
            needSplit: true,
            colSpan: preColSpan,
            dontShowRow: true,
          }
          subRowIndex++
          waitForAddArr.push(nullObj)
          total = 0
          count = 0
          titleCellFlag = false
          firstSubLine = false
        }

        // 判断是否为声明行
        if (cell.text && total === 0) {
          // 将声明行声明的rowspan作为总数，判断下方rowspan相加是否等于声明行声明的数量
          total = cell.rowSpan
          titleCellFlag = false
          firstSubLine = true
          subRowIndex = 1
          cell.show = true
          cell.showRowSpan = total
        } else if (cell.rowSpan > 0 && !titleCellFlag && firstSubLine) {
          // 判断是否为声明行后首行，因为首行不需要移动
          count += cell.rowSpan
          firstSubLine = false
          cell.noBoarder = true
          cell.show = true
          cell.showRowSpan = total
        } else if (cell.rowSpan > 0 && !titleCellFlag && !firstSubLine) {
          // 既非声明行，也非首行，需要移动
          count += cell.rowSpan
          // cell.type = 'notShow'
          cell.needSplit = true
          cell.order = subRowIndex
          cell.dontShowRow = true
          subRowIndex++

          // 如果之前添加过空行补充位置，刚好最后一位还有内容，将其互换
          if (
            forEachCount === rowArr.length &&
            !waitForAddArr[waitForAddArr.length - 1].dataIndex
          ) {
            waitForAddArr[waitForAddArr.length - 1].order += 1
            cell.order -= 1
            waitForAddArr.push(cell)
          } else {
            waitForAddArr.push(cell)
          }
        }

        // 如果count和total相等了，证明已经处理完成。将计数器还原
        if (count === total) {
          total = 0
          count = 0
          titleCellFlag = false
          firstSubLine = false
        }
        // 保存上一个的colspan保持生成的格子与原格式一致
        preColSpan = cell.colSpan
      })

      // 将所有不需要移动的放入original
      rowArr.forEach((cell) => {
        if (cell.needSplit !== true) {
          original.push(cell)
        }
      })

      // 增加新的数组
      waitForAddArr.forEach((cell) => {
        const target = cell.order
        // if (cell.type === 'notShow') {
        //   cell.type = 'inputs'
        // }
        cell.noBoarder = true
        if (addArr[target] === undefined) {
          const temp = []
          temp.push(cell)
          addArr[target] = temp
        } else if (addArr[target].length > 0) {
          addArr[target].push(cell)
        }
      })

      // 如果没有新增，将单元格边框设置为显示
      if (addArr.length < 1) {
        original.forEach((cell) => {
          if (cell.type === 'input' || cell.type === 'inputs') {
            cell.noBoarder = false
          }
        })
      }

      return {
        old: original,
        add: addArr,
      }
    },
    // 扫描配置，如果有插槽则拼接插槽
    scanConfigSlot (config) {
      const columnsArr = config.columns
      for (let i = 0; i < columnsArr.length; i++) {
        for (let j = 0; j < columnsArr[i].length; j++) {
          // 如果发现type为slot，开始匹配对应的slot配置文件
          if (columnsArr[i][j].type === 'slot') {
            const targetName = columnsArr[i][j].slotConfig
            // 找不到目标插槽配置
            if (
              !this.configFromWeb[targetName] ||
              !this.configFromWeb[targetName].columns
            ) {
              this.$logger('report.slotConfig').error('无法找到目标插槽的配置！')
              return
            }

            // 替换columns，合并data
            config.columns[i] = []
            const before = config.columns.slice(0, i)
            let after = config.columns.slice(i + 1, config.columns.length)

            const addArr = []
            for (
              let k = 0;
              k < this.configFromWeb[targetName].columns.length;
              k++
            ) {
              const temp = []
              this.configFromWeb[targetName].columns[k].forEach((cell) => {
                temp.push(cell)
              })
              addArr.push(temp)
            }

            const newArr = []
            // 拼接之前的数组
            if (before.length > 0) {
              if (before.length >= 1) {
                before.forEach((item) => {
                  newArr.push(item)
                })
              } else {
                newArr.push(before)
              }
            }

            addArr.forEach((arr) => {
              newArr.push(arr)
            })

            // 拼接之后的数组
            if (after.length === 1) {
              if (after[0].type === 'slot' || after[0][0].type === 'slot') {
                after = []
              }
            }
            if (after.length > 0) {
              if (after.length >= 1) {
                after.forEach((item) => {
                  newArr.push(item)
                })
              } else {
                newArr.push(after)
              }
            }

            config.columns = newArr
            if (this.configFromWeb[targetName].slotsDeclare) {
              config.slotsDeclare = this.configFromWeb[targetName].slotsDeclare
            } else {
              config.slotsDeclare = []
            }

            if (
              config.data.images &&
              this.configFromWeb[targetName].data.images
            ) {
              config.data.images = {
                ...config.data.images,
                ...this.configFromWeb[targetName].data.images,
              }
              delete this.configFromWeb[targetName].data.images
            }
            config.data = {
              ...config.data,
              ...this.configFromWeb[targetName].data,
            }
          }
        }
      }
      this.config = config
    },
    // 扫描所有插槽名
    scanConfigName (config, resut) {
      if (config.slotsDeclare) {
        config.slotsDeclare.forEach((name) => {
          resut.push(name)
        })
      }
    },
    // 获取插槽
    getConfigAndJoin (config, outerLock) {
      // 检查主配置插槽声明是否合法
      const check = this.checkSlotDefine(config)
      const waitForDownloadSlotName = []
      if (check) {
        // 扫描主配置中声明的插槽名
        this.scanConfigName(config, waitForDownloadSlotName)

        const total = waitForDownloadSlotName.length
        let count = 0

        // 挨个获取插槽
        waitForDownloadSlotName.forEach((configName) => {
          getConfigByName(
            configName,
            this.serverName,
            (res) => {
              this.configFromWeb[configName] = res
              count++
            },
            this.env === 'dev'
          )
        })

        // 使用定时器循环判断锁状态，用于多个插槽，要等待统一获取完成之后，再进行下一步初始化
        const timer = setInterval(() => {
          if (count >= total) {
            clearInterval(timer)
            this.scanConfigSlot(config)
            if (config.slotsDeclare.length > 0) {
              const lock = { status: true }
              this.getConfigAndJoin(config, lock)
              const innerTimer = setInterval(() => {
                if (!lock.status) {
                  clearInterval(innerTimer)
                  outerLock.status = false
                }
              }, 100)
            } else {
              outerLock.status = false
            }
          }
        }, 100)
      } else {
        this.$logger('report.slotConfig').error('插槽配置有误！')
        outerLock.status = false
      }
    },
    // 获取配置之后的初始化
    configInit () {
      this.configEnded = false
      this.allSlotsConfigEnded = false
      this._configEndPromise = new Promise(resolve => { this._configEndResolve = resolve })
      // 将初始化好的配置拷贝一份留存
      this.originalConfig = Object.assign({}, this.config)
      if (!this.dontFormat) {
        // 扫描配置文件中有没有rowSpan，进行格式化调整
        this.formatConfigRow(this.config)
      }
      this.activeConfig = this.config
      this.showSkeleton = false
      // 判断是否有动态Index
      this.activeConfig.columns.forEach((row) => {
        row.forEach((cell) => {
          if (cell.dynamicDataIndex === true) {
            // 如果有动态index，取其函数，运行函数得到真实index保存
            // eslint-disable-next-line no-eval
            const func = eval(
              '(' + cell.customFunctionForDynamicDataIndex + ')'
            )
            cell.dataIndex = func(this.config)
          }
          // 处理 自定义函数的旧逻辑
          if (
            ['action', 'click'].includes(cell.eventType) &&
            cell.customFunction &&
            !cell.events
          ) {
            cell.events = []
            cell.events.push({
              type: cell.eventType,
              customFunction: cell.customFunction,
            })
          }
        })
      })
      // 将数据复制到临时数据中，带有@@@的数据，我们将其整体作为一个key保存，当编辑完成后，再将其解析，回填到需要的数据中
      this.activeConfig.tempData = {}
      // 是否有@@@深层引用
      this.activeConfig.columns.forEach((row) => {
        row.forEach((cell) => {
          // 将@@@解析
          if (
            cell.dataIndex !== undefined &&
            cell.dataIndex.indexOf('@@@') !== -1
          ) {
            this.activeConfig.tempData[cell.dataIndex] = this.getDeepObject(
              this.activeConfig.data,
              cell.dataIndex
            )
          }
        })
      })

      // 对配置进行转换
      this.originalConfig.columns = this.transformArray(
        JSON.parse(JSON.stringify(this.config.columns))
      )

      this.$nextTick(() => {
        this.scanFinish = true
        // 无 slot 时不会触发 slotRendered，在此视为配置彻底结束
        if (!this.config?.slotsDeclare?.length) {
          this._markConfigEnded()
        }
      })
    },
  },
  beforeMount () {
    // 如果只是展示
    if (this.displayOnly) {
      this.onlyDisplay = true
      this.type = 'display'
    }
    // 如果有本地配置，优先使用本地配置
    if (this.localConfig) {
      // 如果配置是普通渲染器
      this.config = this.localConfig
      if (this.configData !== undefined) {
        this.config.data = this.configData
      }
      if (this.config.data.images === undefined) {
        this.config.data.images = {}
      }
      this.configInit()
      this.$logger('report.beforeMount').log('本地 config 就绪', { configName: this.configName })
    } else {
      // 如果本地配置没有值，则从琉璃中获取
      getConfigByName(
        this.configName,
        this.serverName,
        (res) => {
          this.config = JSON.parse(JSON.stringify(res))
          if (this.configData !== undefined) {
            this.config.data = this.configData
          }
          if (this.config.data.images === undefined) {
            this.config.data.images = {}
          }
          this.configInit()
          this.$logger('report.beforeMount').log('远程 config 加载完成', { configName: this.configName })
        },
        this.env === 'dev'
      )
    }
  },
  computed: {
    ...mapState('account', { currUser: 'user' }),
    widget () {
      return this.isWidget // 返回isWidget的值
    },
  },
  mounted () {
    // 如果外界传来了registerMap，我们将本VM对象注册到map中
    if (this.registerMap !== undefined) {
      this.registerMap.push(this)
    }
    // TAB 页签的 slotRef 由 XTab 在 onComponentMounted 中统一注册（与 Cover 普通子组件一致），此处不再自我注册
    // 将原始数据备份保存
    if (this.configData) {
      this.dataCache = JSON.parse(JSON.stringify(this.configData))
    } else {
      if (this.config?.data) {
        this.dataCache = JSON.parse(JSON.stringify(this.config.data))
      }
    }
  },
}
</script>

<style lang="less" scoped>
.tools {
  text-align: center;
  cursor: pointer;

  .toolsItem {
    display: inline-block;
  }
}
</style>
