<template>
  <div class="mm-upload">
    <input
      v-show="false"
      ref="input"
      :disabled="disabled"
      :multiple="multiple"
      :accept="acceptType"
      type="file"
      @change="handleChange" />
    <div
      v-if="drag && !hiddenAppend"
      class="mm-upload-drag"
      @dragleave.prevent.stop="handleDragLeave"
      @dragover.prevent.stop="handleDragOver"
      @drop.prevent.stop="handleDrop"
      @click.prevent.stop="triggerUpload">
      <slot name="trigger">
        <ui-icon
          name="upload"
          size="28px"
          class="mm-upload-drag-icon"></ui-icon>
        <span>
          {{ t("ui.upload.dragAndDrop") }}
          <a>
            {{ t("ui.upload.click") }}
          </a>
        </span>
      </slot>
      <slot name="trigger-desc"></slot>
    </div>
    <div
      v-else-if="listType !== 'pictureCard' && !hiddenAppend"
      class="mm-upload-action-icon-wrap"
      @click.stop="triggerUpload">
      <slot name="trigger">
        <ui-button
          class="mm-button"
          type="primary">
          {{ t("ui.upload.clickUpload") }}
        </ui-button>
      </slot>
    </div>
    <div
      v-if="!auto"
      class="mm-upload-action-icon-wrap"
      @click.stop="startUpload">
      <slot name="manual-trigger">
        <ui-button
          class="mm-button mm-upload-start-upload"
          ghost
          type="primary">
          {{ t("ui.upload.startUpload") }}
        </ui-button>
      </slot>
    </div>
    <component
      :is="listComponentType"
      :list="uploadTasks"
      :has-preview="hasPreview"
      @deleted="handleDelete"
      @click="handlePreview">
      <template #file="{ file, index, deleteHandle }">
        <slot
          name="thumbnail"
          :file="file"
          :index="index"
          :deleteHandle="deleteHandle"></slot>
      </template>
    </component>
    <div
      v-if="listType === 'pictureCard' && !hiddenAppend"
      class="mm-upload-action-icon-wrap mm-upload-picture-card-trigger-wrap"
      @click.stop="triggerUpload">
      <slot name="trigger">
        <div class="mm-upload-picture-card-trigger">
          <ui-icon
            :name="'add'"
            :size="'28px'"
            class="mm-upload-picture-card-icon"></ui-icon>
        </div>
      </slot>
    </div>
  </div>
</template>

<script>
import Input from "../input"
import Button from "../button"
import Icon from "../icon"

import locale from "../../mixins/locale"

import TextList from "./components/text-list"
import PictureList from "./components/picture-list"
import PictureCardList from "./components/picture-card-list"
import upload from "./scripts/upload"
import { getSizeByte } from "./scripts/util"

import config from "../../managers/config"

config.defaults({
  "Upload.multiple": false,
  "Upload.drag": false,
  "Upload.auto": true,
  "Upload.listType": "text",
  "Upload.headers": () => ({}),
  "Upload.withCredentials": false,
  "Upload.limit": Infinity,
  "Upload.maxSize": undefined,
  "Upload.beforeUpload": () => Promise.resolve(),
  "Upload.customRequest": upload,
})

const UPLOAD_STATUS = {
  padding: "padding",
  uploading: "uploading",
  error: "error",
  success: "success",
  exceed: "exceed",
}

const UPLOAD_ITEM = {
  file: null,
  progress: 0,
  status: "",
  xhr: null,
}

const LIST_COMPONENT_TYPES = {
  text: "text-list",
  picture: "picture-list",
  pictureCard: "picture-card-list",
}

