<!--
 * @Author: fxs bjnsfxs@163.com
 * @Date: 2024-09-02 17:57:15
 * @LastEditors: fxs bjnsfxs@163.com
 * @LastEditTime: 2024-10-14 18:35:26
 * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
 * @Description: 
 * 
-->
<script setup lang="ts">
import type { ResponseInfo } from '@/types/res'

import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
import router from '@/router'
import FileUpload from '@/components/form/FileUpload.vue'
import axiosInstance from '@/utils/axios/axiosInstance'

import { ref, reactive, computed } from 'vue'
import { useRequest } from '@/hooks/useRequest'
import { useCommonStore } from '@/stores/useCommon'
import { downLoadData } from '@/utils/table/table'
import { resetTimeToMidnight } from '@/utils/common'
import { shouldListenToEvent } from '@/utils/table/table'

import { throttleFunc } from '@/utils/common'

interface uploadEvent {
  id?: number // 更新需要传入id
  gid: string
  actionId: string
  actionName: string
  status: number
  remark?: string
}

interface UploadOption {
  id?: number // 更新需要传入id
  optionId: string
  optionName: string
  optionType: string
  actionId: number
  status: number
}

interface DownLoadEvent {
  actionId: string
  actionName: string
  createdAt: string
  gid: string
  id: number
  remark: string
  status: number
  updatedAt: string
}

interface DownLoadOption {
  actionId: number
  createdAt: string
  id: number
  optionId: string
  optionName: string
  optionType: string
  status: number
  updatedAt: string
}

type GetTableReturn<T> = T extends 'all'
  ? {
      allEventTable: Array<DownLoadEvent>
      allOptionsInfo: { [key: string]: Array<DownLoadOption> }
    }
  : T extends 'event'
    ? { allEventTable: Array<DownLoadEvent> }
    : T extends 'option'
      ? { allOptionsInfo: { [key: string]: Array<DownLoadOption> } }
      : never

const { selectInfo } = useCommonStore()

const { AllApi } = useRequest()

// 事件表格ref
const eventTableRef = ref()

// 头部ref
const headerCard = ref<typeof HeaderCard>()

// 上传ref
const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)

// 上传的信息
const uploadInfo = reactive({
  dialogTitle: '上传事件信息'
})

// 现在的路由路径名称
const nowRouteName = computed(() => router.currentRoute.value.name)

/**
 * @description: 进入详情页，触发headercard的添加事件，增加一个面包屑导航
 * @param {*} info 传入的信息
 * @return {*}
 */
const headerAddPath = (info: any) => {
  const { name, pathName } = info

  headerCard.value?.addPath(name, pathName)
}

/**
 * @description:  展示上传之后的反馈信息
 * @param {*} state 这个状态只表示是否全部成功，其余情况皆为false
 * @param {*} name 区分是事件还是选项
 * @return {*}
 */
const showUploadMsg = (failedCount: number, totalCount: number, name: string): boolean => {
  if (failedCount < 0) throw new Error('Error!FailedCount < 0')

  let state = true
  let duration = 3000
  let title = '上传成功'
  let position: 'top-left' | 'top-right' = 'top-left'
  let message: string = `${name}全部上传成功`
  let type: 'success' | 'warning' | 'error' = 'success'

  // 有上传失败的情况下
  if (failedCount > 0) {
    state = false
    position = 'top-right'
    duration = 8000
    if (failedCount === totalCount) {
      message = `${name}全部上传失败`
      type = 'error'
    } else {
      message = `${name}部分上传失败`
      type = 'warning'
    }
  }
  ElNotification({
    type,
    title,
    message,
    duration,
    position
  })

  return state
}

/**
 * @description:  提交所有新上传的事件及选项请求
 * @param {*} reqList 请求列表
 * @param {*} msg 提示信息，用于展示上传之后返回的消息
 * @return {*}
 */
const submitUpload = async (reqList: Array<Promise<boolean>>, msg: string): Promise<boolean> => {
  const result = await Promise.allSettled(reqList).then((res) => {
    const failedList = res.filter((item: any) => item.value === false)
    const failedCount = failedList.length
    const totalCount = res.length
    let state = showUploadMsg(failedCount, totalCount, msg)
    return state
  })

  return result
}

