<script setup lang="ts">
import type { UploadProps, UploadRequestOptions } from 'element-plus';

import type { PropType } from 'vue';

import type { AxiosProgressEvent } from '#/api/infra/file';

import { computed, getCurrentInstance, inject, onMounted, ref } from 'vue';

import { IconifyIcon } from '@vben/icons';
import { generateAcceptedFileTypes } from '@vben/utils';

import {
  ElButton,
  ElForm,
  ElFormItem,
  ElMessage,
  ElUpload,
} from 'element-plus';

import { useUpload } from '#/components/upload/use-upload';

const props = defineProps({
  modelValue: {
    type: Object as PropType<any>,
    required: true,
  },
});

const emit = defineEmits(['update:modelValue']);
const formRef = ref(); // 表单引用
const uploadRef = ref(); // 上传组件引用
const parent = inject('parent', null); // 获取父组件实例
const { uploadUrl, httpRequest } = useUpload(); // 使用上传组件的钩子
const fileList = ref<UploadProps['fileList']>([]); // 文件列表
const uploadingCount = ref(0); // 上传中的文件数量

const supportedFileTypes = [
  'TXT',
  'MARKDOWN',
  'MDX',
  'PDF',
  'HTML',
  'XLSX',
  'XLS',
  'DOC',
  'DOCX',
  'CSV',
  'EML',
  'MSG',
  'PPTX',
  'XML',
  'EPUB',
  'PPT',
  'MD',
  'HTM',
]; // 支持的文件类型和大小限制
const allowedExtensions = new Set(
  supportedFileTypes.map((ext) => ext.toLowerCase()),
); // 小写的扩展名列表
const maxFileSize = 15; // 最大文件大小(MB)
const acceptedFileTypes = computed(() =>
  generateAcceptedFileTypes(supportedFileTypes),
); // 构建 accept 属性值，用于限制文件选择对话框中可见的文件类型

/** 表单数据 */
const modelData = computed({
  get: () => {
    return props.modelValue;
  },
  set: (val) => emit('update:modelValue', val),
});

/** 确保 list 属性存在 */
function ensureListExists() {
  if (!props.modelValue.list) {
    emit('update:modelValue', {
      ...props.modelValue,
      list: [],
    });
  }
}

/** 是否所有文件都已上传完成 */
const isAllUploaded = computed(() => {
  return (
    modelData.value.list &&
    modelData.value.list.length > 0 &&
    uploadingCount.value === 0
  );
});

/**
 * 上传前检查文件类型和大小
 *
 * @param file 待上传的文件
 * @returns 是否允许上传
 */
function beforeUpload(file: any) {
  // 1.1 检查文件扩展名
  const fileName = file.name.toLowerCase();
  const fileExtension = fileName.slice(
    Math.max(0, fileName.lastIndexOf('.') + 1),
  );
  if (!allowedExtensions.has(fileExtension)) {
    ElMessage.error('不支持的文件类型！');
    return false;
  }
  // 1.2 检查文件大小
  if (!(file.size / 1024 / 1024 < maxFileSize)) {
    ElMessage.error(`文件大小不能超过 ${maxFileSize} MB！`);
    return false;
  }

  // 2. 增加上传中的文件计数
  uploadingCount.value++;
  return true;
}

async function customRequest(info: UploadRequestOptions) {
  const file = info.file as File;
  const name = file?.name;
  try {
    // 上传文件
    const progressEvent: AxiosProgressEvent = (e) => {
      const percent = Math.trunc((e.loaded / e.total!) * 100);
      info.onProgress!({ percent });
    };
    const res = await httpRequest(info.file as File, progressEvent);
    info.onSuccess!(res);
    ElMessage.success('上传成功');
    ensureListExists();
    emit('update:modelValue', {
      ...props.modelValue,
      list: [
        ...props.modelValue.list,
        {
          name,
          url: res,
        },
      ],
    });
  } catch (error: any) {
    console.error(error);
    info.onError!(error);
  } finally {
    uploadingCount.value = Math.max(0, uploadingCount.value - 1);
  }
}

/**
 * 从列表中移除文件
 *
 * @param index 要移除的文件索引
 */
function removeFile(index: number) {
  // 从列表中移除文件
  const newList = [...props.modelValue.list];
  newList.splice(index, 1);
  // 更新表单数据
  emit('update:modelValue', {
    ...props.modelValue,
    list: newList,
  });
}

/** 下一步按钮处理 */
function handleNextStep() {
  // 1.1 检查是否有文件上传
  if (!modelData.value.list || modelData.value.list.length === 0) {
    ElMessage.warning('请上传至少一个文件');
    return;
  }
  // 1.2 检查是否有文件正在上传
  if (uploadingCount.value > 0) {
    ElMessage.warning('请等待所有文件上传完成');
    return;
  }

  // 2. 获取父组件的goToNextStep方法
  const parentEl = parent || getCurrentInstance()?.parent;
  if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
    parentEl.exposed.goToNextStep();
  }
}

/** 初始化 */
onMounted(() => {
  ensureListExists();
});
</script>

<template>
  <ElForm ref="formRef" :model="modelData" label-width="0" class="mt-5">
    <ElFormItem class="mb-5">
      <div class="w-full">
        <div
          class="w-full rounded-md border-2 border-dashed border-gray-200 p-5 text-center hover:border-blue-500"
        >
          <ElUpload
            ref="uploadRef"
            class="upload-demo"
            :action="uploadUrl"
            v-model:file-list="fileList"
            :accept="acceptedFileTypes"
            :show-file-list="false"
            :before-upload="beforeUpload"
            :http-request="customRequest"
            :multiple="true"
            drag
          >
            <div class="flex flex-col items-center justify-center py-5">
              <IconifyIcon
                icon="ep:upload-filled"
                class="mb-2.5 text-xs text-gray-400"
              />
              <div class="ant-upload-text text-base text-gray-400">
                拖拽文件至此，或者
                <em class="cursor-pointer not-italic text-blue-500">
                  选择文件
                </em>
              </div>
              <div class="mt-2.5 text-sm text-gray-400">
                已支持 {{ supportedFileTypes.join('、') }}，每个文件不超过
                {{ maxFileSize }} MB。
              </div>
            </div>
          </ElUpload>
        </div>
        <div
          v-if="modelData.list && modelData.list.length > 0"
          class="mt-4 grid grid-cols-1 gap-2"
        >
          <div
            v-for="(file, index) in modelData.list"
            :key="index"
            class="flex items-center justify-between rounded-sm border-l-4 border-l-blue-500 px-3 py-1 shadow-sm transition-all duration-300 hover:bg-blue-50"
          >
            <div class="flex items-center">
              <IconifyIcon icon="lucide:file-text" class="mr-2 text-blue-500" />
              <span class="break-all text-sm text-gray-600">
                {{ file.name }}
              </span>
            </div>
            <ElButton
              type="danger"
              text
              link
              @click="removeFile(index)"
              class="ml-2"
            >
              <IconifyIcon icon="lucide:trash-2" />
            </ElButton>
          </div>
        </div>
      </div>
    </ElFormItem>
    <ElFormItem>
      <div class="flex w-full justify-end">
        <ElButton
          type="primary"
          @click="handleNextStep"
          :disabled="!isAllUploaded"
        >
          下一步
        </ElButton>
      </div>
    </ElFormItem>
  </ElForm>
</template>