export default {
  name: "mm-upload",
  components: {
    "ui-button": Button,
    "ui-input": Input,
    "ui-icon": Icon,
    "text-list": TextList,
    "picture-list": PictureList,
    "picture-card-list": PictureCardList,
  },
  mixins: [locale],
  props: {
    url: String,
    name: {
      type: String,
      default: "file",
    },
    extraData: Object,
    accept: String,
    multiple: {
      type: Boolean,
      default: () => config.get("Upload.multiple"),
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    drag: {
      type: Boolean,
      default: () => config.get("Upload.drag"),
    },
    auto: {
      type: Boolean,
      default: () => config.get("Upload.auto"),
    },
    listType: {
      type: String,
      default: () => config.get("Upload.listType"),
      validator(value) {
        return ["picture", "pictureCard", "text"].includes(value)
      },
    },
    headers: {
      type: Object,
      default() {
        return config.get("Upload.headers").call(this)
      },
    },
    withCredentials: {
      type: Boolean,
      default: () => config.get("Upload.withCredentials"),
    },
    limit: {
      type: Number,
      default: () => config.get("Upload.limit"),
    },
    maxSize: {
      type: String,
      default: () => config.get("Upload.maxSize"),
    },
    onChange: {
      type: Function,
      default() {
        return Promise.resolve()
      },
    },
    beforeUpload: {
      type: Function,
      default() {
        return config.get("Upload.beforeUpload").call(this)
      },
    },
    onProgress: {
      type: Function,
      default() {},
    },
    onSuccess: {
      type: Function,
      default() {},
    },
    onError: {
      type: Function,
      default() {},
    },
    onExceed: {
      type: Function,
      default() {},
    },
    onOverSize: {
      type: Function,
      default() {},
    },
    onPreview: {
      type: Function,
      default() {},
    },
    onRemove: {
      type: Function,
      default() {},
    },
    customRequest: {
      type: Function,
      default() {
        return config.get("Upload.customRequest").call(this)
      },
    },
    defaultFileList: {
      type: Array,
      default: () => ([]),
    },
    hasPreview: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      /**
       * upload tasks list
       * @type {Array}
       * @example
       * [{
       *   file: File Object,
       *   progress: Uploading progress,
       *   status: 'padding | uploading | error | success | exceed',
       *   xhr: A instance of upload XMLHttpRequest
       * }]
       */
      uploadTasks: this.defaultFileList.map((item) => ({
        file: item,
        status: "success",
      })),
      hiddenAppend: false,
    }
  },
  computed: {
    listComponentType() {
      return LIST_COMPONENT_TYPES[this.listType]
    },
    acceptType() {
      return ["picture", "pictureCard"].includes(this.listType) ? "image/*" : this.accept
    },
  },
  watch: {
    uploadTasks: {
      immediate: true,
      handler(newV) {
        this.hiddenAppend = this.limit <= newV.length
      },
    },
  },
  methods: {
    triggerUpload() {
      this.$refs.input.click()
    },
    handleChange(e) {
      let { target: { files } } = e
      files && this.whenGetFiles(e, files)
    },
    whenGetFiles(event, files) {
      files = Array.prototype.slice.call(files)
      if (this.isExceed(this.uploadTasks.length + files.length) || this.hasOverSize(files)) {
        return
      }

      this.onChange(event, files)

      for (let file of files) {
        this.uploadTasks.push({
          ...UPLOAD_ITEM,
          file,
        })
      }

      if (this.auto) {
        this.startUpload()
      }

      this.$refs.input.value = ""
    },
    isExceed(count) {
      if (count > this.limit) {
        this.onExceed(count, this.limit)
        return true
      }
      return false
    },
    hasOverSize(files) {
      if (!this.maxSize) {
        return false
      }
      const maxSize = getSizeByte(this.maxSize)
      let overSizeStack = []

      files.forEach(item => {
        if (item.size > maxSize) {
          overSizeStack.push(item)
        }
      })

      if (overSizeStack.length) {
        this.onOverSize(this.overSizeStack)
        return true
      }
      return false
    },
    startUpload() {
      const tasks = this.uploadTasks.filter(item => !item.status)
      tasks.forEach(task => {
        const { file } = task
        const beforeUpload = this.beforeUpload(file)
        if (beforeUpload.then) {
          beforeUpload.then(
            resFile => {
              this.uploadFile(resFile || file, task)
            },
            () => this.handleDelete(task)
          )
        } else if (beforeUpload) {
          this.uploadFile(file, task)
        } else {
          this.handleDelete(task)
        }
      })
    },
    uploadFile(file, task) {
      const { url, withCredentials, headers, name, extraData } = this
      task.status = UPLOAD_STATUS.padding

      let uploadOptions = {
        url,
        filename: name,
        file,
        extraData,
        withCredentials,
        headers,
        onProgress: progress => {
          task.progress = Math.ceil(progress)
          task.status = UPLOAD_STATUS.uploading
          this.onProgress(task)
        },
        onSuccess: res => {
          task.progress = 100
          task.status = UPLOAD_STATUS.success
          task.response = res
          this.onSuccess(task)
        },
        onError: err => {
          task.status = UPLOAD_STATUS.error
          this.onError(task, err)
        },
      }
      task.xhr = this.customRequest(uploadOptions)
    },
    handleDelete(index) {
      this.onRemove(this.uploadTasks.splice(index, 1)[0], this.uploadTasks)
    },
    handlePreview(index) {
      this.onPreview(this.uploadTasks[index])
    },
    handleDragOver({ target, dataTransfer }) {
      target.classList.add("hover")
      dataTransfer.dropEffect = "copy"
    },
    handleDragLeave({ target }) {
      target.classList.remove("hover")
    },
    handleDrop(event) {
      const { target, dataTransfer: { files } } = event
      target.classList.remove("hover")
      files && this.whenGetFiles(event, files)
    },
    handleReset() {
      this.uploadTasks = this.defaultFileList.map((item) => ({
        file: item,
        status: "success",
      }))
    },
  },
}
</script>
