<template>
  <div>
    <div v-if="!props.disabled && props.text" style="display: inline-flex;" :class="{ doing: doing || !uploadKey }"
      @click="open()">
      <slot name="button"><el-button>{{ props.text }}</el-button></slot>
    </div>
    <input ref="fileInputRef" v-if="!props.disabled" accept="image/*" @change="fileInputChange" type="file"
      style="position: absolute; top:-100vh;" :multiple="props.max != 1" />
    <div class="list" :class="{ noEffect: props.noEffect }">
      <slot :list="data">
        <div v-for="(item, index) in data" :key="index" class="item" :class="{ error: item.error, abort: item.abort }"
          :style="{ '--ps': item.uploading ? (item.uploading?.loaded || 0) * 100 / (item.uploading?.total || 1) + '%' : '100%' }"
          @click="imgPreview(index)">
          <img :src="item.url ? getUrl(props.saveMin ? (item.url + '.min.webp') : item.url) : item.preview">
          <div class="btns">
            <div>
              <svg t="1729755606480" class="icon" viewBox="0 0 1024 1024" version="1.1"
                xmlns="http://www.w3.org/2000/svg" p-id="3478" width="200" height="200">
                <path
                  d="M995.474286 446.902857a569.782857 569.782857 0 0 0-95.817143-134.034286 540.708571 540.708571 0 0 0-775.314286 0 569.782857 569.782857 0 0 0-95.817143 134.034286 143.36 143.36 0 0 0 0 130.194286 569.782857 569.782857 0 0 0 95.817143 134.034286 540.708571 540.708571 0 0 0 775.314286 0 569.782857 569.782857 0 0 0 95.817143-134.034286 143.36 143.36 0 0 0 0-130.194286zM512 676.571429a164.571429 164.571429 0 1 1 164.571429-164.571429 164.571429 164.571429 0 0 1-164.571429 164.571429z"
                  p-id="3479"></path>
              </svg>
            </div>
            <div v-if="item.url" @click.stop="download(item.url)">
              <svg t="1729757927480" class="icon" viewBox="0 0 1024 1024" version="1.1"
                xmlns="http://www.w3.org/2000/svg" p-id="9348" width="200" height="200">
                <path
                  d="M14.224034 1009.780983v-224.022264h109.647986v74.722589h776.250943V785.758719h109.653003v224.022264H14.224034zM123.87202 350.076808l259.815653 0.145501V14.219017h259.810637v329.781845h259.805619l-385.082482 404.429175-394.349427-398.353229z m0 0"
                  fill="#231815" p-id="9349"></path>
              </svg>
            </div>
            <div v-if="item.uploading && !item.abort" class="btn" @click.stop="item.uploading.abort()">
              <svg t="1729756297498" class="icon" viewBox="0 0 1024 1024" version="1.1"
                xmlns="http://www.w3.org/2000/svg" p-id="9240" width="200" height="200">
                <path
                  d="M507.426133 156.4672c-4.2496-0.119467-8.413867 0.119467-12.6464 0.136533V56.661333c0-27.392-30.651733-43.6224-53.316266-28.245333L82.756267 272.0256a34.133333 34.133333 0 0 0 0 56.4736l358.741333 243.592533c22.664533 15.394133 53.316267-0.836267 53.316267-28.245333v-101.0688c172.834133-2.0992 316.5696 118.545067 323.515733 274.551467 5.3248 119.671467-71.424 225.757867-185.207467 274.756266-2.065067 0.887467-4.232533 2.628267-4.0448 6.178134 0.1536 2.901333 3.106133 3.754667 5.7344 2.9696C816.930133 947.677867 951.005867 790.306133 956.074667 600.917333c6.365867-238.660267-194.491733-437.640533-448.648534-444.450133z"
                  fill="#040000" p-id="9241"></path>
              </svg>
            </div>

            <div v-if="item.url && !props.disabled" @click.stop="item.remove()" class="btn">
              <svg t="1729755512665" class="icon" viewBox="0 0 1024 1024" version="1.1"
                xmlns="http://www.w3.org/2000/svg" p-id="2491" width="200" height="200">
                <path
                  d="M513.43007 1019.262092c-280.20375 0-507.388982-227.207745-507.388982-507.410472 0-280.224216 227.185232-507.409448 507.388982-507.409448 280.247752 0 507.391029 227.185232 507.391029 507.409448C1020.821099 792.054347 793.678846 1019.262092 513.43007 1019.262092zM746.107387 363.903034c9.540284-9.53926 9.540284-25.021883 0-34.539654l-51.822272-51.800783c-9.535167-9.558703-24.977881-9.558703-34.518165 0L512.976746 424.334381 366.184495 277.562597c-9.53619-9.558703-24.977881-9.558703-34.518165 0l-51.822272 51.800783c-9.538237 9.517771-9.538237 25.001417 0 34.539654l146.793274 146.770761-146.793274 146.790204c-9.538237 9.518794-9.538237 25.004487 0 34.540677l51.822272 51.79976c9.540284 9.538237 24.981974 9.538237 34.518165 0L512.976746 597.014232l146.790204 146.790204c9.540284 9.538237 24.982998 9.538237 34.518165 0l51.822272-51.79976c9.540284-9.53619 9.540284-25.021883 0-34.540677L599.317183 510.674818 746.107387 363.903034z"
                  p-id="2492"></path>
              </svg>
            </div>
          </div>
        </div>
      </slot>
    </div>
    <el-image-viewer v-if="imgShow" @close="imgShow = false" ref="imgRef"
      :url-list="data.map(a => a.url ? getUrl(a.url) : a.preview)" />
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, getCurrentInstance, watch } from 'vue'
import { request } from '@/utils'
import { ElMessage } from 'element-plus'

