<template>
  <div class="edit-add-detail">
    <div class="form-content">
      <el-form
        ref="addEditForm"
        :model="addEditForm"
        :rules="addEditRules"
        label-width="100px"
        label-position="top"
      >
        <div class="rows">
          <div class="rows-left">
            <div class="rows-hr"></div>
          </div>
          <div class="rows-title">
            <div class="text-overflow">视频资料基本信息</div>
          </div>
        </div>
        <el-row :gutter="24">
          <el-col :span="6">
            <el-form-item label="所属路段" prop="roadSegmentName">
              <el-input clearable v-model="addEditForm.roadSegmentName" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="资料名称" prop="fileName">
              <el-input clearable v-model="addEditForm.fileName"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="资料简介" prop="fileIntroduction">
              <el-input
                clearable
                v-model="addEditForm.fileIntroduction"
                placeholder="请输入资料简介"
              ></el-input>
            </el-form-item>
          </el-col>
        </el-row>

        <div class="rows">
          <div class="rows-left">
            <div class="rows-hr"></div>
          </div>
          <div class="rows-title">
            <div class="text-overflow">资料附件</div>
          </div>
        </div>
        <el-form-item
          label=""
          prop="fileList"
          :rules="[
            {
              required: true,
              message: '资料附件不能为空',
              trigger: 'change'
            }
          ]"
        >
          <el-button size="small" type="primary" @click="selectFile">
            附件 +
          </el-button>
          <el-button
            size="small"
            type="danger"
            @click="deleteFile()"
            :disabled="!currentDataDel.length"
            v-show="addEditForm.fileList.length"
          >
            附件 -
          </el-button>
          <div class="attachment-tips-text">
            只能上传
            <span>
              <template v-for="(item, index) in typeRegularArr"
                >{{ item
                }}{{ typeRegularArr.length - 1 === index ? '' : '/' }}</template
              >
            </span>
            文件
          </div>
        </el-form-item>
        <uploader
          :options="uploadOption"
          @file-added="onFileAdded"
          @file-error="uploadError"
          @file-progress="onFileProgress"
          @file-success="onFileSuccess"
          ref="uploader"
          v-show="false"
          v-if="whetherToCancel"
        >
          <uploader-unsupport> </uploader-unsupport>
          <uploader-btn ref="uploaderBtn"></uploader-btn>
          <uploader-list>
          </uploader-list>
        </uploader>
        <el-table
          stripe
          ref="multipleSelection"
          :data="addEditForm.fileList"
          @selection-change="handleSelectionChange"
          row-key="id"
          class="table-list"
          border
          v-show="addEditForm.fileList.length"
        >
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column
            type="index"
            width="80"
            label="序号"
            align="center"
            :index="index => index + 1"
          />
          <el-table-column
            prop="fileName"
            show-overflow-tooltip
            min-width="120"
            align="center"
            label="附件名称"
          >
          </el-table-column>
          <el-table-column
            prop="size"
            min-width="80"
            align="center"
            label="附件大小"
            :formatter="({size}) => formatFileSize(size)"
          />
          <el-table-column
            prop="progress"
            min-width="130"
            align="center"
            label="上传进度"
          >
            <template v-slot="{row}">
              <template
                v-if="row.status === 'error' || row.status === 'mergeError'"
              >
                <el-progress status="exception" :percentage="0" />
              </template>
              <template v-else-if="row.status === 'success'">
                <el-progress status="success" :percentage="100" />
              </template>
              <template v-else-if="row.status === 'pause'">
                <el-progress
                  status="warning"
                  :percentage="+(row._prevProgress * 100).toFixed(2)"
                />
              </template>
              <template
                v-else-if="
                  row.status === 'upload' || row.status === 'initialization'
                "
              >
                <el-progress
                  :percentage="
                    +(row._prevProgress * 100).toFixed(2) >= 90
                      ? 90
                      : +(row._prevProgress * 100).toFixed(2)
                  "
                />
              </template>
              <template v-else-if="row.status === 'merge'">
                <el-progress :percentage="90" />
              </template>
            </template>
          </el-table-column>
          <el-table-column
            prop="size"
            show-overflow-tooltip
            min-width="90"
            align="center"
            label="上传速度"
          >
            <template v-slot="{row}">
              {{ `${formatFileSize(row.currentSpeed)} / S` }}
            </template>
          </el-table-column>
          <el-table-column
            prop="size"
            show-overflow-tooltip
            min-width="120"
            align="center"
            label="预计上传时长"
          >
            <template v-slot="{row}">
              {{
                !Number.isFinite(row.timeRemaining()) ||
                row.timeRemaining() === 0
                  ? '-'
                  : formatTime(row.timeRemaining())
              }}
            </template>
          </el-table-column>
          <el-table-column
            prop="status"
            show-overflow-tooltip
            min-width="100"
            align="center"
            label="上传状态"
          >
            <template v-slot="{row}">
              <template v-if="row.status === 'initialization'">
                <el-tag size="medium" type="warning">文件初始化中..</el-tag>
              </template>
              <template v-else-if="row.status === 'error'">
                <el-tag size="medium" type="danger">文件上传失败</el-tag>
              </template>
              <template v-else-if="row.status === 'mergeError'">
                <el-tag size="medium" type="danger">文件合并失败</el-tag>
              </template>
              <template v-else-if="row.status === 'success'">
                <el-tag size="medium" type="success">文件上传成功</el-tag>
              </template>
              <template v-else-if="row.status === 'pause'">
                <el-tag size="medium" type="warning">文件上传暂停</el-tag>
              </template>
              <template v-else-if="row.status === 'upload'">
                <el-tag size="medium" type="primary">文件上传中...</el-tag>
              </template>
              <template v-else-if="row.status === 'merge'">
                <el-tag size="medium" type="primary">文件合并中...</el-tag>
              </template>
            </template>
          </el-table-column>
          <el-table-column
            show-overflow-tooltip
            min-width="130"
            align="center"
            label="操作"
          >
            <template v-slot="{row}">
              <template
                v-if="
                  row.status !== 'initialization' &&
                    (!row.isComplete() || row.status === 'mergeError') &&
                    row.status !== 'success'
                "
              >
                <el-link
                  v-if="row.status === 'error' || row.status === 'mergeError'"
                  type="primary"
                  :underline="false"
                  style="margin-right: 10px"
                  @click="reUpload(row)"
                >
                  重新上传
                </el-link>
                <el-link
                  v-else
                  :type="row.paused ? 'primary' : 'warning'"
                  :underline="false"
                  style="margin-right: 10px"
                  @click="filePauseUpload(row)"
                >
                  {{ row.paused ? '继续' : '暂停' }}上传
                </el-link>
              </template>

              <el-link
                type="danger"
                :underline="false"
                style="margin-right: 10px"
                @click="deleteFile(row)"
              >
                {{ row.status === 'success' ? '删除附件' : '取消上传' }}
              </el-link>
            </template>
          </el-table-column>
        </el-table>
      </el-form>
    </div>
    <div slot="footer" class="footer">
      <el-button type="primary" @click="handleSave" :loading="saveLoading">
        {{ $t('domestic.save') }}
      </el-button>
      <el-button @click="changePage('TablePage')">
        {{ $t('domestic.cancel') }}
      </el-button>
    </div>
  </div>
