<template>
    <div class="chunk-file-uploader">
        <div class="upload-btn-group">
            <el-button type="primary" @click="selectFile"> 附件 +</el-button>
            <el-button
                v-show="fileList_.length"
                :disabled="!currentDataDel.length"
                type="danger"
                @click="deleteFile()"
            >
                附件 -
            </el-button>
            <div class="attachment-tips-text">
                可上传的文件类型：
                <span>
                    <template v-for="(item, index) in typeRegularArr" :key="index"
                        >{{ item }}{{ typeRegularArr.length - 1 === index ? '' : '、' }}</template
                    >
                </span>
            </div>
            <div class="attachment-tips-text" v-if="maxFileCount > 0">
                最多上传{{ maxFileCount }}个文件（已上传{{ fileList_.length }}个）
            </div>
        </div>
        <uploader
            v-show="false"
            ref="uploader"
            :options="uploadOption"
            @file-added="onFileAdded"
            @file-error="uploadError"
            @file-progress="onFileProgress"
            @file-success="onFileSuccess"
        >
            <uploader-unsupport />
            <uploader-btn ref="uploaderBtnRef" />
            <uploader-list />
        </uploader>
        <div class="table-container">
            <el-table
                v-show="fileList_.length"
                ref="multipleSelection"
                :data="fileList_"
                border
                class="table-list"
                scrollbar-always-on
                style="width: 100%;"
                max-height="100%"
                row-key="id"
                stripe
                @selection-change="handleSelectionChange"
            >
                <el-table-column align="center" type="selection" width="55" />
                <el-table-column
                    :index="(index) => index + 1"
                    align="center"
                    label="序号"
                    type="index"
                    width="80"
                />
                <el-table-column
                    align="center"
                    label="文件名称"
                    min-width="120"
                    prop="fileName"
                    show-overflow-tooltip
                />
                <slot name="customFields"></slot>
                <el-table-column
                    :formatter="formatFileSize"
                    align="center"
                    label="附件大小"
                    min-width="80"
                    prop="size"
                />
                <el-table-column align="center" label="上传进度" min-width="130" prop="progress">
                    <template #default="{ row }">
                        <el-progress
                            :percentage="getProgressPercentage(row)"
                            :status="getProgressStatus(row)"
                        />
                    </template>
                </el-table-column>
                <el-table-column
                    align="center"
                    label="上传速度"
                    min-width="90"
                    prop="size"
                    show-overflow-tooltip
                >
                    <template #default="{ row }">
                        {{ `${utils.formatFileSize(row.currentSpeed || 0)} / S` }}
                    </template>
                </el-table-column>
                <el-table-column
                    align="center"
                    label="预计上传时长"
                    min-width="120"
                    prop="size"
                    show-overflow-tooltip
                >
                    <template #default="{ row }">
                        {{ formatTimeRemaining(row) }}
                    </template>
                </el-table-column>
                <el-table-column
                    align="center"
                    label="上传状态"
                    min-width="100"
                    prop="status"
                    show-overflow-tooltip
                >
                    <template #default="{ row }">
                        <el-tag :type="getStatusTagType(row.status)">
                            {{ getStatusText(row.status) }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column align="center" label="操作" min-width="160" show-overflow-tooltip>
                    <template #default="{ row }">
                        <div class="operation-buttons">
                            <template v-if="showUploadControls(row)">
                                <el-link
                                    v-if="row.status === 'error' || row.status === 'mergeError'"
                                    type="warning"
                                    underline="never"
                                    @click="reUpload(row)"
                                >
                                    重新上传
                                </el-link>
                                <el-link
                                    v-else
                                    :type="row.paused ? 'primary' : 'warning'"
                                    underline="never"
                                    @click="filePauseUpload(row)"
                                >
                                    {{ row.paused ? '继续' : '暂停' }}上传
                                </el-link>
                            </template>

                            <el-link type="danger" underline="never" @click="deleteFile(row)">
                                {{ row.status === 'success' ? '删除附件' : '取消上传' }}
                            </el-link>

                            <template v-if="row.status === 'success'">
                                <el-link
                                    v-if="filePreviewShow(row.extensionName) && showPreviewBtn"
                                    type="primary"
                                    underline="never"
                                    @click="handlePreview(row)"
                                >
                                    预览
                                </el-link>
                                <el-link
                                    v-if="showDownloadBtn"
                                    type="primary"
                                    underline="never"
                                    @click="handleDownload(row)"
                                >
                                    下载
                                </el-link>
                            </template>
                        </div>
                    </template>
                </el-table-column>
            </el-table>
        </div>
        <file-preview-dialog v-model:is-show="previewDialogVisible" :file="previewFile" />
    </div>
</template>

<script lang="ts" name="chunkFileUploader" setup>
// Vue相关导入
import { computed, nextTick, ref, watch } from 'vue'
import type { ComponentPublicInstance } from 'vue'

// Element Plus相关导入
import { ElMessage, ElMessageBox } from 'element-plus'

// 第三方库导入
import SparkMD5 from 'spark-md5'

// 项目内部导入
import { getToken } from '@/utils/cookie'
import { completeUpload, initFile } from '@/api/system/chunkUplaod'
import utils from '@/utils/utils'
import FilePreviewDialog from '@/components/FilePreview/filePreviewDialog.vue'
import { fileType, filePreviewShow } from '@/utils/fileUtils'

// 类型定义
interface IFile {
    id?: number | string
    fileName: string
    ignored: boolean
    status?: string
    paused?: boolean
    _prevProgress?: number
    timeRemaining?: () => number
    currentSpeed?: number
    extensionName?: string
    fileId?: string
    [key: string]: any
}

type FileStatus =
    | 'initialization'
    | 'upload'
    | 'pause'
    | 'merge'
    | 'success'
    | 'error'
    | 'mergeError'

// Props定义
const props = defineProps({
    multiple: {
        type: Boolean,
        default: true
    },
    fileList: {
        type: Array<IFile>,
        default: () => []
    },
    typeRegularArr: {
        type: Array<string>,
        default: () => []
    },
    typeRegularMessage: {
        type: String,
        default: '只能上传视频/ 文档 / 图片格式的文件'
    },
    customDownloadFile: {
        type: Function,
        default: null
    },
    chunkSize: {
        type: Number,
        default: 1024 * 1024 * 5
    },
    showPreviewBtn: {
        type: Boolean,
        default: true
    },
    showDownloadBtn: {
        type: Boolean,
        default: true
    },
    maxFileCount: {
        type: Number,
        default: 0
    }
})

// Emits定义
const emit = defineEmits<{
    startUpload: []
    change: [files: IFile[]]
    deleteFileAPI: [id: string | number]
}>()

// 响应式数据
const uploading = ref(false)
const fileList_ = ref<IFile[]>([])
const currentDataDel = ref<(string | number)[]>([])
const previewDialogVisible = ref(false)
const uploaderBtnRef = ref<ComponentPublicInstance | null>(null)
const previewFile = ref({
    id: '',
    name: '',
    type: ''
})

// 监听器
watch(
    () => props.fileList,
    (newVal) => {
        fileList_.value = newVal as IFile[]
    },
    { immediate: true, deep: true }
)

// 计算属性
const headers = computed(() => ({
    Authorization: `Bearer ${getToken()}`
}))

const baseUrl = computed(() => window.config.API_BASE_URL)
const downloadUrl = computed(() => `${baseUrl.value}/file/v1/downloadFile?fileId=`)

const uploadOption = computed(() => ({
    target: `${baseUrl.value}/file/v1/chunkUpload`,
    autoUpload: false,
    testChunks: true,
    chunkSize: props.chunkSize,
    singleFile: false,
    forceChunkSize: true,
    simultaneousUploads: 10,
    maxChunkRetries: 3,
    chunkRetryInterval: 2000,
    headers: headers.value,
    onFileAdded: onFileAdded,
    processParams: processParams,
    checkChunkUploadedByResponse: checkChunkUploadedByResponse
}))

// 工具函数
const formatFileSize = (row: IFile) => utils.formatFileSize(row.byteCount || row.size)

const getProgressPercentage = (row: IFile): number => {
    const progress = Math.floor(+(row._prevProgress || 0) * 100)
    if (row.status === 'success') return 100
    if (row.status === 'error' || row.status === 'mergeError') return 0
    if (row.status === 'merge') return 90
    console.log(row._prevProgress, progress, 'row._prevProgress')
    return Math.min(progress, 90)
}

const getProgressStatus = (row: IFile): string => {
    if (row.status === 'success') return 'success'
    if (row.status === 'error' || row.status === 'mergeError') return 'exception'
    if (row.status === 'pause') return 'warning'
    return ''
}

const formatTimeRemaining = (row: IFile): string => {
    if (!row.timeRemaining || !Number.isFinite(row.timeRemaining()) || row.timeRemaining() === 0) {
        return '-'
    }
    return utils.formatTime(row.timeRemaining())
}

const getStatusTagType = (status: FileStatus): string => {
    const statusMap: Record<FileStatus, string> = {
        initialization: 'warning',
        upload: 'primary',
        pause: 'warning',
        merge: 'primary',
        success: 'success',
        error: 'danger',
        mergeError: 'danger'
    }
    return statusMap[status] || 'info'
}

const getStatusText = (status: FileStatus): string => {
    const statusTextMap: Record<FileStatus, string> = {
        initialization: '文件初始化中..',
        upload: '文件上传中...',
        pause: '文件上传暂停',
        merge: '文件合并中...',
        success: '文件上传成功',
        error: '文件上传失败',
        mergeError: '文件合并失败'
    }
    return statusTextMap[status] || '未知状态'
}

const showUploadControls = (row: IFile): boolean => {
    return (
        row.status !== 'initialization' &&
        (!row.isComplete?.() || row.status === 'mergeError') &&
        row.status !== 'success'
    )
}

// 文件验证
const validateFileType = (fileName: string): boolean => {
    if (!props.typeRegularArr.length) return true
    const typeRegular = new RegExp(`\\.(${props.typeRegularArr.join('|')})$`, 'i')
    return typeRegular.test(fileName)
}

const validateFileCount = (): boolean => {
    return props.maxFileCount === 0 || fileList_.value.length < props.maxFileCount
}

// 选择文件
function selectFile() {
    const inputElement = uploaderBtnRef.value?.$el.querySelector('input[type="file"]')
    inputElement?.click()
}

// 文件删除
async function deleteFile(file?: IFile) {
    try {
        await ElMessageBox.confirm('确定删除选中附件吗？', '提示', {
            confirmButtonText: '确认',
            cancelButtonText: '取消',
            type: 'warning'
        })

        if (file) {
            const index = fileList_.value.findIndex((i) => i.id === file.id)
            file.cancel?.()
            if (index !== -1) {
                fileList_.value.splice(index, 1)
                emit('deleteFileAPI', file.id!)
            }
        } else {
            currentDataDel.value.forEach((id) => {
                const index = fileList_.value.findIndex((i) => i.id === id)
                if (index !== -1) {
                    fileList_.value[index].cancel?.()
                    fileList_.value.splice(index, 1)
                }
            })
        }

        uploading.value = fileList_.value.some((i) => i.status !== 'success')
        emit('change', fileList_.value)
    } catch {
        // 用户取消删除
    }
}

// 文件上传回调
function onFileProgress(rootFile: IFile) {
    rootFile.status = 'upload'
}

function uploadError(rootFile: IFile, file: IFile) {
    rootFile.status = 'error'
    ElMessage.error(`"${file.name}"上传失败`)
}

async function onFileSuccess(rootFile: IFile, file: IFile) {
    const targetFile = fileList_.value.find((f) => f.id === rootFile.id || f === rootFile)
    if (!targetFile) return

    try {
        targetFile.status = 'merge'
        const { value = {} } = await completeUpload(rootFile.uploadId)
        await nextTick()

        if (value.success) {
            Object.assign(targetFile, {
                byteCount: file.size,
                extensionName: value.fileExt,
                fileId: value.fileId,
                id: value.fileId,
                fileType: value.fileType,
                status: 'success'
            })

            setTimeout(() => {
                uploading.value = fileList_.value.some((i) => i.status !== 'success')
            })
            emit('change', fileList_.value)
        } else {
            targetFile.status = 'mergeError'
        }
    } catch {
        targetFile.status = 'mergeError'
    }
}

// 文件操作
function reUpload(file: IFile) {
    file.retry?.()
    initializeFile(file)
}

function filePauseUpload(file: IFile) {
    if (file.paused) {
        file.status = 'upload'
        file.resume?.()
    } else {
        file.status = 'pause'
        file.pause?.()
    }
}

function handleSelectionChange(val: IFile[]) {
    currentDataDel.value = val
        .map((item) => item.id)
        .filter((id): id is string | number => id !== undefined)
}

// 上传配置
function checkChunkUploadedByResponse(chunk: any) {
    const successChunk = chunk.file.successChunk || []
    return successChunk.includes(chunk.offset)
}

function processParams(params: any, file: IFile) {
    return {
        chunk: `chunk_${params.chunkNumber - 1}`,
        uploadId: file.uploadId
    }
}

// 文件添加处理
function onFileAdded(file: IFile) {
    uploading.value = true
    emit('startUpload')

    // 验证文件数量
    if (!validateFileCount()) {
        ElMessage.error(`最多只能上传${props.maxFileCount}个文件`)
        file.ignored = true
        uploading.value = fileList_.value.some((i) => i.status !== 'success')
        emit('change', fileList_.value)
        return
    }

    // 验证文件类型
    if (!validateFileType(file.name)) {
        ElMessage.error(props.typeRegularMessage)
        file.ignored = true
        uploading.value = fileList_.value.some((i) => i.status !== 'success')
        emit('change', fileList_.value)
        return
    }

    filePassToUpdate(file)
}

function filePassToUpdate(file: IFile) {
    Object.assign(file, {
        ignored: false,
        status: 'initialization',
        uploadId: '',
        fileName: file.name
    })

    if (!props.multiple) {
        fileList_.value = []
    }

    fileList_.value.push(file)
    initializeFile(file)
}

// 文件初始化
async function initializeFile(file: IFile) {
    file.pause?.()
    file.status = 'initialization'

    try {
        const md5 = await generateMd5(file.file)
        const response = await initFile({
            md5,
            filename: file.name,
            size: file.size,
            partSize: props.chunkSize
        })

        const {
            state = false,
            value: { uploadId = '', uploadResult = {}, successChunk = '' } = {}
        } = response

        if (state && uploadResult.mergeStatus === '1') {
            // 文件已存在
            Object.assign(file, {
                status: 'success',
                fileName: file.fileName,
                byteCount: uploadResult.size,
                extensionName: uploadResult.fileExt,
                fileId: uploadResult.fileId,
                fileType: uploadResult.fileType || (file.isVideo ? '1' : '2'),
                id: uploadResult.fileId,
                successChunk
            })
            emit('change', fileList_.value)
            setTimeout(() => {
                uploading.value = fileList_.value.some((i) => i.status !== 'success')
            })
        } else if (state && uploadId) {
            // 开始上传
            file.status = 'upload'
            file.uploadId = uploadId
            file.successChunk = parseSuccessChunk(successChunk)
            file.resume?.()
        } else {
            file.status = 'error'
        }
    } catch {
        file.status = 'error'
        ElMessage.error('文件初始化错误，请重新上传')
    }
}

function parseSuccessChunk(successChunk: string): number[] {
    if (typeof successChunk === 'string' && successChunk) {
        return successChunk
            .split(',')
            .map((chunk) => +chunk.replace('chunk_', ''))
            .sort((a, b) => a - b)
    }
    return []
}

// MD5生成
function generateMd5(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
        const chunkSize = props.chunkSize
        const chunks = Math.ceil(file.size / chunkSize)
        let currentChunk = 0
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()

        fileReader.onload = (e) => {
            if (e.target?.result) {
                spark.append(e.target.result as ArrayBuffer)
                currentChunk += 1
                if (currentChunk < chunks) {
                    loadNext()
                } else {
                    const nameBuffer = new TextEncoder().encode(file.name)
                    spark.append(nameBuffer)
                    resolve(spark.end())
                }
            } else {
                reject(new Error('FileReader event target is null'))
            }
        }

        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()
    })
}