/**
 * @description: 请求表格数据，如果一次请求没有拿到所有的数据，则再请求一次
 * @param {*} url 请求地址
 * @param {*} otherInfo 请求参数
 * @param {*} offset 偏移量
 * @param {*} limit 请求数量
 * @return {*}
 */
const getTableData = async (
  url: string,
  otherInfo: any,
  offset = 0,
  limit = 10000
): Promise<Array<any>> => {
  let total = limit + offset // 目前请求到的总数
  let finalResult = []
  finalResult = await axiosInstance
    .post(url, {
      offset,
      limit,
      ...otherInfo
    })
    .then(async (res) => {
      let resData = JSON.parse(JSON.stringify(res))
      let result = []
      if (!resData.data) return []

      if (resData.count > total) {
        result = await getTableData(url, otherInfo, total, limit)
      }
      return [...resData.data, ...result]
    })
    .catch((err) => {
      console.log(err)
      return []
    })
  return finalResult
}

/**
 * @description:  批量请求选项数据
 * @param {*} url 请求的地址
 * @param {*} reqParams 请求的参数列表，主要是actionid
 * @param {*} eventTable 事件列表
 * @return {Promise<Object>}  返回一个最终的结果，其中key是每个事件的actionid，值是选项列表
 */
const batReqOptionsData = async (
  url: string,
  reqParams: Array<any>,
  eventTable: Array<DownLoadEvent>
): Promise<{ [key: string]: Array<DownLoadOption> }> => {
  let reqList: Array<Promise<any>> = []
  let finalResult: {
    [key: string]: Array<DownLoadOption>
  } = {}

  reqParams.map((item) => {
    reqList.push(
      getTableData(url, item)
        .then((res) => {
          let actionId = eventTable.find((i) => i.id === item.actionId)?.actionId
          if (actionId) finalResult[actionId] = res
        })
        .catch((err) => {
          console.log(err)
        })
    )
  })
  await Promise.all(reqList)
  return finalResult
}

/**
 * @description: 拿到事件数据和选项数据，首先需要拿到所有的事件列表，然后将他们的actionId抽出来形成一个列表，这个列表去作为optin的查询参数批量查询
 * @return {*} 返回事件数据和选项数据
 */
const getAllTable = async <T extends 'all' | 'event' | 'option' = 'all'>(
  table: T = 'all' as any
): Promise<GetTableReturn<T>> => {
  let allEventTable: Array<DownLoadEvent> = [],
    allOptionsInfo: { [key: string]: Array<DownLoadOption> } = {}

  allEventTable = await getTableData(AllApi.gameActionList, { gid: selectInfo.gid })

  let optionReqList = allEventTable.map((item) => {
    return { actionId: item.id }
  })
  allOptionsInfo = await batReqOptionsData(
    AllApi.gameActionOptionList,
    optionReqList,
    allEventTable
  )
  // 只需要事件列表在这里就返回
  if (table === 'event') {
    return { allEventTable } as GetTableReturn<T>
  }

  // 只需要选项在这里就只返回选项
  if (table === 'option') {
    return { allOptionsInfo } as GetTableReturn<T>
  }

  return { allEventTable, allOptionsInfo } as GetTableReturn<T>
}

/**
 * @description: 开始上传
 * @return {*}
 */
const startUpload = async () => {
  if (uploadRef.value) {
    uploadRef.value.startUpload()
  }
}

/**
 * @description: 获取事件列表和选项列表的数据，开始下载
 * @return {*}
 */
const startDownload = async () => {
  let { allEventTable, allOptionsInfo } = await getAllTable()
  downLoadData(`allevents_${resetTimeToMidnight(new Date())}`, {
    allEventTable,
    allOptionsInfo
  })
}

// 把开始下载方法包装一下，节流
const throttleStartDownload = throttleFunc(startDownload, 1000)

/**
 * @description: 创建一个上传请求函数
 * @param {*} url 请求地址
 * @param {*} reqParams 请求参数
 * @param {*} name 上传的是事件还是选项
 * @return {*}
 */
