<template>
  <view class="chunk-upload">
    <!-- 上传按钮 -->
    <button @click="chooseVideo" :disabled="uploading">
      {{ uploading ? `上传中...${progress}%` : '选择视频上传' }}
    </button>
    
    <!-- 进度条 -->
    <progress v-if="uploading" :percent="progress" show-info stroke-width="3" />
    
    <!-- 上传状态 -->
    <view class="status" v-if="status">
      {{ status }}
    </view>
  </view>
</template>

<script>
<script>
// 引入 spark-md5 计算文件哈希
import SparkMD5 from 'spark-md5'

export default {
  name: 'ChunkUpload',
  props: {
    // 上传接口地址
    uploadUrl: {
      type: String,
      required: true
    },
    // 分片大小 (默认2MB)
    chunkSize: {
      type: Number,
      default: 2 * 1024 * 1024
    },
    // 允许的视频类型
    accept: {
      type: String,
      default: 'video/*'
    },
    // 最大文件大小 (MB)
    maxSize: {
      type: Number,
      default: 500
    }
  },
		data() {
			return {
				uploading: false,
				      progress: 0,
				      status: '',
				      file: null,
				      fileMd5: '',
				      cancelToken: null
			};
		}
		methods: {
		    // 选择视频文件
		    async chooseVideo() {
		      if (this.uploading) {
		        uni.showToast({ title: '正在上传中', icon: 'none' })
		        return
		      }
		
		      try {
		        const res = await this.getFile()
		        if (!res) return
		        
		        this.file = res
		        const sizeMB = res.size / 1024 / 1024
		        
		        if (sizeMB > this.maxSize) {
		          uni.showToast({ title: `文件不能超过${this.maxSize}MB`, icon: 'none' })
		          return
		        }
		
		        uni.showLoading({ title: '计算文件MD5...', mask: true })
		        this.fileMd5 = await this.calculateFileMD5(res)
		        uni.hideLoading()
		        
		        this.startUpload()
		      } catch (error) {
		        console.error('选择文件出错:', error)
		        uni.hideLoading()
		        uni.showToast({ title: '选择文件失败', icon: 'none' })
		      }
		    },
		    
		    // 获取文件 (兼容各平台)
		    getFile() {
		      return new Promise((resolve, reject) => {
		        // #ifdef H5
		        const input = document.createElement('input')
		        input.type = 'file'
		        input.accept = this.accept
		        input.onchange = (e) => {
		          resolve(e.target.files[0])
		        }
		        input.click()
		        // #endif
		        
		        // #ifndef H5
		        uni.chooseMedia({
		          count: 1,
		          mediaType: ['video'],
		          sourceType: ['album', 'camera'],
		          success: (res) => {
		            resolve(res.tempFiles[0])
		          },
		          fail: (err) => {
		            reject(err)
		          }
		        })
		        // #endif
		      })
		    },
		    
		    // 计算文件MD5 (分块计算)
		    calculateFileMD5(file) {
		      return new Promise((resolve) => {
		        const chunkSize = 1 * 1024 * 1024 // 1MB分块计算MD5
		        const chunks = Math.ceil(file.size / chunkSize)
		        const spark = new SparkMD5.ArrayBuffer()
		        let currentChunk = 0
		
		        const loadNext = () => {
		          const start = currentChunk * chunkSize
		          const end = Math.min(start + chunkSize, file.size)
		          
		          // #ifdef H5
		          const reader = new FileReader()
		          reader.onload = (e) => {
		            spark.append(e.target.result)
		            currentChunk++
		            if (currentChunk < chunks) {
		              loadNext()
		            } else {
		              resolve(spark.end())
		            }
		          }
		          reader.readAsArrayBuffer(file.slice(start, end))
		          // #endif
		          
		          // #ifdef APP-PLUS
		          const fileReader = new plus.io.FileReader()
		          fileReader.onload = (e) => {
		            spark.append(e.target.result)
		            currentChunk++
		            if (currentChunk < chunks) {
		              loadNext()
		            } else {
		              resolve(spark.end())
		            }
		          }
		          fileReader.readAsArrayBuffer(file.slice(start, end))
		          // #endif
		          
		          // #ifdef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
		          const fs = uni.getFileSystemManager()
		          fs.readFile({
		            filePath: file.path,
		            position: start,
		            length: end - start,
		            arrayBuffer: true,
		            success: (res) => {
		              spark.append(res.data)
		              currentChunk++
		              if (currentChunk < chunks) {
		                loadNext()
		              } else {
		                resolve(spark.end())
		              }
		            },
		            fail: (err) => {
		              console.error("读取文件失败:", err)
		              reject(err)
		            }
		          })
		          // #endif
		        }
		
		        loadNext()
		      })
		    },
		    
		    // 开始分片上传
		    async startUpload() {
		      if (!this.file) return
		      
		      this.uploading = true
		      this.progress = 0
		      this.status = '准备上传...'
		      
		      try {
		        // 1. 检查文件是否已上传过
		        const checkRes = await this.checkFile()
		        
		        if (checkRes.uploaded) {
		          this.status = '文件已存在，秒传成功'
		          this.progress = 100
		          this.$emit('success', checkRes)
		          return
		        }
		        
		        // 2. 开始分片上传
		        const uploadedChunks = checkRes.uploadedChunks || []
		        const chunks = Math.ceil(this.file.size / this.chunkSize)
		        let uploadedSize = 0
		        
		        for (let i = 0; i < chunks; i++) {
		          if (uploadedChunks.includes(i)) {
		            uploadedSize += this.getChunkSize(i)
		            this.updateProgress(uploadedSize)
		            continue
		          }
		          
		          // 上传当前分片
		          await this.uploadChunk(i)
		          uploadedSize += this.getChunkSize(i)
		          this.updateProgress(uploadedSize)
		        }
		        
		        // 3. 合并分片
		        this.status = '正在合并文件...'
		        const mergeRes = await this.mergeChunks()
		        
		        this.status = '上传成功'
		        this.$emit('success', mergeRes)
		      } catch (error) {
		        console.error('上传出错:', error)
		        this.status = '上传失败: ' + (error.message || error.errMsg || '未知错误')
		        this.$emit('fail', error)
		      } finally {
		        this.uploading = false
		      }
		    },
		    
		    // 检查文件状态
		    checkFile() {
		      return new Promise((resolve, reject) => {
		        uni.request({
		          url: `${this.uploadUrl}/check`,
		          method: 'POST',
		          data: {
		            fileMd5: this.fileMd5,
		            fileName: this.file.name,
		            fileSize: this.file.size,
		            chunkSize: this.chunkSize
		          },
		          success: (res) => {
		            if (res.data.code === 0) {
		              resolve(res.data.data)
		            } else {
		              reject(new Error(res.data.msg))
		            }
		          },
		          fail: (err) => {
		            reject(err)
		          }
		        })
		      })
		    },
		    
		    // 上传分片
		    uploadChunk(chunkIndex) {
		      return new Promise((resolve, reject) => {
		        const start = chunkIndex * this.chunkSize
		        const end = Math.min(start + this.chunkSize, this.file.size)
		        const chunkBlob = this.file.slice(start, end)
		        
		        // #ifdef H5 || APP-PLUS
		        const formData = new FormData()
		        formData.append('file', chunkBlob)
		        formData.append('chunkIndex', chunkIndex)
		        formData.append('fileMd5', this.fileMd5)
		        formData.append('chunkSize', this.chunkSize)
		        formData.append('totalChunks', Math.ceil(this.file.size / this.chunkSize))
		        
		        const xhr = new XMLHttpRequest()
		        this.cancelToken = xhr
		        
		        xhr.open('POST', `${this.uploadUrl}/upload`, true)
		        
		        xhr.upload.onprogress = (e) => {
		          if (e.lengthComputable) {
		            // 计算当前分片的上传进度
		            const chunkProgress = Math.round((e.loaded / e.total) * 100)
		            const baseProgress = (chunkIndex / Math.ceil(this.file.size / this.chunkSize)) * 100
		            this.progress = Math.min(baseProgress + (chunkProgress / Math.ceil(this.file.size / this.chunkSize)), 100)
		          }
		        }
		        
		        xhr.onload = () => {
		          if (xhr.status === 200) {
		            const res = JSON.parse(xhr.responseText)
		            if (res.code === 0) {
		              resolve(res.data)
		            } else {
		              reject(new Error(res.msg))
		            }
		          } else {
		            reject(new Error(`上传失败: ${xhr.status}`))
		          }
		        }
		        
		        xhr.onerror = () => {
		          reject(new Error('上传出错'))
		        }
		        
		        xhr.send(formData)
		        // #endif
		        
		        // #ifdef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
		        uni.uploadFile({
		          url: `${this.uploadUrl}/upload`,
		          filePath: this.file.path,
		          name: 'file',
		          formData: {
		            chunkIndex,
		            fileMd5: this.fileMd5,
		            chunkSize: this.chunkSize,
		            totalChunks: Math.ceil(this.file.size / this.chunkSize))
		          },
		          header: {
		            'Content-Type': 'multipart/form-data'
		          },
		          success: (res) => {
		            const data = JSON.parse(res.data)
		            if (data.code === 0) {
		              resolve(data.data)
		            } else {
		              reject(new Error(data.msg))
		            }
		          },
		          fail: (err) => {
		            reject(err)
		          }
		        })
		        // #endif
		      })
		    },
		    
		    // 合并分片
		    mergeChunks() {
		      return new Promise((resolve, reject) => {
		        uni.request({
		          url: `${this.uploadUrl}/merge`,
		          method: 'POST',
		          data: {
		            fileMd5: this.fileMd5,
		            fileName: this.file.name,
		            fileSize: this.file.size,
		            chunkSize: this.chunkSize
		          },
		          success: (res) => {
		            if (res.data.code === 0) {
		              resolve(res.data.data)
		            } else {
		              reject(new Error(res.data.msg))
		            }
		          },
		          fail: (err) => {
		            reject(err)
		          }
		        })
		      })
		    },
		    
		    // 获取当前分片大小
		    getChunkSize(chunkIndex) {
		      const start = chunkIndex * this.chunkSize
		      const end = Math.min(start + this.chunkSize, this.file.size)
		      return end - start
		    },
		    
		    // 更新进度
		    updateProgress(uploadedSize) {
		      this.progress = Math.round((uploadedSize / this.file.size) * 100)
		    },
		    
		    // 取消上传
		    cancelUpload() {
		      if (this.cancelToken) {
		        // #ifdef H5 || APP-PLUS
		        this.cancelToken.abort()
		        // #endif
		        // #ifdef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
		        // 小程序端无法直接取消上传，需要标记取消状态
		        // #endif
		        this.cancelToken = null
		      }
		      this.uploading = false
		      this.status = '上传已取消'
		      this.$emit('cancel')
		    }
		  }
		}
</script>

<style scoped>
.chunk-upload {
  padding: 20rpx;
}

button {
  margin-bottom: 20rpx;
}

.status {
  margin-top: 20rpx;
  color: #666;
  font-size: 24rpx;
}
</style>