const base = "/file/upload/"

const baseUrl = import.meta.env.VITE_APP_BASE_API

const { proxy } = getCurrentInstance()

const imgShow = ref(false)

const imgPreview = async (index) => {
  imgShow.value = true
  await proxy.$nextTick()
  proxy.$refs.imgRef.setActiveItem(index)
}


const props = defineProps({
  /**
   * 上传的文件
   */
  modelValue: {
    type: [Array, String],
    required: true
  },
  /**
   * 上传的数量,0：表示无限制,1：表示单文件上传
   */
  max: {
    type: Number,
    default: 0
  },
  /**
   * 获取上传凭证的方法
   */
  getUploadKey: {
    type: Function,
    default: async () => {
      let r = await request.get("/uploadKey")
      return r.data
    }
  },
  /**
   * 上传路径前缀
   */
  prefix: {
    type: String,
    default: "default"
  },
  /**
   * 禁止上传（图片预览模式）
   */
  disabled: {
    type: Boolean,
    default: false
  },
  /**
   * 按钮文字
   */
  text: {
    type: String,
    default: '上传图片'
  },
  /**
   * 上传中?
   */
  uploading: {
    type: Boolean,
    default: false
  },
  /**
   * 允许上传的文件最大大小,0表示不限制，默认：0
   */
  maxSize: {
    type: Number,
    default: 0
  },
  /**
   * 并行上传数量：默认：2
   */
  threadNum: {
    type: Number,
    default: 2
  },
  /** 重试次数，默认：3 */
  retry: {
    type: Number,
    default: 3
  },
  saveSrc: {
    type: Boolean,
    default: false
  },
  saveMin: {
    type: Boolean,
    default: true
  },
  watermark: {
    type: Boolean,
    default: true
  },
  noEffect: {
    type: Boolean,
    default: false
  }


})


const emit = defineEmits(["update:modelValue", "change", "update:uploading"]);

const mv = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit("update:modelValue", value)
    emit("change", value)
  }
})
const uploading = computed({
  get() {
    return props.uploading
  },
  set(value) {
    emit("update:uploading", value)
  }
})

const data = ref([])

const uploadKey = ref("");

const getUrl = (url) => {
  return baseUrl + base + 'download?' + request.params({ url, key: uploadKey.value }).toString()
}

const download = (url) => {
  window.open(getUrl(props.saveSrc ? (url + '.zip') : url))
}


onMounted(async () => {
  uploadKey.value = await props.getUploadKey()
  if (props.max == 1) {
    data.value = []
    if (mv.value) {
      data.value = [{ url: mv.value }]
    }
  } else {
    data.value = []
    if (mv.value) {
      data.value = mv.value.map(a => ({ url: a }))
    }
  }
  for (let one of data.value) {
    one.remove = () => {
      remove(one)
    }
  }
  console.debug(proxy.$refs.imgRef)
})

watch(mv, () => {
  if (props.max == 1) {
    data.value = []
    if (mv.value) {
      data.value = [{ url: mv.value }]
    }
  } else {
    data.value = []
    if (mv.value) {
      data.value = mv.value.map(a => ({ url: a }))
    }
  }
})

const remove = (one) => {
  data.value.splice(data.value.indexOf(one), 1)
  if (props.max == 1) {
    mv.value = data.value[0]?.url
  } else {
    mv.value = data.value.filter(a => 'url' in a).map(a => a.url)
  }
  request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url }))
  request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url + '.min.webp' }))
  request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url + '.zip' }))
}

onUnmounted(async () => {
  await request.get(base + "removeUploadKey-" + uploadKey.value)
})
window.addEventListener('beforeunload', async () => {
  await request.get(base + "removeUploadKey-" + uploadKey.value)
})


/**
 * 刷新上传凭证
 */
const reloadUploadKey = async () => {
  request.get(base + "removeUploadKey-" + uploadKey.value)
  uploadKey.value = await props.getUploadKey()
}

const fileInputRef = ref(null);

/**
 * 打开文件选择对话框
 */
const open = () => {
  if (doing.value) {
    return
  }
  try {
    fileInputRef.value.click();
  } catch (e) { }
}

const fileInputChange = () => {

  let files = fileInputRef.value.files
  if (files.length > 0) {
    // fileInputRef.value.value = ''
    for (let file of files) {
      addUploadFile(file)
    }
    uploadFiles()
  }
}

