<script setup lang="ts">
import { MaterialAPI } from '@/config/API.ts'
import { useFileUpload } from '@/hooks/File/useFileUpload.ts'
import { useMaterial } from '@/hooks/Material/useMaterial.ts'
import router from '@/router'
import type { ResponseInfo } from '@/types/axios.ts'

import axiosInstance from '@/utils/axios/axiosInstance.ts'
import type {
  GameData,
  GameSelectItem,
  ImgUploadRes,
  ResFileInfo,
  TagItem,
  UploadAssetForm,
} from '@/views/Material/types/uploadFileType.ts'
import type {
  FormInstance,
  FormRules,
  UploadFile,
  UploadFiles,
  UploadInstance,
  UploadRawFile,
  UploadRequestOptions,
} from 'element-plus'
import { ElMessageBox } from 'element-plus'
import { nextTick, onMounted, type Reactive, reactive, ref, toRaw } from 'vue'
import { VideoProcessor } from '@/utils/file/handleVideoFile.ts'
import { ImageProcessor } from '@/utils/file/handleImgFile.ts'
import TagSelect from '@/components/Material/TagSelect.vue'

const { getAllTags } = useMaterial()
const { isValidFile, handleFile } = useFileUpload()
const { getGameInfo } = useMaterial()

// 表单数据
const uploadForm = reactive<UploadAssetForm>({
  name: '',
  tags: [],
  gid: '',
  pid: '',
  filePath: '',
  md5: '',
  isImg9: 1,
  resolution: {
    width: 0,
    height: 0,
  },
  headImgPath: '',
  videoType: 1,
})

// 表单规则
const rules = reactive<FormRules<UploadAssetForm>>({
  name: [
    { required: true, message: '请输入创意名', trigger: 'blur' },
    { min: 1, max: 255, message: '3-5个字符', trigger: 'blur' },
  ],
  tags: [
    { required: true, message: '请选择标签', trigger: 'change' },
    { required: true, message: '请选择标签', trigger: 'blur' },
  ],
  isImg9: [
    { required: true, message: '请选择是否是九宫格', trigger: 'change' },
  ],
  gid: [{ required: true, message: '请选择游戏', trigger: 'change' }],
  filePath: [{ required: true, message: '请选择素材', trigger: 'change' }],
})

const gameSelect = reactive<GameSelectItem[]>([]) // 游戏级联选择框数据
const uploadRef = ref<UploadInstance>()
const fileFormRef = ref<FormInstance>()
const tagInput = ref('') // 标签输入框数据

const isVideo = ref(false) // 当前上传的时候是视频
const limit = 1 // 限制上传文件数量
const allTags: Array<TagItem> = [] // 所有标签
const isUploading = ref(false)
const videoDialogVisible = ref(false)
const previewUrl = ref('')
const previewVideoRef = ref<HTMLVideoElement>()

const coverUrl = ref('') // 封面图片地址
const coverFile = ref<File | null>(null) // 封面图片文件

// 级联选择框属性配置
const cascadePropsConfig = {
  expandTrigger: 'hover' as const,
}

/**
 * 选择游戏之后的处理
 * @param value 目前选择的值
 */
const gameSelectChange = (value: string[]) => {
  uploadForm.pid = value[0]
  uploadForm.gid = value[1]
}

/**
 * 生成游戏级联选择框数据
 *
 * @param gameInfoList 游戏信息
 * @param gameSelect 级联选择框的值
 */
const generateGameSelect = (
  gameInfoList: GameData,
  gameSelect: Reactive<GameSelectItem[]>,
) => {
  gameSelect.splice(0, gameSelect.length)
  for (let [k, v] of Object.entries(gameInfoList)) {
    const children = v.map(item => {
      return {
        value: item.gid,
        label: item.gameName,
      }
    })
    gameSelect.push({
      value: k,
      label: k === '' ? '默认' : k,
      children,
    })
  }
}

/**
 * 当文件列表发生变化时的处理事件
 *
 * @param file 选择的文件
 * @param fileList 文件列表
 */
