<template>
  <a-modal
    v-model:open="visible"
    :width="422"
    :title="title"
    :mask-closable="false"
    :esc-to-close="false"
  >
    <slot></slot>
    <a-form :label-col="{ style: { width: '80px' } }" label-align="left">
      <a-form-item label="选择文件">
        <a-upload
          v-if="visible"
          ref="uploadRef"
          :action="uploadUrl"
          :headers="headers"
          :accept="accept"
          :data="uploadDataDict"
          :show-upload-list="false"
          :custom-request="customRequest"
          :disabled="disabled || loading"
          :before-upload="handleBeforeUpload"
          @change="handleFileChange"
        >
          <a-button>
            <template #icon><ImportOutlined /></template>点击导入
          </a-button>
        </a-upload>

        <a-progress v-if="loading" :percent="uploadProgress" />

        <template #help>
          <p
            v-if="result.message && !result.success"
            class="tips-info tips-error"
          >
            {{ result.message }}
          </p>
        </template>
      </a-form-item>
      <a-form-item label="下载模板" v-if="showDownload">
        <a-button :loading="downloading" @click="onDownloadTemplate">
          <template #icon><DownloadOutlined /></template>{{ downloadBtnTitle }}
        </a-button>
      </a-form-item>
    </a-form>

    <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, watch } from "vue";
import { DownloadOutlined, ImportOutlined } from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import type { UploadProps } from "ant-design-vue";

import type { AxiosError, AxiosProgressEvent } from "axios";
import { request } from "@/utils/request";
import { getFileMD5 } from "@/utils/crypto";

import useModal from "@/hooks/useModal";
import { downloadByUrl } from "@/utils/tool";

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;
    autoUpload?: boolean;
    disabled?: boolean;
    uploadFileAlias?: string;
    downloadBtnTitle?: string;
    downloadUrl?: string;
    downloadFilename?: string;
    downloadHandle?: PromiseFunc;
    beforeSubmitHandle?: PromiseFunc;
    showDownload?: boolean;
  }>(),
  {
    title: "文件上传",
    uploadUrl: "",
    format: () => ["xls", "xlsx"],
    uploadData: () => {
      return {};
    },
    maxSize: 20 * 1024 * 1024,
    uploadFileAlias: "file",
    autoUpload: true,
    disabled: false,
    downloadBtnTitle: "导入模板",
    showDownload: true,
  }
);

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 uploadProgress = ref(0);
const curFileUid = ref("");

const accept = computed(() => {
  return props.format.map((el) => `.${el}`).join();
});

const downloading = ref(false);
async function onDownloadTemplate() {
  if (props.downloadHandle) {
    if (downloading.value) return;
    downloading.value = true;
    await props.downloadHandle().catch(() => false);
    downloading.value = false;
    return;
  }

  if (props.downloadUrl) {
    downloadByUrl(props.downloadUrl, props.downloadFilename || "");
    return;
  }
}

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);
}

const handleFileChange: UploadProps["onChange"] = ({ file, fileList }) => {
  if (!fileList.length) {
    curFileUid.value = "";
    result.value = {
      success: true,
      message: "",
    };
    return;
  }

  if (curFileUid.value !== file.uid) {
    result.value = {
      success: true,
      message: "",
    };
  }
  curFileUid.value = file.uid;
  canUpload.value = file.status === "uploading";
};

const handleBeforeUpload: UploadProps["beforeUpload"] = async (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 getFileMD5(file);
  headers.value.md5 = md5;

  if (props.autoUpload) {
    loading.value = true;
    return true;
  }

  return false;
};

const customRequest: UploadProps["customRequest"] = (option) => {
  const { file, 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, file as File);
  emit("uploading");

  (
    request({
      url: option.action as string,
      data: formData,
      headers: option.headers,
      onUploadProgress: (data: AxiosProgressEvent) => {
        uploadProgress.value = data.total
          ? Math.floor((100 * data.loaded) / data.total)
          : 0;
      },
    }) as Promise<Record<string, any>>
  )
    .then((res) => {
      handleSuccess(res);
    })
    .catch((error: AxiosError<{ message: string }> | null) => {
      handleError(error?.response?.data?.message);
    });
};

function handleError(message: string | undefined) {
  canUpload.value = false;
  loading.value = false;
  result.value = {
    success: false,
    message: message || "上传错误",
  };
  emit("uploadError", result.value);
}
function handleSuccess(data: Record<string, any>) {
  canUpload.value = false;
  loading.value = false;
  result.value = {
    success: true,
    message: "上传成功！",
  };
  emit("uploadSuccess", {
    ...result.value,
    ...data,
  });
  close();
}

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

/* init modal */
watch(
  () => visible.value,
  (val) => {
    if (val) {
      modalOpenHandle();
    }
  },
  {
    immediate: true,
  }
);

function modalOpenHandle() {
  canUpload.value = false;
  result.value = {
    success: true,
    message: "",
  };
  headers.value = { md5: "" };
  loading.value = false;
  uploadDataDict.value = {};
  curFileUid.value = "";
}
</script>
