<template>
  <view :class="['wd-upload', customClass]">
    <!-- 预览列表 -->
    <view :class="['wd-upload__preview', customPreviewClass]" v-for="(file, index) in uploadFiles" :key="index">
      <!-- 成功时展示图片 -->
      <view class="wd-upload__status-content">
        <image :src="file.url" mode="aspectFit" class="wd-upload__picture" @click="onPreviewImage(index)" />
      </view>

      <view v-if="file.status !== 'success'" class="wd-upload__mask wd-upload__status-content">
        <!-- loading时展示loading图标和进度 -->
        <view v-if="file.status === 'loading'" class="wd-upload__status-content">
          <wd-loading :size="24" :color="loadingColor" />
          <text class="wd-upload__progress-txt">{{ file.percent }}%</text>
        </view>
        <!-- 失败时展示失败图标以及失败信息 -->
        <view v-if="file.status === 'fail'" class="wd-upload__status-content">
          <wd-icon name="close-outline" custom-class="wd-upload__icon"></wd-icon>
          <text class="wd-upload__progress-txt">{{ file.error || '上传失败' }}</text>
        </view>
      </view>
      <!-- 上传状态为上传中时不展示移除按钮 -->
      <wd-icon v-if="file.status !== 'loading' && !disabled" name="error-fill" custom-class="wd-upload__close" @click="removeFile(index)"></wd-icon>
    </view>

    <view @click="handleChoose">
      <slot v-if="useDefaultSlot"></slot>
      <!-- 唤起项 -->
      <view
        v-if="!useDefaultSlot && (!limit || uploadFiles.length < limit)"
        :class="['wd-upload__evoke', disabled ? 'is-disabled' : '', customEvokeClass]"
      >
        <!-- 唤起项图标 -->
        <wd-icon class="wd-upload__evoke-icon" name="fill-camera"></wd-icon>
        <!-- 有限制个数时确认是否展示限制个数 -->
        <view v-if="limit && showLimitNum" class="wd-upload__evoke-num">（{{ uploadFiles.length }}/{{ limit }}）</view>
      </view>
    </view>
  </view>
</template>