const handleFileChange = (file: UploadFile, fileList: UploadFiles) => {
  if (!file || !file.raw) {
    ElMessage.warning('请传入合法文件')
    return
  }
  // 不是合法文件直接返回不处理
  if (!isValidFile(file)) {
    fileList.splice(1)
    ElMessage.warning('上传文件超出限制或不是规定文件')
    return
  }
  // 统一处理文件上传
  const processUpload = async () => {
    handleFile(file, fileList, isVideo, uploadForm, previewUrl)
    nextTick(() => {
      fileFormRef.value?.validateField('filePath')
    })
    // 重置文件状态保证可重复上传
    fileList.forEach(f => (f.status = 'ready'))

    // 如果是视频的话，还需要生成封面
    if (isVideo.value) {
      nextTick(async () => {
        const videoProcessor = new VideoProcessor(previewUrl.value)
        const { width, height } = await videoProcessor.getVideoInfo()
        coverFile.value = await videoProcessor.generateCoverFile()
        // await uploadFormFile(file, true)
        // isUploading.value = false
        coverUrl.value = await videoProcessor.generateCover()
        uploadForm.resolution = {
          width,
          height,
        }
      })
    } else {
      const imgProcessor = new ImageProcessor(file.raw!)
      const { width, height } = await imgProcessor.getResolution()
      uploadForm.resolution = {
        width,
        height,
      }
      coverUrl.value = file.url!
    }
  }

  if (fileList.length > limit) {
    // 超限需要用户确认
    ElMessageBox.confirm('上传将覆盖前一个文件，是否继续上传？', {
      confirmButtonText: '继续上传',
      cancelButtonText: '取消',
      type: 'warning',
    })
      .then(() => {
        // 保留最新文件，删除旧文件（假设旧文件在数组开头）
        fileList.splice(0, 1)
        processUpload()
      })
      .catch(() => {
        // 取消时移除当前新增的文件
        const newFileIndex = fileList.findIndex(f => f.uid === file.uid)
        if (newFileIndex > -1) fileList.splice(newFileIndex, 1)
      })
  } else {
    // 未超限直接上传
    processUpload()
  }
}

/**
 * 处理表单提交
 *
 * 这个函数不处理表单的提交，只是作为触发器，完成表单验证的功能，然后将后续流程交给文件上传部分
 * @param formEl 表单对象
 */
const submitAsset = async (formEl: FormInstance | undefined) => {
  if (isUploading.value) return
  if (!formEl) return
  // 表单验证
  const isValid = await formEl.validate()
  if (!isValid) {
    ElMessage.warning('请填写完整信息')
    return
  }
  // 交给文件上传的处理流程
  uploadRef.value?.submit()
}

/**
 * 上传单个文件，获得回显
 * @param file 上传的文件
 * @returns 回显信息，上传失败则是null
 */
const uploadSingleFile = async (
  file: UploadRawFile | File,
): Promise<ResFileInfo | null> => {
  const formData = new FormData()
  formData.append('file', file)
  const result = (await axiosInstance.postForm(
    MaterialAPI.uploadAsset,
    formData,
  )) as ResFileInfo
  if (result.code !== 0) {
    ElMessage.error('上传素材失败')
    return null
  }
  return result
}

/**
 * 文件上传
 *
 * 表单提交需要先拿到文件上传成功后的回显，所以文件上传成功后才会执行表单的提交
 * @param options 上传文件信息
 */
const uploadFormFile = async (options: UploadRequestOptions) => {
  if (!options || !options.file) {
    ElMessage.warning('请先上传文件')
    return
  }
  isUploading.value = true
  // 如果有封面图片，需要上传封面图片
  if (coverFile.value !== null) {
    const res = await uploadSingleFile(coverFile.value)
    if (!res) {
      isUploading.value = false
      return false
    }
    uploadForm.headImgPath = res.path
  }
  const file = options.file
  const res = await uploadSingleFile(file)
  if (!res) {
    isUploading.value = false
    return false
  }
  const { md5, path } = res
  uploadForm.md5 = md5
  uploadForm.filePath = path

  // 拿到回显后执行表单的提交流程
  await submitForm()
}

// 判断横版还是竖版，返回数字
const getVideoType = (): 1 | 2 => {
  const { width, height } = uploadForm.resolution
  if (width > height) {
    return 1
  }
  return 2
}

const submitForm = async () => {
  const url = isVideo.value ? MaterialAPI.videoSubmit : MaterialAPI.imgSubmit
  const nFormData = JSON.parse(JSON.stringify(toRaw(uploadForm)))

  // 区分视频和图片，他们需要的字段不同
  if (isVideo.value) {
    nFormData.videoPath = nFormData.filePath
    nFormData.videoType = getVideoType()
    // 对于视频上传需要删除掉九宫格字段信息
    delete nFormData.isImg9
  } else {
    nFormData.imgPath = nFormData.filePath
  }
  // 移除不需要的filePath
  delete nFormData.filePath

  const res = (await axiosInstance.post(url, nFormData)) as ResponseInfo
  let result = res as ImgUploadRes
  if (isVideo.value) {
    result = res as ImgUploadRes
  }
  if (result.code !== 0) {
    ElMessage.error('上传素材失败')
    isUploading.value = false
    return
  }
  ElMessage.success('上传素材成功')
  isUploading.value = false
  // 重置表单
  uploadRef.value?.clearFiles()
  tagInput.value = ''
  fileFormRef.value?.resetFields()
  coverFile.value = null
}

/**
 * 返回上一页
 */
const goBack = () => {
  router.back()
}

/**
 * 更新tag标签列表
 */
const updateTags = async () => {
  const tags = await getAllTags()
  allTags.splice(0, allTags.length, ...tags)
}

/**
 * 更新游戏标签，并且重新生成游戏选择列表
 */
const updateGameInfo = async () => {
  const info = await getGameInfo()
  if (info === null) {
    ElMessage.error('获取游戏信息失败')
    return
  }

  generateGameSelect(info, gameSelect)
}

/**
 * 处理预览
 */
const handlePreview = () => {
  videoDialogVisible.value = true
}

