<template>
    <div v-if="isImageFile" class="message-file-image-container" @click="handleImagePreview">
        <img class="message-file-image-content" draggable="false" :src="fileContent.url" @load="handleImageLoaded" />
    </div>

    <div v-else-if="isVideoFile" class="message-file-video-container">
        <video class="message-file-video-content" :class="{ 'is-pc': isPC }" :src="fileContent.url" controls playsinline
            webkit-playsinline preload="metadata" @loadedmetadata="handleVideoLoaded" />
    </div>

    <div v-else class="message-file-wrapper" :class="bubbleClass">
        <div class="message-file-progress message-file-upload" :class="bubbleClass" :style="{ width: uploadWidth }" />

        <div class="message-file-container" @click="downloadFile">
            <div class="message-file-text-container">
                <div class="message-file-name">{{ getFileName(fileContent.name) }}</div>
                <div class="message-file-meta">
                    <template v-if="fileContent.uploadProgress !== undefined">
                        {{ uploadText }}
                    </template>
                    <template v-else>
                        {{ displayFileSize }}
                    </template>
                </div>
            </div>

            <div class="message-file-icon-wrapper">
                <div v-if="isDownloading" class="message-file-download-circle"
                    :style="{ background: downloadCircleBackground }">
                    <div class="message-file-download-circle-inner" />
                </div>
                <img v-else class="message-file-icon" :src="getFileIconStyle(fileExt)" alt="file icon" />
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import toast from '../../../utils/toast'
import { parseUnknownError } from '../../../utils/parseUnknownError'
import { isPC } from '../../../utils/env'

import excelIcon from '../../../assets/icons/file-excel.svg'
import imageIcon from '../../../assets/icons/file-image.svg'
import pdfIcon from '../../../assets/icons/file-pdf.svg'
import pptIcon from '../../../assets/icons/file-ppt.svg'
import txtIcon from '../../../assets/icons/file-txt.svg'
import unknownIcon from '../../../assets/icons/file-unknown.svg'
import wordIcon from '../../../assets/icons/file-word.svg'
import zipIcon from '../../../assets/icons/file-zip.svg'

const fileExtToIcon = new Map<string, string>([
    ['ppt', pptIcon],
    ['pptx', pptIcon],
    ['docx', wordIcon],
    ['doc', wordIcon],
    ['xls', excelIcon],
    ['xlsx', excelIcon],
    ['txt', txtIcon],
    ['json', txtIcon],
    ['js', txtIcon],
    ['jsx', txtIcon],
    ['ts', txtIcon],
    ['tsx', txtIcon],
    ['java', txtIcon],
    ['cs', txtIcon],
    ['cpp', txtIcon],
    ['c', txtIcon],
    ['log', txtIcon],
    ['pdf', pdfIcon],
    ['zip', zipIcon],
    ['rar', zipIcon],
    ['7z', zipIcon],
    ['tar', zipIcon],
    ['jpg', imageIcon],
    ['bmp', imageIcon],
    ['gif', imageIcon],
    ['png', imageIcon],
    ['jpeg', imageIcon],
    ['tif', imageIcon],
    ['wmf', imageIcon],
    ['dib', imageIcon],
])

type MessageFileContent = {
    name: string
    url: string
    size: string | number
    uploadProgress?: number
}

type MessageFileProps = {
    fileContent: MessageFileContent
    isSeatSide: boolean
    previewImage?: () => void
    meidaMessageOnload?: () => void
}

const IMAGE_FILE_EXT_REGEXP = /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i
const VIDEO_FILE_EXT_REGEXP = /\.(mp4|webm|ogg|mov|m4v|avi|wmv|mkv)(\?.*)?$/i

const props = defineProps<MessageFileProps>()

const { t } = useI18n()
const downloadProgress = ref(-1)
const hasNotifiedVideoLoaded = ref(false)

const matchFileType = (value: string | undefined, regexp: RegExp) => {
    if (!value) {
        return false
    }

    return regexp.test(value)
}

const getFileName = (fileName: string) => {
    let showFileName = ''
    const disLength = fileName.length
    const substringC = 8
    let nameLength = 0
    let cutIndex = 0

    for (let i = 0; i < disLength; i += 1) {
        const currentChar = fileName.charAt(i)

        if (escape(currentChar).indexOf('%u') < 0) {
            nameLength += 1
        } else {
            nameLength += 2
        }

        if (nameLength > 8) {
            cutIndex = i
            break
        }
    }

    if (nameLength > substringC) {
        const reg = new RegExp(`^(^.{${cutIndex}})(.+)(.{2}\\.+\\w+$)`, 'g')
        showFileName = fileName.replace(reg, '$1...$3')
    } else {
        showFileName = fileName
    }

    return showFileName
}
const getFileIconStyle = (fileExt?: string) => {
    return fileExtToIcon.get(fileExt ?? '') ?? unknownIcon
}

const formatFileSize = (fileSize: string | number) => {
    const numericFileSize = Number(fileSize)

    if (!Number.isFinite(numericFileSize) || numericFileSize < 0) {
        return String(fileSize || '0 b')
    }

    if (numericFileSize === 0) {
        return '0 b'
    }

    const units = ['b', 'kb', 'mb', 'gb']
    let value = numericFileSize
    let unitIndex = 0

    while (value >= 1024 && unitIndex < units.length - 1) {
        value /= 1024
        unitIndex += 1
    }

    const fractionDigits = unitIndex === 0 ? 0 : value >= 100 ? 0 : value >= 10 ? 1 : 2
    const formattedValue = Number(value.toFixed(fractionDigits))

    return `${formattedValue} ${units[unitIndex]}`
}