const createUploadReqFunc = async (url: string, reqParams: any, name: string): Promise<boolean> => {
  let result: boolean = false
  try {
    const res: ResponseInfo = await axiosInstance.post<string, ResponseInfo>(url, reqParams)

    if (res.code !== 0) {
      ElNotification({
        type: 'warning',
        title: '事件上传失败',
        dangerouslyUseHTMLString: true,
        message: `${name}上传失败.<br/>${res.msg}`,
        position: 'top-left',
        duration: 8000
      })
    }

    result = res.code === 0
  } catch (err: any) {
    ElNotification({
      type: 'error',
      title: '上传失败',
      message: `${name}上传失败,请检查参数`,
      position: 'top-right',
      duration: 5000
    })
    console.log(err)
  }
  return result
}

/**
 * @description: 上传所有事件
 * @param {*} uploadEventTable 上传的事件列表
 * @param {*} allEventTable 获取的事件列表
 * @return {Promise<boolean>} 返回是否上传成功
 */
const uploadEvent = async (
  uploadEventTable: Array<uploadEvent>,
  allEventTable: Array<DownLoadEvent>
): Promise<boolean> => {
  let eventReqUrl = AllApi.setGameAction
  let eventReqList: Array<Promise<boolean>> = []
  uploadEventTable.map((item: uploadEvent) => {
    let { id, ...otherInfo } = item
    if (allEventTable.some((i: DownLoadEvent) => i.actionId === item.actionId)) {
      eventReqUrl = AllApi.updateGameAction
    }
    let eventReq = createUploadReqFunc(eventReqUrl, otherInfo, item.actionName)
    eventReqList.push(eventReq) // 统一放入请求列表中
  })
  return await submitUpload(eventReqList, '事件') // 等待所有的事件请求完成
}

/**
 * @description: 上传所有选项
 * @param {*} uploadOptionsInfo 上传的选项列表
 * @return {*}
 */
const uploadOpiton = async (uploadOptionsInfo: { [key: string]: Array<UploadOption> }) => {
  const { allEventTable, allOptionsInfo } = await getAllTable() // 重新获取所有事件列表和选项列表
  let optionsReqList: Array<Promise<boolean>> = []
  if (Object.keys(uploadOptionsInfo).length) {
    // 开始上传选项
    allEventTable.map((item) => {
      // 在上传的事件列表中，找到有对应的事件的actionid的那一组数据
      let uploadOptionItem = uploadOptionsInfo[item.actionId]
      // 在现有的事件列表中，找到对应事件的actionid的那一组数据
      let nowOptionItem = allOptionsInfo[item.actionId] // 找到所有在已有事件列表中的选项列表

      // 如果有已存在的事件，并且上传的选项列表中也有对应的actionid，则开始上传
      if (uploadOptionItem) {
        // 对找到的那一组数据进行循环，区分出来哪些是已有的，哪些是新上传的
        // 新上传的需要给他加上actionid，然后上传，这个actionid其实是事件列表的id字段
        uploadOptionItem.map((i) => {
          const { id, actionId: originalActionId, ...otherInfo } = i // 上传参数拆分出来
          const isUpdate = nowOptionItem.some((k) => k.id === id) // 判断是否是更新选项

          // 设置请求 URL 和参数
          const optionReqUrl = isUpdate ? AllApi.updateGameActionOption : AllApi.addGameActionOption
          const reqParams = isUpdate
            ? { id, ...otherInfo } // 更新选项
            : { actionId: item.id, ...otherInfo } // 新增选项

          let eventReq = createUploadReqFunc(optionReqUrl, reqParams, item.actionName)
          optionsReqList.push(eventReq) // 统一放入请求列表中
        })
      } else {
        setTimeout(() => {
          ElNotification({
            type: 'warning',
            title: `没有对应事件`,
            message: `没有${item.actionName}事件，为事件添加的选项无效`,
            position: 'top-right',
            duration: 8000
          })
        }, 0)
      }
    })

    await submitUpload(optionsReqList, '选项') // 等待所有选项上传完成
  } else {
    ElNotification({
      type: 'warning',
      title: '没有选项被上传',
      message: `上传选项为空`,
      position: 'top-right',
      duration: 8000
    })
  }
}