onMounted(async () => {
  await updateTags()
  await updateGameInfo()
})
</script>

<template>
  <div class="uploadAssetContainer">
    <el-page-header class="header" @back="goBack">
      <template #content>
        <span class="mr-3">上传素材</span>
      </template>
    </el-page-header>
    <div class="content">
      <p class="title">素材信息</p>
      <div class="formContainer">
        <el-form
          :rules="rules"
          :model="uploadForm"
          :label-position="'left'"
          ref="fileFormRef"
          label-width="auto"
          class="uploadForm"
        >
          <el-form-item label="创意名" prop="name">
            <el-input
              placeholder="请输入创意名"
              class="w220"
              v-model="uploadForm.name"
            />
          </el-form-item>
          <el-form-item v-if="!isVideo" label="是否是九宫格图片" prop="isImg9">
            <el-radio-group v-model="uploadForm.isImg9">
              <el-radio :value="1">是</el-radio>
              <el-radio :value="0">否</el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item label="游戏" prop="gid">
            <el-cascader
              v-model="uploadForm.gid"
              :options="gameSelect"
              :props="cascadePropsConfig"
              @change="gameSelectChange"
            />
          </el-form-item>
          <el-form-item label="标签" prop="tags">
            <div class="tagContainer">
              <tag-select v-model:selected-tag="uploadForm.tags"></tag-select>
            </div>
          </el-form-item>
          <!--          :limit="limit"-->
          <el-form-item label="文件" prop="filePath">
            <div class="fileContainer">
              <el-upload
                class="uploadContainer"
                drag
                :auto-upload="false"
                list-type="picture"
                :on-change="handleFileChange"
                ref="uploadRef"
                :http-request="uploadFormFile"
              >
                <template #file="{ file }">
                  <div class="fileInfo">
                    <div class="fileIcon" @click="handlePreview">
                      <el-image
                        :src="coverUrl"
                        class="fileListPreviewContent fileListPreviewImg"
                      />
                      <div class="fileListPreviewContent fileListPreviewMask">
                        <el-icon>
                          <zoom-in />
                        </el-icon>
                      </div>
                    </div>

                    <div>{{ file.name }}</div>
                  </div>
                </template>
                <el-icon class="el-icon--upload">
                  <upload-filled />
                </el-icon>
                <div class="el-upload__text">
                  <p>将文件或文件夹拖到此处，或点击上传文件</p>
                  <p class="upload-tips">1.仅支持图片和视频文件，单个上传</p>
                  <p class="upload-tips">
                    2.支持图片格式：png、jpg、jpeg，不超过100MB；支持视频格式：mp4、avi，不超过100MB
                  </p>
                  <p class="upload-tips">
                    3.素材上传后需对尺寸、码率等进行分析，约1-3分钟后方可用于投放
                  </p>
                </div>
              </el-upload>
            </div>
          </el-form-item>
        </el-form>
        <el-dialog
          v-model="videoDialogVisible"
          title="预览"
          class="previewDialog"
        >
          <div class="previewContainer">
            <video
              class="previewContent"
              v-if="isVideo"
              :src="previewUrl"
              width="400"
              height="400"
              controls
              ref="previewVideoRef"
            ></video>
            <img class="previewContent" v-else :src="previewUrl" alt="" />
          </div>
        </el-dialog>
        <div class="fileFooter">
          <el-button
            color="#197AFB"
            class="el-button-mini w120"
            @click="submitAsset(fileFormRef)"
            :loading="isUploading"
            >确定
          </el-button>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.uploadAssetContainer {
  width: 100%;
}

.header {
  width: 100%;
  height: 5vh;
  display: flex;
  background: #f8f9fc;
  align-items: center;
  padding-left: 30px;
  border-bottom: 1px solid #e8eaec;
}

.title {
  padding: 15px;
  font-size: 16px;
  font-weight: 700;
  color: #333;
  background: #f8f9fc;
  border-radius: 3px 3px 0 0;
}

.content {
  width: 90%;

  margin: 10px auto;
}

.formContainer {
  padding: 24px;
  background-color: #fff;
}

.tagContainer {
  display: flex;
  flex-direction: column;
}

.inputTag {
  margin-top: 15px;
  height: 50px;
  width: 800px;
}

.fileContainer {
  width: 800px;
}

.uploadContainer > .el-upload__text {
  color: #606266;
  font-size: 14px;
  text-align: center;
}

.upload-tips {
  color: #b6b7b7;
}

.fileInfo {
  display: flex;
  align-items: center;
  user-select: none;
}

.fileIcon {
  margin-right: 10px;
  position: relative;
  width: 100px;
  height: 100px;
  cursor: pointer;
}

.fileIcon:hover .fileListPreviewMask {
  visibility: visible;
}

.fileListPreviewContent {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
}

.fileListPreviewMask {
  visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.6);
  color: white;
  font-size: 30px;
}

.previewDialog {
  width: 800px;
  height: 800px;
}

.previewContainer {
  width: 80%;
  height: 100%;
  margin: 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>