const addUploadFile = async (file) => {
  console.debug(file)
  if (props.max > 1 && props.max == data.value.length) {
    ElMessage.error('最多只允许上传' + props.max + '个文件')
    return
  }
  // 判断类型
  if (!file.type.startsWith("image/")) {
    ElMessage.error(file.name + '不是图片')
    return
  }
  if (props.maxSize > 0) {
    if (file.size > props.maxSize) {
      ElMessage.error(file.name + '大小超过了' + props.maxSize.toFileSize())
      return
    }
  }
  if (props.max == 1) {
    data.value.length = 0
  }


  data.value.push({ raw: file, preview: window.URL.createObjectURL(file) })

}

let doing = ref(false);
const uploadFiles = async () => {
  if (doing.value) {
    return
  }
  doing.value = true
  uploading.value = true
  await reloadUploadKey()
  let index = 0
  let doOne = async () => {
    while (index < data.value.length) {
      await uploadFile(index++)
    }
  }

  let a = [];
  for (let i = 0; i < props.threadNum; i++) {
    a.push(doOne())
  }
  Promise.all(a).then(() => {
    console.debug('end')
  }).finally(() => {
    data.value = data.value.filter(a => !a.error && !a.abort)
    console.debug('finally')
    doing.value = false
    uploading.value = false
  })
}



const uploadFile = async (index, retry = 0) => {
  console.debug(index)
  let one = data.value[index];
  if (!one) {
    return
  }
  if (one.url) {
    return
  }
  if (retry >= props.retry) {
    one.error = true
    return
  }
  if (one.uploading || one.error || one.abort) {
    return
  }
  retry++

  one.uploading = { loaded: 0, total: one.raw.size }
  one.uploading.controller = new AbortController();
  one.uploading.abort = () => {
    one.uploading.controller.abort()
  }

  one.remove = () => {
    remove(one)
  }



  let formData = new FormData()
  formData.append("prefix", props.prefix)
  formData.append("key", uploadKey.value)
  formData.append("saveSrc", props.saveSrc)
  formData.append("saveThumbnail", props.saveMin)
  formData.append("watermark", props.watermark)
  formData.append("file", one.raw)

  let onUploadProgress = (event) => {
    one.uploading.loaded = event.loaded

  }

  try {
    let r = await request.post(base + "image?r=" + Math.random(), formData, {
      onUploadProgress,
      timeout: 600000,
      showLoading: false,
      signal: one.uploading.controller.signal
    })
    console.debug(r)
    if (r.code == 200) {
      delete one.raw
      delete one.uploading
      one.url = r.data
      if (props.max == 1) {
        mv.value = r.data
      } else {
        mv.value = data.value.filter(a => 'url' in a).map(a => a.url)
      }
    } else {
      delete one.uploading
      await uploadFile(index, retry)
    }
  } catch (e) {
    delete one.uploading
    await uploadFile(index, retry)
  }

}

defineExpose({ open, reloadUploadKey })
</script>
<style lang="scss" scoped>
.doing {
  opacity: .5;
  position: relative;

  &::before {
    content: '';
    position: absolute;
    height: 100%;
    width: 100%;
    background-color: #0000;
    cursor: not-allowed;

  }
}

.list {
  position: relative;
  display: flex;
  flex-wrap: wrap;

  &.noEffect {
    --image-border-radius: 0;
    --image-margin: 0;
    --image-border: none;
    --image-box-shadow: none;

  }

  .item {

    width: var(--image-size, 10em);
    height: var(--image-size, 10em);

    position: relative;
    overflow: hidden;
    border-radius: var(--image-border-radius, .4em);
    margin: var(--image-margin, .5em .5em 0 0);
    padding: 0;
    border: var(--image-border, #FFF solid 2px);
    box-shadow: var(--image-box-shadow, 0 0 .2em #0005);
    cursor: pointer;

    img {
      object-fit: var(--image-fit, cover);
      width: 100%;
      height: 100%;
      object-position: center;
      transition: all .5s;

    }

    .btns {
      position: absolute;
      z-index: 1;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      top: 100%;
      left: 0;
      transition: all .5s;


      &>* {
        padding: .3em;
        background-color: #0006;
        border-radius: .3em;
        margin: .1em;
        display: flex;
        justify-content: center;
        align-items: center;
        transition: all .3s;
        border: solid 1px #FFF8;

        &:hover {
          transform: translate(1px, 1px);
        }


        svg {
          width: 1em;
          height: 1em;

          path {
            fill: #FFFA;
          }
        }
      }


    }

    &:hover {
      img {
        transform: scale(1.05);
      }

      .btns {
        top: 0;
      }
    }

    &.abort {
      opacity: .5;
    }

    &.error {
      border-color: var(--el-color-danger, #f56c6c);
    }

    &::after {
      content: '';
      pointer-events: none;
      background-color: #00000035;
      position: absolute;
      top: var(--ps, 100%);
      // top: 30%;
      left: 0;
      height: 100%;
      width: 100%;
      backdrop-filter: blur(1px);


    }


  }
}
</style>