// 上传选项的时候，选项的键要设置为actionid
// 需要先上传事件，然后上传完了，用上传的选项中的第一个（如果有）的actionid去找到对应的事件的ID（不是actionid），然后作为选项上传的id
/**
 * @description: 当文件添加后，开始进行上传前的处理
 *  首先需要上传事件，如果现有上传的列表中的actionid已经包含在了现有列表中，那么判定为更新，否则为新增
 *  上传完成后，需要首先获取一次新的数据，然后再开始上传选项。
 *  分别在上传和已有的选项列表中，找到对应的acionID的那一组选项，对这一组数据，如果已经存在对应的id，那么就是更新，否则为新增
 *  然后上传，上传完毕后，整体列表刷新，关闭弹框
 * @param {*} data 上传的文件数据,里面包含allEventTable:所有的事件列表，allOptionsInfo:所有选项列表
 * @return {*}
 */
const uploadSuccess = async (data: {
  allEventTable: Array<uploadEvent>
  allOptionsInfo: { [key: string]: Array<UploadOption> }
}) => {
  let uploadEventTable = data.allEventTable
  let uploadOptionsInfo = data.allOptionsInfo

  let allEventTable: Array<DownLoadEvent> = []

  // 上传的事件列表有值，则开始上传
  ;({ allEventTable } = await getAllTable('event')) // 获取所有事件列表和选项列表

  let eventUploadResult = false

  if (Array.isArray(uploadEventTable) && uploadEventTable.length > 0) {
    // 将新事件和旧事件区分，对于新事件走新增，对于旧事件走更新

    eventUploadResult = await uploadEvent(uploadEventTable, allEventTable)
    // 如果事件上传全部失败，那么就不要再上传选项了
    if (eventUploadResult) {
      uploadOpiton(uploadOptionsInfo)
    }
  } else {
    ElNotification({
      type: 'error',
      title: '上传参数有误',
      message: `上传参数为空，请检查参数`,
      position: 'top-left',
      duration: 5000
    })
  }

  uploadRef.value?.uploadCallback()
  eventTableRef.value?.updateData()
}
</script>

<template>
  <div class="enventManage">
    <div class="breadcrumbBox">
      <HeaderCard
        ref="headerCard"
        :need-breadcrumb="true"
        :title="'事件管理'"
        :need-pf-select="false"
      ></HeaderCard>
    </div>

    <div class="handleEvent" v-if="nowRouteName === 'EventTable'">
      <div class="fileGroup">
        <el-button class="fileBtn" color="#626aef" @click="startUpload">上传</el-button>
        <el-button class="fileBtn" @click="throttleStartDownload">下载</el-button>
      </div>
    </div>
    <div class="eventTableBox">
      <!-- 监听表格的跳转事件 -->
      <router-view v-slot="{ Component, route }">
        <!-- 是eventtable组件就去监听enterdetail事件 -->
        <!-- 注释也不要写到keep-alive里面，会报错 -->

        <component
          :is="Component"
          ref="eventTableRef"
          v-if="shouldListenToEvent(route.name, 'EventTable')"
          @enterEventDetail="headerAddPath"
        />

        <!-- 如果不是正常渲染其他组件 -->
        <component v-if="route.name === 'EventDetail'" :is="Component" />
        <!-- <component v-else :is="Component" /> -->
      </router-view>
    </div>
    <div class="uploadFileBox">
      <FileUpload
        @upload-success="uploadSuccess"
        ref="uploadRef"
        :title="uploadInfo.dialogTitle"
      ></FileUpload>
    </div>
  </div>
</template>

<style scoped>
.enventManage {
  width: 98%;
  margin: 1% auto;
  background-color: white;
  box-sizing: border-box;
  position: relative;
}

.breadcrumbBox {
  background-color: white;
  box-sizing: border-box;
  height: 64px;
  font-size: 16px;
  color: #17233d;
  font-weight: 600;
  /* padding: 0 24px; */
  line-height: 64px;
}

.eventTableBox {
  box-sizing: border-box;
  padding: 0px 24px;
}

.handleEvent {
  position: absolute;
  /* width: 12%; */
  background-color: white;
  box-sizing: border-box;
  /* height: 48px; */
  font-size: 16px;
  font-weight: 600;
  top: 20px;
  right: 24px;
  /* position: relative; */
  /* justify-content: flex-end; */
}

.fileGroup {
  width: 100%;
}
.fileBtn {
  margin-right: 10px;
}
</style>