const bubbleClass = computed(() => ({
    'is-in-message': !props.isSeatSide,
    'is-out-message': props.isSeatSide,
}))

const fileExt = computed(() => {
    const matched = props.fileContent.url.match(/\.([a-z0-9]+)(?:\?.*)?$/i)
    return matched?.[1]?.toLowerCase()
})
const isImageFile = computed(() => {
    return matchFileType(props.fileContent.url, IMAGE_FILE_EXT_REGEXP)
})
const isVideoFile = computed(() => {
    if (!props.fileContent.url || isImageFile.value) {
        return false
    }

    return matchFileType(props.fileContent.url, VIDEO_FILE_EXT_REGEXP)
})
const isDownloading = computed(() => downloadProgress.value >= 0 && props.fileContent.uploadProgress === undefined)
const displayFileSize = computed(() => formatFileSize(props.fileContent.size))
const downloadCircleBackground = computed(() => {
    const progress = Math.max(0, Math.min(downloadProgress.value, 100))
    const progressAngle = progress * 3.6

    if (progress <= 0) {
        return '#E6E9F0'
    }

    return `conic-gradient(from 0deg, #676A70 0deg ${progressAngle}deg, #E6E9F0 ${progressAngle}deg 360deg)`
})
const uploadWidth = computed(() => {
    const progress = props.fileContent.uploadProgress
    return progress !== undefined && progress >= 0 ? `${progress}%` : '0%'
})
const uploadText = computed(() => t('上传中'))

const handleImageLoaded = () => {
    props.meidaMessageOnload?.()
}

const handleImagePreview = () => {
    props.previewImage?.()
}

const handleVideoLoaded = () => {
    if (hasNotifiedVideoLoaded.value) {
        return
    }

    hasNotifiedVideoLoaded.value = true
    props.meidaMessageOnload?.()
}

const requestBlob = (url: string) => {
    return new Promise<Blob>((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)
        xhr.responseType = 'blob'

        xhr.onprogress = (progressEvent: ProgressEvent<EventTarget>) => {
            if (progressEvent.lengthComputable && progressEvent.total > 0) {
                downloadProgress.value = Math.round((progressEvent.loaded * 100) / progressEvent.total)
            } else {
                downloadProgress.value = -1
            }
        }

        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response)
                return
            }

            reject(new Error(`download failed with status ${xhr.status}`))
        }

        xhr.onerror = () => {
            reject(new Error('download file error'))
        }

        xhr.send()
    })
}

const downloadFile = async () => {
    if (
        props.fileContent.url === '' ||
        downloadProgress.value >= 0 ||
        (props.fileContent.uploadProgress !== undefined && props.fileContent.uploadProgress >= 0)
    ) {
        return
    }

    downloadProgress.value = 0

    try {
        const blob = await requestBlob(props.fileContent.url)

        downloadProgress.value = -1

        const objectUrl = window.URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = objectUrl
        a.download = props.fileContent.name
        a.click()
        window.URL.revokeObjectURL(objectUrl)
    } catch (error) {
        downloadProgress.value = -1
        toast.error(parseUnknownError(error))
    }
}
</script>

<style scoped>
.message-file-image-container {
    overflow: hidden;
    cursor: pointer;
}

.message-file-image-content {
    display: block;
    max-width: min(calc(100vw - 180px), 400px);
    max-height: min(calc(100vw - 180px), 400px);
    width: inherit;
    height: inherit;
}

.message-file-video-container {
    overflow: hidden;
}

.message-file-video-content {
    display: block;
    width: min(calc(100vw - 180px), 400px);
    max-width: min(calc(100vw - 180px), 400px);
    border-radius: 10px;
    background: #000;
}

.message-file-video-content.is-pc {
    max-width: min(calc(100vw - 180px), 400px);
    max-height: min(calc(100vw - 180px), 400px);
    width: inherit;
    height: inherit;
    background: transparent;
    outline: none;
}

.message-file-wrapper {
    position: relative;
    width: 240px;
}

.message-file-wrapper.is-in-message {
    background: #FFFFFF;
    border-radius: 0 10px 10px 10px;
    --message-file-circle-inner-bg: #FFFFFF;
}

.message-file-wrapper.is-out-message {
    background: #EEF0F5;
    border-radius: 10px 0 10px 10px;
    --message-file-circle-inner-bg: #EEF0F5;
}

.message-file-progress {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 1;
    height: 100%;
    background: rgba(210, 224, 250, 1);
}

.message-file-progress.is-in-message {
    border-radius: 0 10px 10px 10px;
}

.message-file-progress.is-out-message {
    border-radius: 10px 0 10px 10px;
}

.message-file-container {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 240px;
    padding: 12px;
    cursor: pointer;
    box-sizing: border-box;
}

.message-file-icon-wrapper {
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
}

.message-file-text-container {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    margin-right: 40px;
    height: 40px;
    min-width: 0;
}

.message-file-name {
    font-size: 14px;
    line-height: 20px;
}

.message-file-meta {
    font-size: 12px;
    line-height: 16px;
    color: rgba(114, 122, 138, 1);
}

.message-file-download-circle {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3px;
    box-sizing: border-box;
}

.message-file-download-circle-inner {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: var(--message-file-circle-inner-bg);
    display: flex;
    align-items: center;
    justify-content: center;
}

.message-file-icon {
    width: 40px;
    height: 40px;
}
</style>