<template>
    <div id="hc-global-upload-file" class="hc-global-upload-file-box" :class="{ 'global-uploader-single': !global }">
        <!-- 上传 -->
        <HcUploader ref="uploaderRef" class="uploader-app"
                    :options="optionsValue"
                    :file-status-text="fileStatusText"
                    :auto-start="false"
                    @file-added="onFileAdded"
                    @file-success="onFileSuccess"
                    @file-progress="onFileProgress"
                    @file-error="onFileError"
        >
            <HcUploaderUnsupport/>
            <HcUploaderBtn class="hc-global-upload-btn" id="hc-global-upload-btn" ref="uploadBtnRef">选择文件</HcUploaderBtn>
            <HcUploaderList v-show="panelShow">
                <template #default="{ fileList }">
                    <div class="file-panel" :class="{ collapse: collapse }">
                        <div class="file-title">
                            <div class="title">文件列表 {{fileList.length > 0 ? `（${fileList.length}）` : ''}}</div>
                            <div class="operate">
                                <el-button :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
                                    <i :class="collapse ? 'ri-fullscreen-line' : 'ri-subtract-line'"/>
                                </el-button>
                                <el-button title="关闭" link @click="close">
                                    <i class="ri-close-line"/>
                                </el-button>
                            </div>
                        </div>

                        <ul class="file-list">
                            <li v-for="file in fileList" :key="file.id" class="file-item">
                                <HcUploaderFile ref="files" :class="['file_' + file.id, customStatus]" :file="file" :list="true"/>
                            </li>
                            <div v-if="!fileList.length" class="no-file">
                                <i class="ri-file-text-line"/>
                                <span>暂无待上传文件</span>
                            </div>
                        </ul>
                    </div>
                </template>
            </HcUploaderList>
        </HcUploader>
    </div>
</template>

<script setup>
import {computed, nextTick, onMounted, ref, watch} from 'vue'
import {getTokenHeader} from '~src/api/request/header';
import HcUploader from './components/uploader.vue'
import HcUploaderBtn from './components/btn.vue'
import HcUploaderUnsupport from './components/unsupport.vue'
import HcUploaderList from './components/list.vue'
import HcUploaderFile from './components/file.vue'
import {getObjValue, isNullES, getFileSuffix, getRandom} from "js-fast-way";
import {generateMD5} from './common/md5'

const props = defineProps({
    global: {
        type: Boolean,
        default: true
    },
    // 发送给服务器的额外参数
    params: {
        type: Object,
        default: () => ({})
    },
    options: {
        type: Object,
        default: () => ({})
    }
})

//初始变量
const customStatus = ref('')
const panelShow = ref(false)
const collapse = ref(false)
const uploaderRef = ref(null)
const uploadBtnRef = ref(null)
const customParams = ref({})
const uploader = computed(() => uploaderRef.value?.uploader)
const acceptType = 'image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword'

const fileStatusText = {
    success: '上传成功',
    error: '上传失败',
    uploading: '上传中',
    paused: '已暂停',
    waiting: '等待上传'
}

//事件
const emit = defineEmits(['fileAdded', 'fileSuccess', 'fileError', 'filesChange', 'fileProgress'])

//监听
watch(() => [
    props.params,
    props.options
], ([params, options]) => {
    if (params) {
        customParams.value = params
    }
    if (options) {
        initOptions(options)
    }
})

onMounted(() => {
    remove()
    nextTick(() => {
        let input = document.querySelector('#hc-global-upload-btn input')
        input.setAttribute('accept', acceptType)
    })
    customParams.value = props.params
    initOptions(props.options)
})

//配置
const optionsValue = ref({
    target: '/api/blade-resource/largeFile/endpoint/upload-file',
    chunkSize: '2048000',
    fileParameterName: 'file',
    maxChunkRetries: 3,
    headers: getTokenHeader(),
    // 是否开启服务器分片校验
    testChunks: true,
    testMethod: 'POST',
    // 服务器分片校验函数，秒传及断点续传基础
    checkChunkUploadedByResponse: (chunk, message) => {
        //处理OSSID
        let res = {}, params = getObjValue(chunk?.file?.params)
        try {
            res = JSON.parse(message)
        } catch (e) {}
        console.log('res', res)
        if (isNullES(params.ossId)) {
            params.ossId = getRandom()
        }
        return false
    },
    query: (file) => {
        return {...file.params}
    }
})

