<template>
  <a-modal
    v-model:visible="visible"
    :width="422"
    :title="title"
    title-align="start"
    :mask-closable="false"
    :esc-to-close="false"
    @before-open="modalBeforeOpen"
  >
    <slot></slot>
    <div class="import-box">
      <div class="import-temp">
        <span>模板下载：</span>
        <div class="temp-btn">
          <a v-if="downloadUrl" :href="downloadUrl" :download="dfilename">{{
            dfilename
          }}</a>
          <a-button
            v-else-if="downloadHandle"
            type="text"
            @click="downloadHandle"
            >{{ dfilename }}</a-button
          >
        </div>
      </div>
      <div class="import-body">
        <a-upload
          v-if="visible"
          ref="uploadRef"
          draggable
          :action="uploadUrl"
          :headers="headers"
          :max-size="maxSize"
          :format="format"
          :accept="accept"
          :data="uploadDataDict"
          :show-file-list="true"
          :auto-upload="autoUpload"
          :custom-request="customRequest"
          :disabled="disabled"
          :on-before-upload="handleBeforeUpload"
          :file-list="customFileList"
          :show-retry-button="false"
          @change="handleFileChange"
          @error="handleError"
          @success="handleSuccess"
        >
          <template #file-icon>
            <svg-icon name="icon-file"></svg-icon>
          </template>
          <template #success-icon>
            <svg-icon name="icon-success"></svg-icon>
          </template>
          <template #error-icon>
            <svg-icon name="icon-error"></svg-icon>
          </template>
          <template #remove-icon>
            <svg-icon name="icon-delete"></svg-icon>
          </template>
        </a-upload>
        <p
          v-if="result.message && !result.success"
          class="tips-info tips-error"
        >
          {{ result.message }}
        </p>
      </div>
    </div>

    <template v-if="!autoUpload" #footer>
      <a-button @click="close">取消</a-button>
      <a-button
        type="primary"
        :disabled="loading || !canUpload"
        @click="confirm"
        >确认</a-button
      >
    </template>
  </a-modal>
</template>

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

  import useModal from '@/hooks/modal';

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

  /* modal */
  const { visible, open, close } = useModal();
  defineExpose({ open, close });

  interface PromiseFunc {
    (): Promise<void>;
  }

  const props = withDefaults(
    defineProps<{
      title: string;
      uploadUrl: string;
      format?: string[];
      uploadData?: Record<string, any>;
      maxSize?: number;
      uploadFilename?: string;
      autoUpload?: boolean;
      disabled?: boolean;
      uploadFileAlias?: string;
      downloadUrl?: string;
      downloadFilename?: string;
      downloadHandle?: PromiseFunc;
      beforeSubmitHandle?: PromiseFunc;
    }>(),
    {
      title: '文件上传',
      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 canUpload = ref(false);
  const uploadDataDict = ref({});
  const headers = ref({ md5: '' });
  const result = ref({ success: true, message: '' });
  const loading = ref(false);
  const customFileList = ref<FileItem[]>([]);

  const dfilename = computed(() => {
    if (props.downloadFilename) return props.downloadFilename;
    return props.downloadUrl ? props.downloadUrl.split('/').pop() : '';
  });
  const accept = computed(() => {
    return props.format.map((el) => `.${el}`).join();
  });

  async function confirm() {
    loading.value = true;
    if (props.beforeSubmitHandle) {
      let handleResult = true;
      await props.beforeSubmitHandle().catch(() => {
        handleResult = false;
      });
      if (!handleResult) return;
    }
    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 (fileList.length) {
      customFileList.value = fileList.slice(-1);
      const lastUid = customFileList.value[0]?.uid;
      if (lastUid !== fileList[0].uid) {
        result.value = {
          success: true,
          message: '',
        };
      }
    } else {
      customFileList.value = [];
      result.value = {
        success: true,
        message: '',
      };
    }
    canUpload.value = customFileList.value[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;
  }

  interface UploadResultType {
    hasError: boolean;
    failRecords: Array<{
      msg: string;
      lineNum: number;
    }>;
  }

  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));
        },
      }) as Promise<UploadResultType>
    )
      .then((res) => {
        // 所有excel导入的特殊处理
        if (res.hasError) {
          const failRecords = res.failRecords;
          const message = failRecords
            .map((item) => `第${item.lineNum}行：${item.msg}`)
            .join('。');

          onError({ data: { message } });
          return;
        }
        onSuccess(res);
      })
      .catch((error: AxiosError) => {
        onError(error.response);
      });

    return {
      abort: uploadController.abort,
    };
  }

  function handleError(fileItem: FileItem) {
    canUpload.value = false;
    loading.value = false;
    result.value = {
      success: false,
      message: fileItem.response.data?.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 modalBeforeOpen() {
    canUpload.value = false;
    result.value = {
      success: true,
      message: '',
    };
    headers.value = { md5: '' };
    loading.value = false;
    uploadDataDict.value = {};
    customFileList.value = [];
  }
</script>

<style lang="less">
  .import-box {
    .import-temp {
      display: flex;
      justify-content: space-between;
      margin-bottom: 10px;

      > span {
        flex-grow: 0;
        flex-shrink: 0;
        height: 20px;
        line-height: 20px;
        display: block;
      }

      .temp-btn {
        flex-grow: 2;
        text-align: left;

        > a {
          flex-grow: 2;
          line-height: 20px;
          color: var(--color-primary);

          &:hover {
            text-decoration: underline;
            opacity: 0.8;
          }
        }
      }
      .arco-btn {
        line-height: 20px;
        height: auto;
        padding: 0;
        background: transparent;
        border: none;

        &:hover {
          text-decoration: underline;
          opacity: 0.8;
        }
      }
    }
    .arco-upload-drag {
      padding: 40px 0;
      > div:first-child {
        height: 54px;
        background-image: url(assets/images/upload-icon.png);
        background-size: auto 100%;
        background-repeat: no-repeat;
        background-position: center;
        margin-bottom: 16px;
      }
      svg {
        display: none;
      }
    }

    .arco-upload-list-item {
      margin-top: 8px !important;
      background-color: var(--color-fill-1);
      border-radius: var(--border-radius-small);
      .arco-upload-list-item-operation {
        margin: 0 12px;
      }

      .svg-icon {
        vertical-align: -2px;
      }
      .arco-upload-list-item-file-icon {
        margin-right: 6px;
        color: inherit;
      }

      &.arco-upload-list-item-error {
        .arco-upload-list-item-file-icon {
          color: var(--color-danger);
        }
      }
    }
    .arco-upload-progress {
      > * {
        display: none;
      }
      .arco-upload-icon-success {
        display: block;
      }
    }

    .tips-info {
      max-height: 100px;
      overflow: hidden;
      margin-top: 5px;
    }
  }
</style>