</template>

<script>
import PubSub from 'pubsub-js'
import {mapState} from 'vuex'
import {getDictionaryByCode} from '@/utils/dictionary'
import {
  tableAdd,
  tableDetail,
  tableEdit
} from '@/api/patrolAndRescue/documentation/videoMaterials'
import {completeUpload, initFile} from '@/api/systemInterface'
import SparkMD5 from 'spark-md5'
import moment from 'moment'
import 'moment-duration-format'

export default {
  name: 'EditAddPage',
  props: {
    componentName: {
      default: 'TablePage',
      type: String
    }
  },
  data() {
    return {
      addEditForm: {
        fileList: []
      },
      addEditRules: {
        roadSegmentName: [
          {
            required: true,
            message: '路段名称不能为空',
            trigger: 'change'
          }
        ],
        fileName: [
          {
            required: true,
            message: '资料名称不能为空',
            trigger: 'blur'
          }
        ],
        fileList: {
          name: [
            {
              required: true,
              message: '附件不能为空',
              trigger: 'blur'
            }
          ],
        }
      },
      currentDataDel: [],
      saveLoading: false, // 上传loading
      id: '', // id标识符
      whetherToCancel: false,
      pubSub: null,
      typeRegularArr: [
        'mp4',
        'flv', // 视频格式
      ]
    }
  },
  computed: {
    actionUrl: _ => window.context.portal,
    previewUrl: _ =>
      window.context.portal + '/file/onlinePreviewController/v1/getFileById_',
    downloadUrl: _ => window.context.portal + '/file/v1/downloadFile?fileId=',
    ...mapState({
      headers: state => ({
        Authorization: `Bearer ${state.login.currentUser.token}`
      })
    }),
    uploadOption() {
      return {
        target: `${this.actionUrl}/file/v1/chunkUpload`,
        autoUpload: false, // 设置为手动上传，以便先检查类型和请求分片ID
        testChunks: true, // 关闭自动分片检查
        checkChunkUploadedByResponse: this.checkChunkUploadedByResponse,
        singleFile: false, // 确保每个组件只处理单文件上传
        forceChunkSize: true, // 是否强制所有的块都是小于等于 chunkSize 的值
        chunkSize: 10 * 1024 * 1024, // 设置分片大小为10MB
        simultaneousUploads: 10, // 并发数
        maxChunkRetries: 3, // 设置最大重试次数
        chunkRetryInterval: 2000, // 设置重试间隔时间，单位为毫秒
        headers: this.headers,
        onFileAdded: this.onFileAdded, // 文件添加时的钩子
        processParams: this.processParams // 加工分片上传请求体
      }
    }
  },
  watch: {
    componentName: {
      handler(newVal) {
        if (newVal === this.$options.name) {
          this.openPage()
        } else {
          this.closePage()
        }
      }
    }
  },
  mounted() {
    this.pubSub = PubSub.subscribe('EditAddPage', (_, data) => {
      if (data.id) {
        this.id = data.id
        this.getDetail(data.id)
      } else {
        this.addEditForm = {
          ...this.addEditForm,
          companyId: data.companyId || '',
          companyIds: data.companyIds || '',
          companyName: data.companyName || '',
          roadSegmentId: data.roadSegmentId || '',
          roadSegmentName: data.roadSegmentName || '',
        }
      }
    })
  },
  methods: {
    getDictionaryByCode,

    // 打开页面执行
    openPage() {
      this.whetherToCancel = true
    },

    // 关闭页面执行
    closePage() {
      const {addEditRules, pubSub} = this
      this.$refs.uploader?.uploader?.cancel()

      setTimeout(_ => {
        // 重置组件数据
        Object.assign(this.$data, this.$options.data.call(this), {
          addEditRules,
          pubSub
        })
        this.$nextTick(_ => {
          this.$refs.addEditForm.$el.scrollTop = 0
          this.$refs.addEditForm.clearValidate()
        })
      }, 0)
    },

    // 获取详情
    async getDetail(id) {
      const {files = [], ...addEditForm} = await tableDetail(
        id
      )

      this.addEditForm = {
        ...addEditForm,
        fileList: files?.map(i => ({
          ...i,
          timeRemaining: () => 0,
          isComplete: () => true,
          cancel: _ => _,
          status: 'success',
          size: i.byteCount,
          isVideo: true,
          currentSpeed: 0,
          id: i.id
        }))
      }
    },

    // 选择文件
    selectFile() {
      // 查找uploaderBtn组件内部的input元素，并模拟点击
      const inputElement = this.$refs.uploaderBtn.$el.querySelector(
        'input[type="file"]'
      )
      if (inputElement) {
        inputElement.click()
      }
    },

    //表格选中
    handleSelectionChange(val) {
      this.currentDataDel = val.map(item => item.id)
    },

    // 文件添加后（验证文件类型）
    onFileAdded(file) {
      this.saveLoading = true
      const {size, name} = file
      const maxSize = 500 * 1024 * 1024 // 20MB
      const typeRegular = new RegExp(
        `\\.(${this.typeRegularArr.join('|')})$`,
        'i'
      )
      const verification = [
        {
          condition: !typeRegular.test(name),
          result: () =>
            this.$message.warning('只能上传视频文件')
        }
      ]
      const findItem = verification.find(i => i.condition)
      if (findItem) {
        findItem.result && findItem.result()
        this.saveLoading = this.addEditForm.fileList.some(
          i => i.status !== 'success'
        )
        file.ignored = true //文件校验，不符规则的文件过滤掉
      } else {
        file.ignored = false //文件校验，不符规则的文件过滤掉
        const addProps = {
          status: 'initialization',
          isVideo: file.fileType.startsWith('video/'),
          videoTime: 0,
          uploadId: '',
          fileName: file.name
        }
        Object.keys(addProps).forEach(key => {
          this.$set(file, key, addProps[key])
        })

        this.addEditForm.fileList.push(file)
        this.initializeFile(file)
      }
    },

    // 整个文件预处理
    async initializeFile(file) {
      file.pause()
      file.status = 'initialization'
      const fileObject = file.file // 源文件对象
      try {
        const md5 = await this.generateMd5(fileObject)
        file.videoTime = file.isVideo
          ? await this.getVideoDuration(fileObject)
          : 0

        // 请求分片信息
        try {
          let {
            state = false,
            value: {uploadId = '', uploadResult = {}, successChunk = ''} = {}
          } = await initFile({
            md5,
            videoTime: file.videoTime,
            filename: file.name,
            size: file.size,
            partSize: this.uploadOption.chunkSize
          })

          const requiredLearningTime = file.isVideo
            ? uploadResult.videoTime || file.videoTime
            : 0

          if (state && uploadResult.mergeStatus === '1') {
            file.status = 'success'
            const addProps = {
              fileName: file.fileName,
              fileSizes: uploadResult.size,
              fileExt: uploadResult.fileExt,
              fileId: uploadResult.fileId,
              fileType: uploadResult.fileType || (file.isVideo ? '1' : '2'),
              videoTime: uploadResult.videoTime || file.videoTime,
              id: uploadResult.fileId,
              successChunk,
              requiredLearningTime
            }
            Object.keys(addProps).forEach(key => {
              this.$set(file, key, addProps[key])
            })
            setTimeout(_ => {
              this.saveLoading = this.addEditForm.fileList.some(
                i => i.status !== 'success'
              )
            })
          } else if (state && uploadId) {
            file.status = 'upload'
            file.uploadId = uploadId
            if (typeof successChunk === 'string' && successChunk) {
              // 前缀并转换为数字数组
              successChunk = successChunk
                .split(',')
                .map(chunk => +chunk.replace('chunk_', ''))
                .sort((a, b) => a - b)
            } else {
              successChunk = []
            }
            this.$set(file, 'successChunk', successChunk)
            file.resume()
          } else {
            file.status = 'error'
          }
        } catch (error) {
          file.status = 'error'
        }
      } catch {
        file.status = 'error'
        this.$message.error('文件初始化错误，请重新上传')
      }
    },

    //  可选的函数用于根据 XHR 响应内容检测每个块是否上传成功了
    checkChunkUploadedByResponse(chunk) {
      const successChunk = chunk.file.successChunk || []
      return successChunk.includes(chunk.offset)
    },

    /**
     * 生成文件的 MD5 哈希值
     * @param {File} file - 要加密的文件对象
     * @returns {Promise} - 返回包含MD5哈希值的Promise
     */
    generateMd5(file) {
      return new Promise((resolve, reject) => {
        const chunkSize = 2 * 1024 * 1024 // 2MB
        const chunks = Math.ceil(file.size / chunkSize)
        let currentChunk = 0
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()

        fileReader.onload = e => {
          spark.append(e.target.result)
          currentChunk += 1
          if (currentChunk < chunks) {
            loadNext()
          } else {
            // 在文件内容加密完成后，追加文件名进行加密
            const nameBuffer = new TextEncoder().encode(file.name) // 将文件名转换为 ArrayBuffer
            spark.append(nameBuffer)
            const hash = spark.end()
            resolve(hash)
          }
        }

        fileReader.onerror = () => {
          reject(new Error('Error reading file'))
        }

        function loadNext() {
          const start = currentChunk * chunkSize
          const end = Math.min(start + chunkSize, file.size)
          if (start < file.size) {
            const blob = file.slice(start, end)
            fileReader.readAsArrayBuffer(blob)
          }
        }

        loadNext()
      })
    },

    /**
     * 获取视频文件的时长
     * @param {File} file - 视频文件对象
     * @returns {Promise} - 返回包含视频时长的Promise
     */
    getVideoDuration(file) {
      return new Promise((resolve, reject) => {
        const video = document.createElement('video') // 创建video元素
        video.preload = 'metadata' // 预加载视频元数据

        // 当视频元数据加载完成时，获取视频时长
        video.onloadedmetadata = () => {
          window.URL.revokeObjectURL(video.src) // 释放对象URL
          resolve(Math.floor(video.duration)) // 解析视频总时长
        }

        video.onerror = () => {
          reject(new Error('无法加载视频文件的元数据')) // 处理加载错误
        }

        video.src = URL.createObjectURL(file) // 设置视频文件的URL
      })
    },

    // 加工分片上传请求体
    processParams(params, file, check) {
      return {
        chunk: `chunk_${params.chunkNumber - 1}`,
        uploadId: file.uploadId
      }
    },

    // 文件上传中
    onFileProgress(rootFile, file, chunk) {
      rootFile.status = 'upload'
    },

    // 格式化文件大小
    formatFileSize(bytes) {
      const k = 1024
      const sizes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
      if (bytes < k) return '0 KB' // 最小单位为 kb
      const i = Math.floor(Math.log(bytes) / Math.log(k))
      const size = bytes / Math.pow(k, i)
      const formattedSize = parseFloat(size.toFixed(2))
      return `${formattedSize} ${sizes[i - 1]}` // i-1 因为 kb 是最小单位
    },

    // 格式化时间为时分秒（可大于24个小时）
    formatTime(time) {
      return moment
        .duration(time, 'seconds')
        .format('H小时m分ss秒', {trim: false})
    },

    // 文件上传成功
    async onFileSuccess(rootFile, file, response) {
      try {
        file.status = 'merge'
        // 合并文件
        const {value = {}} = await completeUpload(rootFile.uploadId)
        if (value.mergeStatus === '1') {
          file.status = 'success'
          const addProps = {
            fileSizes: file.size,
            fileExt: value.fileExt,
            fileId: value.fileId,
            fileType: value.fileType
          }

          Object.keys(addProps).forEach(key => {
            this.$set(file, key, addProps[key])
          })

          setTimeout(_ => {
            this.saveLoading = this.addEditForm.fileList.some(
              i => i.status !== 'success'
            )
          })
        } else {
          file.bootstrap()
          file.status = 'mergeError'
        }
      } catch {
        file.bootstrap()
        file.status = 'mergeError'
      }
    },

    // 文件上传失败
    uploadError(rootFile, file, message, chunk) {
      rootFile.status = 'error'
      this.$message.error(`"${file.name}"上传失败`)
    },

    // 文件删除
    deleteFile(file) {
      this.$confirm('确定删除选中附件吗？', '提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(async () => {
          if (file) {
            const index = this.addEditForm.fileList.findIndex(
              i => i.id === file.id
            )
            file.cancel && file.cancel()
            if (index !== -1) {
              this.addEditForm.fileList.splice(index, 1)
            }
          } else {
            this.currentDataDel.forEach(id => {
              const index = this.addEditForm.fileList.findIndex(
                i => i.id === id
              )
              if (index !== -1) {
                this.addEditForm.fileList[index]?.cancel()
                this.addEditForm.fileList.splice(index, 1)
              }
            })
          }
          this.saveLoading = this.addEditForm.fileList.some(
            i => i.status !== 'success'
          )
        })
        .catch(_ => _)
    },

    // 文件重新上传
    reUpload(file) {
      file.retry()
      this.initializeFile(file)
    },

    // 文件继续/暂停上传
    filePauseUpload(file) {
      if (file.paused) {
        file.status = 'upload'
        file.resume()
      } else {
        file.status = 'pause'
        file.pause()
      }
    },

    // 保存
    handleSave() {
      console.log('this.addEditForm', this.addEditForm)
      this.$refs.addEditForm.validate(async (valid, object) => {
        if (!valid) {
          this.$message.warning(
            `有${
              Object.keys(object).length
            }个字段未通过校验，请正确填写表单内容。`
          )
        } else {
          this.saveLoading = true
          const myInterFace = this.id ? tableEdit : tableAdd
          const {fileList, ...addEditForm} = this.addEditForm
          try {
            let {state, message} = await myInterFace({
              ...addEditForm,
              files: 
                fileList.map(file => ({
                  byteCount: file.fileSizes || file.byteCount,
                  extensionName: file.fileExt || file.extensionName,
                  id: file.fileId || file.id,
                  fileName: file.fileName,
                }))
            })
            if (state) {
              message = this.id ? '编辑' : '新增'
              this.$message.success(`资料${message}成功`)
              this.changePage('TablePage', this.id ? 'edit' : 'add')
            } else {
              this.$message.error(message)
            }
          } catch {
            this.saveLoading = false
          }
        }
      })
    },

    // 改变页面
    changePage(pageName = '', type = '') {
      if (!pageName) return
      this.$emit('changePage', pageName, _ => {
        PubSub.publish(pageName, {type})
      })
    }
  },
  beforeDestroy() {
    // 在组件销毁之前 清除订阅消息
    PubSub.unsubscribe(this.pubSub)
  }
}
</script>

<style lang="scss" scoped></style>