//设置配置
const initOptions = ({target, fileName, maxChunk, accept}) => {
    // 自定义上传url
    if (target) {
        uploader.value.opts.target = target
    }
    // 自定义文件上传参数名
    if (fileName) {
        uploader.value.opts.fileParameterName = fileName
    }
    // 并发上传数量
    if (maxChunk) {
        uploader.value.opts.maxChunkRetries = maxChunk
    }
    // 自定义文件上传类型
    if (accept) {
        nextTick(() => {
            let input = document.querySelector('#hc-global-upload-btn input')
            input.setAttribute('accept', accept ? accept : acceptType)
        })
    }
}

//选择了文件
const onFileAdded = async (file) => {
    panelShow.value = true
    trigger('fileAdded')
    // 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
    file.params = {
        ...customParams,
        objectType: getFileSuffix(file.name),
        fileType: file.fileType,
    }
    // 计算MD5
    const md5 = await computeMD5(file)
    startUpload(file, md5)
}

//计算文件的MD5
const computeMD5 = (file) => {
    // 文件状态设为"计算MD5"
    statusSet(file.id, 'md5')
    // 暂停文件
    file.pause()
    // 计算MD5时隐藏”开始“按钮
    setResumeStyle(file.id, 'none')
    nextTick(() => {
        document.querySelector(`.hc-custom-status-${file.id}`).innerText = '校验MD5中'
    })
    // 开始计算MD5
    return new Promise((resolve, reject) => {
        generateMD5(file, {
            onSuccess(md5) {
                statusRemove(file.id)
                resolve(md5)
            },
            onError() {
                error(`文件${file.name}读取出错，请检查该文件`)
                file.cancel()
                statusRemove(file.id)
                reject()
            }
        })
    })
}

const setResumeStyle = (id, val = 'none') => {
    nextTick(() => {
        try {
            document.querySelector(`.file_${id} .uploader-file-resume`).style.display = val
        } catch (e) {}
    })
}

// md5计算完毕，开始上传
const beforeFileNum = ref(0)
const startUpload = (file, md5) => {
    const fileList = uploader.value.fileList;
    //判断是否满足条件
    const result = fileList.every(({uniqueIdentifier}) => {
        return uniqueIdentifier !== md5
    })
    if (result) {
        file.uniqueIdentifier = md5
        setResumeStyle(file.id,'')
        beforeFileNum.value ++;
        file.resume()
        trigger('fileProgress', true)
    } else {
        file.cancel()
        error('请不要重复上传相同文件')
    }
}

//上传中
const onFileProgress = (rootFile, file, chunk) => {
    console.log(`上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
}

//上传完成
const finishFileNum = ref(0)
const onFileSuccess = (rootFile, file, response) => {
    let res = JSON.parse(response)
    // 服务端自定义的错误（即http状态码为200，但是是错误的情况），这种错误是Uploader无法拦截的
    if (res.code !== 200) {
        errorFileNum.value ++;
        error(res.msg)
        // 文件状态设为“失败”
        statusSet(file.id, 'failed')
    } else {
        finishFileNum.value ++;
        trigger('fileSuccess', getObjValue(res.data))
    }
    if (beforeFileNum.value === (finishFileNum.value + errorFileNum.value)) {
        trigger('filesChange')
        trigger('fileProgress', false)
    }
}

//上传失败
const errorFileNum = ref(0)
const onFileError = (rootFile, file, response) => {
    errorFileNum.value ++;
    error(response)
    trigger('fileError')
}

//新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
const statusSet = (id, status) => {
    const statusMap = {
        md5: {text: '校验MD5', bgc: '#fff'},
        merging: {text: '合并中', bgc: '#e2eeff'},
        transcoding: {text: '转码中', bgc: '#e2eeff'},
        failed: {text: '上传失败', bgc: '#e2eeff'}
    }
    customStatus.value = status
    nextTick(() => {
        const statusTag = document.createElement('p')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc

        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        statusWrap.appendChild(statusTag)
    })
}

const statusRemove = (id) => {
    customStatus.value = ''
    nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
    })
}

//事件
const trigger = (key, data = {}) => {
    emit(key, data)
}

//错误
const error = (msg) => {
    window?.$notification({
        title: '错误',
        message: msg,
        type: 'error',
        duration: 2000
    })
}

//关闭
const close = () => {
    remove()
    isShow(false)
}

//清除
const remove = () => {
    finishFileNum.value = 0
    beforeFileNum.value = 0
    errorFileNum.value = 0
    cancel()
}

const cancel = () => {
    uploader?.value.cancel()
}

//是否显示
const isShow = (res) => {
    panelShow.value = res
}

//点击上传按钮
const btnUpload = () => {
    if (uploadBtnRef.value) {
        uploadBtnRef?.value.$el.click()
    }
}

// 暴露出去
defineExpose({
    close,
    remove,
    cancel,
    isShow,
    btnUpload
})
</script>

<style lang="scss">
@import "./style/index";
</style>