<script lang="ts">
export default {
  name: 'wd-upload',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import { ref, watch } from 'vue'
import { context, getType, isDef, isEqual } from '../common/util'
import { chooseFile } from './utils'

interface Props {
  customClass?: string
  customEvokeClass?: string
  customPreviewClass?: string
  // 多选
  multiple?: boolean
  // 接受类型 暂定接受类型为图片，视频后续添加
  accept?: string
  sizeType?: Array<string>
  sourceType?: Array<string>
  header?: Record<string, any>
  name?: string
  formData?: Record<string, any>
  // 上传相关
  action: string
  fileList?: Record<string, any>[]
  statusKey?: string
  maxSize?: number
  limit?: number
  showLimitNum?: boolean
  disabled?: boolean
  useDefaultSlot?: boolean
  // loading 相关
  loadingType?: string
  loadingColor?: string
  loadingSize?: string
  // eslint-disable-next-line @typescript-eslint/ban-types
  beforePreview?: Function
  // eslint-disable-next-line @typescript-eslint/ban-types
  onPreviewFail?: Function
  // eslint-disable-next-line @typescript-eslint/ban-types
  beforeRemove?: Function
  // eslint-disable-next-line @typescript-eslint/ban-types
  beforeUpload?: Function
  // 图片预览相关
  // eslint-disable-next-line @typescript-eslint/ban-types
  beforeChoose?: Function
  // eslint-disable-next-line @typescript-eslint/ban-types
  buildFormData?: Function
}

const props = withDefaults(defineProps<Props>(), {
  customClass: '',
  customEvokeClass: '',
  customPreviewClass: '',
  multiple: false,
  fileList: () => [] as Record<string, any>[],
  accept: 'image',
  showLimitNum: true,
  disabled: false,
  sourceType: () => ['album', 'camera'],
  sizeType: () => ['original', 'compressed'],
  header: () => {
    return {}
  },
  formData: () => {
    return {}
  },
  name: 'file',
  loadingType: 'ring',
  loadingColor: '#ffffff',
  loadingSize: '24px',
  useDefaultSlot: false,
  statusKey: 'status',
  maxSize: Number.MAX_VALUE
})

const uploadFiles = ref<Record<string, any>[]>([])

watch(
  () => props.fileList,
  (val) => {
    const { statusKey } = props
    if (isEqual(val, uploadFiles.value)) return
    const uploadFileList = val.map((item) => {
      item.uid = context.id++
      item[statusKey] = item[statusKey] || 'success'
      item.size = item.size || ''
      item.action = props.action || ''
      item.response = item.response || ''
      return item
    })
    uploadFiles.value = uploadFileList
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.limit,
  (val) => {
    if (val && val < uploadFiles.value.length) {
      console.error('[wot-design]Error: props limit must less than fileList.length')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforePreview,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforePreview must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.onPreviewFail,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of onPreviewFail must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeRemove,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeRemove must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeUpload,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeUpload must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeChoose,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeChoose must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.buildFormData,
  (fn) => {
    if (fn && getType(fn) !== 'function' && getType(fn) !== 'asyncfunction') {
      console.error('The type of buildFormData must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

const emit = defineEmits(['fail', 'change', 'success', 'progress', 'oversize', 'chooseerror', 'remove'])

/**
 * @description 初始化文件数据
 * @param {Object} file 上传的文件
 */
function initFile(file) {
  // 状态初始化
  const initState = {
    uid: context.id++,
    // 仅h5支持 name
    name: file.name || '',
    status: 'loading',
    size: file.size,
    url: file.path,
    action: props.action,
    percent: 0
  }

  uploadFiles.value.push(initState)

  const { buildFormData, formData = {} } = props

  if (buildFormData) {
    buildFormData({
      file: initState,
      formData,
      resolve: (formData: Record<string, any>) => {
        formData && handleUpload(initState, formData)
      }
    })
  } else {
    handleUpload(initState, formData)
  }
}

/**
 * @description 上传失败捕获
 * @param {Object} err 错误返回信息
 * @param {Object} file 上传的文件
 */
function handleError(err, file, formData: Record<string, any>) {
  const { statusKey } = props
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index][statusKey] = 'fail'
    uploadFiles.value[index].error = err.message
    uploadFiles.value[index].response = err
    emit('fail', { error: err, file, formData })
  }
}

/**
 * @description 上传成功捕获
 * @param {Object} res 接口返回信息
 * @param {Object} file 上传的文件
 */
function handleSuccess(res, file, formData: Record<string, any>) {
  const { statusKey } = props
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index][statusKey] = 'success'
    uploadFiles.value[index].response = res.data
    emit('change', { fileList: uploadFiles.value })
    emit('success', { file, fileList: uploadFiles.value, formData })
  }
}

/**
 * @description 上传中捕获
 * @param {Object} res 接口返回信息
 * @param {Object} file 上传的文件
 */
function handleProgress(res, file) {
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index].percent = res.progress
    emit('progress', { response: res, file })
  }
}

/**
 * @description 上传操作
 * @param {Object} file 上传的文件
 */
function handleUpload(file, formData: Record<string, any>) {
  const { action, name, header = {}, accept } = props

  const uploadTask = uni.uploadFile({
    url: action,
    header,
    name,
    fileName: name,
    fileType: accept as 'image' | 'video' | 'audio',
    formData,
    filePath: file.url,
    success(res) {
      if (res.statusCode === 200) {
        // 上传成功进行文件列表拼接
        handleSuccess(res, file, formData)
      } else {
        // 上传失败处理
        handleError(res, file, formData)
      }
    },
    fail(err) {
      // 上传失败处理
      handleError(err, file, formData)
    }
  })

  // 获取当前文件加载的百分比
  uploadTask.onProgressUpdate((res) => {
    /**
     * res.progress: 上传进度
     * res.totalBytesSent: 已经上传的数据长度
     * res.totalBytesExpectedToSend: 预期需要上传的数据总长度
     */
    handleProgress(res, file)
  })
}

/**
 * @description 选择文件的实际操作，将chooseFile自己用promise包了一层
 */
function onChooseFile() {
  const { multiple, maxSize, accept, sizeType, limit, sourceType, beforeUpload } = props
  // 设置为只选择图片的时候使用 chooseImage 来实现
  if (accept === 'image') {
    // 文件选择
    chooseFile({
      multiple,
      sizeType,
      sourceType,
      maxCount: limit ? limit - uploadFiles.value.length : 9
    })
      .then((res: any) => {
        // 成功选择初始化file
        let files: null | Array<any> = null
        files = Array.prototype.slice.call(res.tempFiles)
        // 单选只有一个
        if (!multiple) {
          files = files.slice(0, 1)
        }

        // 遍历列表逐个初始化上传参数
        const mapFiles = (files) => {
          files.forEach(async (file) => {
            if (!isDef(file.size)) {
              file.size = await getImageInfo(file.path)
            }
            file.size <= maxSize ? initFile(file) : emit('oversize', { file })
          })
        }

        // 上传前的钩子
        if (beforeUpload) {
          // 向下兼容原来的参数写法，2.2.0 向下兼容 2.1.0
          if (beforeUpload.length === 2) {
            beforeUpload(files, (isPass) => {
              isPass && mapFiles(files)
            })
          } else {
            beforeUpload({
              files,
              fileList: uploadFiles.value,
              resolve: (isPass) => {
                isPass && mapFiles(files)
              }
            })
          }
        } else {
          mapFiles(files)
        }
      })
      .catch((error) => {
        emit('chooseerror', { error })
      })
  }
}

/**
 * 获取图片信息
 * @param src 图片地址
 */
function getImageInfo(src: string) {
  return new Promise<number>((resolve, reject) => {
    uni.getImageInfo({
      src: src,
      success: (res) => {
        resolve(res.height * res.width)
      },
      fail: () => {
        reject(0)
      }
    })
  })
}

/**
 * @description 选择文件，内置拦截选择操作
 */
function handleChoose() {
  if (props.disabled) return
  const { beforeChoose } = props

  // 选择图片前的钩子
  if (beforeChoose) {
    // 向下兼容原来的参数写法，2.2.0 向下兼容 2.1.0
    if (beforeChoose.length === 2) {
      beforeChoose(uploadFiles.value, (isPass) => {
        isPass && onChooseFile()
      })
    } else {
      beforeChoose({
        fileList: uploadFiles.value,
        resolve: (isPass) => {
          isPass && onChooseFile()
        }
      })
    }
  } else {
    onChooseFile()
  }
}

/**
 * @description 移除文件
 * @param {Object} file 上传的文件
 * @param {Number} index 删除
 */
function handleRemove(file, index?: number) {
  uploadFiles.value.splice(
    uploadFiles.value.findIndex((item) => item.uid === file.uid),
    1
  )
  emit('change', {
    fileList: uploadFiles.value
  })
  emit('remove', { file })
}

function removeFile(index) {
  const { beforeRemove } = props
  const intIndex = parseInt(index)
  const file = uploadFiles.value[intIndex]
  if (beforeRemove) {
    // 向下兼容原来的参数写法，2.2.0 向下兼容 2.1.0
    if (beforeRemove.length === 3) {
      beforeRemove(file, uploadFiles.value, (isPass) => {
        isPass && handleRemove(file)
      })
    } else {
      beforeRemove({
        file,
        index: intIndex,
        fileList: uploadFiles.value,
        resolve: (isPass) => {
          isPass && handleRemove(file)
        }
      })
    }
  } else {
    handleRemove(file)
  }
}

function onPreview(index, lists) {
  const { onPreviewFail } = props
  uni.previewImage({
    urls: lists,
    current: lists[index],
    fail() {
      if (onPreviewFail) {
        if (onPreviewFail.length === 2) {
          onPreviewFail(index, lists)
        } else {
          onPreviewFail({
            index,
            imgList: lists
          })
        }
      } else {
        uni.showToast({ title: '预览图片失败', icon: 'none' })
      }
    }
  })
}

function onPreviewImage(index) {
  const { beforePreview } = props
  const lists = uploadFiles.value.map((file) => file.url)
  if (beforePreview) {
    // 向下兼容原来的参数写法，2.2.0 向下兼容 2.1.0
    if (beforePreview.length === 2) {
      beforePreview({ index, lists }, (isPass) => {
        isPass && onPreview(index, lists)
      })
    } else {
      beforePreview({
        index,
        imgList: lists,
        resolve: (isPass) => {
          isPass && onPreview(index, lists)
        }
      })
    }
  } else {
    onPreview(index, lists)
  }
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