// 文件预览和下载
function handlePreview(file: IFile) {
    const fileId = file.fileId || file.response?.fileId || ''
    const fileName = file.name || file.fileName || ''
    previewFile.value = {
        id: fileId,
        name: fileName,
        type: fileType(file.extensionName) || 'unknown'
    }
    previewDialogVisible.value = true
}

function handleDownload(file: IFile) {
    if (props.customDownloadFile && typeof props.customDownloadFile === 'function') {
        props.customDownloadFile(file)
    } else {
        downloadFile(file)
    }
}

function downloadFile(file: IFile) {
    const fileId = file.fileId
    if (fileId) {
        const fileName = file.name || file.fileName || ''
        if (!(fileId && fileName)) {
            return ElMessage.warning('文件错误，请删除后重新上传')
        }
        window.open(downloadUrl.value + fileId, '_blank')
    } else {
        ElMessage.info('请先保存附件')
    }
}
</script>

<style lang="scss" scoped>
.chunk-file-uploader {
    width: 100%;
    height: 100%;
}

.upload-btn-group {
    margin-bottom: 20px;
    margin-left: 8px;
}

.table-container {
    height: calc(100% - 84px);
}

.attachment-tips-text {
    margin-top: 4px;
}

.operation-buttons {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: center;
}
</style>
