<template>
  <div class="file-upload">
    <a-input
      v-model.trim="attachmentName"
      placeholder="文件名称"
      readonly
    ></a-input>
    <a-upload
      ref="uploadRef"
      :action="uploadUrl"
      :headers="headers"
      :max-size="maxSize"
      :format="format"
      :data="uploadDataDict"
      :show-file-list="false"
      :auto-upload="autoUpload"
      :custom-request="customRequest"
      :disabled="disabled"
      :on-before-upload="handleBeforeUpload"
      :limit="1"
      @change="handleFileChange"
      @error="handleError"
      @success="handleSuccess"
    >
      <template #upload-button>
        <a-button type="primary" :disabled="loading">选择</a-button>
        <a-button
          v-if="!autoUpload"
          type="primary"
          :loading="loading"
          :disabled="!canUpload"
          @click.stop="startUpload"
          >开始上传</a-button
        >
      </template>
    </a-upload>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue';
  import { fileMD5 } from '@/utils/md5';
  import {
    FileItem,
    Message,
    RequestOption,
    UploadRequest,
  } from '@arco-design/web-vue';
  import axios from 'axios';

  defineOptions({
    name: 'FileUpload',
  });

  const props = withDefaults(
    defineProps<{
      uploadUrl: string;
      format?: string[];
      uploadData?: Record<string, any>;
      maxSize?: number;
      uploadFileAlias?: string;
      autoUpload?: boolean;
      disabled?: boolean;
    }>(),
    {
      uploadUrl: '',
      format: () => ['xls', 'xlsx'],
      uploadData: () => {
        return {};
      },
      maxSize: 20 * 1024 * 1024,
      uploadFileAlias: 'file',
      autoUpload: true,
      disabled: false,
    }
  );

  const emit = defineEmits([
    'uploading',
    'uploadError',
    'uploadSuccess',
    'validError',
  ]);

  const uploadRef = ref();
  const attachmentName = ref('');
  const canUpload = ref(false);
  const uploadDataDict = ref({});
  const headers = ref({ md5: '' });
  const result = ref({ success: true, message: '' });
  const loading = ref(false);

  function startUpload() {
    loading.value = true;
    uploadRef.value?.submit();
  }

  function checkFileFormat(fileType: string) {
    const fileFormat = fileType.split('.').pop()?.toLocaleLowerCase();
    return props.format.some((item) => item.toLocaleLowerCase() === fileFormat);
  }

  function handleFileChange(fileList: FileItem[]) {
    if (props.autoUpload || !fileList.length) return;

    attachmentName.value = fileList[0]?.name || '';
    canUpload.value = fileList[0]?.status === 'init';
  }

  async function handleBeforeUpload(file: File) {
    uploadDataDict.value = {
      ...props.uploadData,
      filename: file.name,
    };

    if (file.size > props.maxSize) {
      handleExceededSize();
      return Promise.reject(result.value);
    }

    if (!checkFileFormat(file.name)) {
      handleFormatError();
      return Promise.reject(result.value);
    }

    const md5 = await fileMD5(file);
    headers.value.md5 = md5;

    if (props.autoUpload) loading.value = true;

    return true;
  }

  function customRequest(option: RequestOption): UploadRequest {
    const { onProgress, onError, onSuccess, fileItem, data } = option;

    const formData = new FormData();
    const paramData: Record<string, any> = data || {};
    Object.entries(paramData).forEach(([k, v]) => {
      formData.append(k, v);
    });
    formData.append(props.uploadFileAlias, fileItem.file as File);
    emit('uploading');
    const uploadController = new AbortController();

    axios
      .post(option.action as string, formData, {
        headers: option.headers,
        signal: uploadController.signal,
        onUploadProgress: ({ loaded, total }) => {
          onProgress(Math.floor((100 * loaded) / total));
        },
      })
      .then((res) => {
        onSuccess(res);
      })
      .catch((error: Error) => {
        onError(error);
      });

    return {
      abort: uploadController.abort,
    };
  }

  function handleError() {
    canUpload.value = false;
    loading.value = false;
    result.value = {
      success: false,
      message: '上传失败',
    };
    emit('uploadError', result.value);
  }
  function handleSuccess(fileItem: FileItem) {
    canUpload.value = false;
    loading.value = false;
    result.value = {
      success: true,
      message: '上传成功！',
    };
    Message.success('上传成功！');
    emit('uploadSuccess', {
      ...result.value,
      filename: fileItem.name,
      response: fileItem.response,
    });
  }

  function handleFormatError() {
    const content = `只支持文件格式为${props.format.join('/')}`;
    result.value = {
      success: false,
      message: content,
    };
    loading.value = false;
    Message.error(content);
    emit('validError', result.value);
  }
  function handleExceededSize() {
    const content = `文件大小不能超过${Math.floor(props.maxSize / 1024)}M`;
    result.value = {
      success: false,
      message: content,
    };
    loading.value = false;
    Message.error(content);
    emit('validError', result.value);
  }
  function setAttachmentName(name: string) {
    attachmentName.value = name;
  }

  defineExpose({ setAttachmentName });
</script>

<style lang="less">
  .file-upload {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;

    .arco-input-wrapper {
      flex-grow: 2;
      margin-right: 10px;
    }
    .arco-upload {
      flex-grow: 0;
      flex-shrink: 0;
    }
  }
</style>
