<template>
  <el-upload
    v-model:file-list="fileList"
    action="#"
    ref="uploadRef"
    :auto-upload="false"
    list-type="picture-card"
    :on-preview="handlePictureCardPreview"
    :on-remove="handleRemove"
    :on-change="handleChange"
    :before-remove="handleBeforeRemove"
    :on-exceed="handleExceed"
    :multiple="multiple"
    :limit="limit"
    class="custom-upload"
    v-loading="uploadPercent > 0"
    :element-loading-text="uploadPercent + '%'"
  >
    <el-icon><Plus /></el-icon>
    <el-dialog :append-to-body="true" v-model="dialogVisible">
      <img class="w-full" :src="dialogImageUrl" alt="Preview Image" />
    </el-dialog>
  </el-upload>
</template>

<script lang="ts" setup generic="T extends string | string[]">
import { Plus } from '@element-plus/icons-vue'
import { uploadFile as uploadFileApi } from '@/api'
import type { UploadProps, UploadUserFile } from 'element-plus'
import type { UploadFileProps } from './types'
import { push } from 'notivue'
import { useMessageBox } from '@/hooks'

const props = withDefaults(defineProps<UploadFileProps>(), {
  limit: 2,
  multiple: false,
})

const modelValue = defineModel<T>({
  required: true,
})
const { confirm } = useMessageBox()

const fileList = ref<UploadUserFile[]>([])
const uploadRef = useTemplateRef('uploadRef')
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const uploadPercent = ref(0)

const isArrayType = computed(() => Array.isArray(modelValue.value))
const hasReachedLimit = computed(() => fileList.value.length >= props.limit)
const showUploadBtn = computed(() => (hasReachedLimit.value ? 'none' : 'flex'))
const isInternalUpdate = ref(false)

const parseModelValueToUrls = (value: T): string[] => {
  if (!value) return []
  return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean)
}

const formatUrlsToModelValue = (urls: string[]): T => {
  return (isArrayType.value ? urls : urls.join(',')) as T
}

const syncFileListToModel = () => {
  if (isInternalUpdate.value) return

  const urls = fileList.value
    .filter((file) => file.status === 'success' && file.url)
    .map((file) => file.url!)

  isInternalUpdate.value = true
  modelValue.value = formatUrlsToModelValue(urls)
  nextTick(() => {
    isInternalUpdate.value = false
  })
}

const syncModelToFileList = () => {
  const urls = parseModelValueToUrls(modelValue.value)
  fileList.value = urls.map((url, index) => ({
    uid: Date.now() + index,
    name: url.split('/').pop() || `file-${index}`,
    url,
    status: 'success' as const,
  }))
}

watch(
  () => modelValue.value,
  (newVal) => {
    if (isInternalUpdate.value) return
    if (newVal !== undefined && newVal !== null) {
      syncModelToFileList()
    }
  },
  { immediate: true },
)

watch(
  fileList,
  () => {
    syncFileListToModel()
  },
  { deep: true },
)

const handleExceed: UploadProps['onExceed'] = (uploadFiles) => {
  if (uploadFiles.length > props.limit) {
    push.error(`最多上传 ${props.limit} 个文件`)
    return
  }
}

const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => {
  if (uploadFiles.length > props.limit) {
    push.error(`最多上传 ${props.limit} 个文件`)
    const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid)
    if (index !== -1) {
      fileList.value.splice(index, 1)
    }
    return
  }

  if (uploadFile.raw && uploadFile.status === 'ready') {
    const uid = uploadFile.uid

    try {
      let fileIndex = fileList.value.findIndex((file) => file.uid === uid)
      if (fileIndex !== -1) {
        fileList.value[fileIndex]!.status = 'uploading'
      }

      const { promise } = uploadFileApi(uploadFile.raw, {
        onProgress: (progress) => {
          uploadPercent.value = progress
        },
      })

      const data = await promise

      const url = data.filePath || ''
      const name = data.finalName || ''

      fileIndex = fileList.value.findIndex((file) => file.uid === uid)

      if (fileIndex !== -1) {
        fileList.value[fileIndex] = {
          ...fileList.value[fileIndex],
          url,
          name,
          status: 'success',
        }
        push.success('上传成功')
      } else {
        console.warn('找不到对应的文件，uid:', uid)
      }
    } catch (error) {
      console.error('上传失败:', error)
      push.error('上传失败，请重试')

      const fileIndex = fileList.value.findIndex((file) => file.uid === uid)
      if (fileIndex !== -1) {
        fileList.value.splice(fileIndex, 1)
      }
    } finally {
      uploadPercent.value = 0
    }
  }
}

const handleBeforeRemove: UploadProps['beforeRemove'] = () => {
  return confirm({
    title: '确定要删除这个文件吗？',
    confirmText: '确定',
    cancelText: '取消',
    type: 'danger',
  })
    .then(() => true)
    .catch(() => false)
}

const handleRemove: UploadProps['onRemove'] = (uploadFile) => {
  console.log('文件已删除:', uploadFile.name)
}

const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
  dialogImageUrl.value = uploadFile.url!
  dialogVisible.value = true
}

const clearFiles = () => {
  fileList.value = []
}

const submit = () => {
  uploadRef.value?.submit()
}

defineExpose({
  clearFiles,
  submit,
})
</script>

<style scoped>
.custom-upload :deep(.el-upload--picture-card) {
  display: v-bind(showUploadBtn);
}
/* 方案1: 适中尺寸（推荐） */
.custom-upload :deep(.el-upload--picture-card) {
  width: 100px;
  height: 100px;
  line-height: 100px;
}

.custom-upload :deep(.el-upload-list--picture-card .el-upload-list__item) {
  width: 100px;
  height: 100px;
}

/* 方案2: 更小尺寸 */
/* .custom-upload :deep(.el-upload--picture-card) {
  width: 80px;
  height: 80px;
  line-height: 80px;
}

.custom-upload :deep(.el-upload-list--picture-card .el-upload-list__item) {
  width: 80px;
  height: 80px;
}

.custom-upload :deep(.el-icon) {
  font-size: 20px;
} */

/* 方案3: 迷你尺寸 */
/* .custom-upload :deep(.el-upload--picture-card) {
  width: 60px;
  height: 60px;
  line-height: 60px;
}

.custom-upload :deep(.el-upload-list--picture-card .el-upload-list__item) {
  width: 60px;
  height: 60px;
}

.custom-upload :deep(.el-icon) {
  font-size: 16px;
} */
</style>
