<template>
  <view class="crop-page">
    <view class="crop-header">
      <view class="header-btn cancel" @click="cancel">取消</view>
      <view class="header-title">照片裁剪</view>
      <view class="header-btn confirm" @click="confirm">确定</view>
    </view>

    <view class="crop-container" ref="cropContainer">
      <!-- 原图显示区域 -->
      <image class="original-image" :src="imageSrc" mode="aspectFit"></image>

      <!-- 用于裁剪的 canvas（隐藏）- 仅在小程序环境中渲染 -->
      <canvas v-if="!isH5" canvas-id="cropCanvas" id="cropCanvas" class="crop-canvas" width="200" height="200"></canvas>

      <!-- 四块镂空遮罩，实现框外变暗、框内高亮 -->
      <view class="mask-top" :style="{ height: cropBox.y + 'px' }"></view>
      <view class="mask-left" :style="{
        top: cropBox.y + 'px',
        width: cropBox.x + 'px',
        height: cropBox.size + 'px'
      }"></view>
      <view class="mask-right" :style="{
        top: cropBox.y + 'px',
        left: cropBox.x + cropBox.size + 'px',
        width: containerSize.width - cropBox.x - cropBox.size + 'px',
        height: cropBox.size + 'px'
      }"></view>
      <view class="mask-bottom" :style="{
        top: cropBox.y + cropBox.size + 'px',
        height: containerSize.height - cropBox.y - cropBox.size + 'px'
      }"></view>

      <!-- 裁剪框 -->
      <view class="crop-box" :style="{
        left: cropBox.x + 'px',
        top: cropBox.y + 'px',
        width: cropBox.size + 'px',
        height: cropBox.size + 'px'
      }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd" @mousedown="onMouseDown">
        <!-- 九宫格线 -->
        <view class="grid-lines">
          <view class="grid-line horizontal"></view>
          <view class="grid-line horizontal"></view>
          <view class="grid-line vertical"></view>
          <view class="grid-line vertical"></view>
        </view>

        <!-- 四角控制点 -->
        <view class="corner-tl" @touchstart.stop="onCornerStart('tl', $event)"
          @mousedown.stop="onCornerMouseDown('tl', $event)"></view>
        <view class="corner-tr" @touchstart.stop="onCornerStart('tr', $event)"
          @mousedown.stop="onCornerMouseDown('tr', $event)"></view>
        <view class="corner-bl" @touchstart.stop="onCornerStart('bl', $event)"
          @mousedown.stop="onCornerMouseDown('bl', $event)"></view>
        <view class="corner-br" @touchstart.stop="onCornerStart('br', $event)"
          @mousedown.stop="onCornerMouseDown('br', $event)"></view>
      </view>
    </view>
  </view>
</template>

<script>
import { baseUrl } from '@/common/config.js'
export default {
  data() {
    return {
      imageSrc: '',
      cropBox: {
        x: 0,
        y: 0,
        size: 200
      },
      imageSize: {
        width: 0,
        height: 0
      },
      containerSize: {
        width: 0,
        height: 0
      },
      displayWidth: 0,
      displayHeight: 0,
      imageOffsetX: 0,
      imageOffsetY: 0,
      scale: 1,
      touchStart: {},
      resizeStart: {},
      isDragging: false,
      isResizing: false,
      isH5: false,
      tempBlobUrl: ''
    }
  },
  onLoad(options) {
    this.imageSrc = decodeURIComponent(options.imageSrc)
    this.initCropBox()
    const sysInfo = uni.getSystemInfoSync()
    this.isH5 = sysInfo.platform === 'h5'
  },
  onReady() {
    this.getImageInfo()
  },
  onUnload() {
    // 销毁blob内存
    if (this.isH5 && this.tempBlobUrl) {
      URL.revokeObjectURL(this.tempBlobUrl)
    }
  },
  methods: {
    initCropBox() {
      this.cropBox.size = 200
    },
    getImageInfo() {
      uni.getImageInfo({
        src: this.imageSrc,
        success: (res) => {
          this.imageSize.width = res.width
          this.imageSize.height = res.height
          this.calculateCropBox()
        },
        fail: () => {
          this.imageSize.width = 1000
          this.imageSize.height = 1000
          this.calculateCropBox()
        }
      })
    },
    calculateCropBox() {
      const sysInfo = uni.getSystemInfoSync()
      const headerHeight = 44
      this.containerSize.width = sysInfo.windowWidth
      this.containerSize.height = sysInfo.windowHeight - headerHeight
      this.scale = Math.min(
        this.containerSize.width / this.imageSize.width,
        this.containerSize.height / this.imageSize.height
      )
      this.displayWidth = this.imageSize.width * this.scale
      this.displayHeight = this.imageSize.height * this.scale
      this.imageOffsetX = (this.containerSize.width - this.displayWidth) / 2
      this.imageOffsetY = (this.containerSize.height - this.displayHeight) / 2
      this.imageOffsetX = Math.max(0, this.imageOffsetX)
      this.imageOffsetY = Math.max(0, this.imageOffsetY)
      const maxBoxSize = Math.min(200, Math.min(this.displayWidth, this.displayHeight))
      this.cropBox.size = Math.max(80, maxBoxSize)
      this.cropBox.x = this.imageOffsetX + (this.displayWidth - this.cropBox.size) / 2
      this.cropBox.y = this.imageOffsetY + (this.displayHeight - this.cropBox.size) / 2
    },
    onTouchStart(e) {
      const touch = e.touches[0]
      this.touchStart = {
        x: touch.clientX,
        y: touch.clientY,
        boxX: this.cropBox.x,
        boxY: this.cropBox.y
      }
      this.isDragging = true
    },
    onTouchMove(e) {
      if (!this.isDragging) return
      e.preventDefault()
      const touch = e.touches[0]
      const deltaX = touch.clientX - this.touchStart.x
      const deltaY = touch.clientY - this.touchStart.y
      let newX = this.touchStart.boxX + deltaX
      let newY = this.touchStart.boxY + deltaY
      const maxX = this.imageOffsetX + this.displayWidth - this.cropBox.size
      const maxY = this.imageOffsetY + this.displayHeight - this.cropBox.size
      const effectiveMinX = Math.min(this.imageOffsetX, maxX)
      const effectiveMaxX = Math.max(this.imageOffsetX, maxX)
      const effectiveMinY = Math.min(this.imageOffsetY, maxY)
      const effectiveMaxY = Math.max(this.imageOffsetY, maxY)
      newX = Math.max(effectiveMinX, Math.min(newX, effectiveMaxX))
      newY = Math.max(effectiveMinY, Math.min(newY, effectiveMaxY))
      this.cropBox.x = newX
      this.cropBox.y = newY
    },
    onTouchEnd() {
      this.isDragging = false
      this.isResizing = false
    },
    onMouseDown(e) {
      this.touchStart = {
        x: e.clientX,
        y: e.clientY,
        boxX: this.cropBox.x,
        boxY: this.cropBox.y
      }
      this.isDragging = true
      this.isResizing = false
      document.addEventListener('mousemove', this.onMouseMove)
      document.addEventListener('mouseup', this.onMouseUp)
    },
    onMouseMove(e) {
      if (!this.isDragging && !this.isResizing) return
      if (this.isDragging && !this.isResizing) {
        const deltaX = e.clientX - this.touchStart.x
        const deltaY = e.clientY - this.touchStart.y
        let newX = this.touchStart.boxX + deltaX
        let newY = this.touchStart.boxY + deltaY
        const maxX = this.imageOffsetX + this.displayWidth - this.cropBox.size
        const maxY = this.imageOffsetY + this.displayHeight - this.cropBox.size
        const effectiveMinX = Math.min(this.imageOffsetX, maxX)
        const effectiveMaxX = Math.max(this.imageOffsetX, maxX)
        const effectiveMinY = Math.min(this.imageOffsetY, maxY)
        const effectiveMaxY = Math.max(this.imageOffsetY, maxY)
        newX = Math.max(effectiveMinX, Math.min(newX, effectiveMaxX))
        newY = Math.max(effectiveMinY, Math.min(newY, effectiveMaxY))
        this.cropBox.x = newX
        this.cropBox.y = newY
      } else if (this.isResizing) {
        this.handleResize(e.clientX, e.clientY)
      }
    },
    onMouseUp() {
      this.isDragging = false
      this.isResizing = false
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
    },
    onCornerStart(corner, e) {
      const touch = e.touches[0]
      this.resizeStart = {
        x: touch.clientX,
        y: touch.clientY,
        boxX: this.cropBox.x,
        boxY: this.cropBox.y,
        boxSize: this.cropBox.size,
        corner: corner
      }
      this.isResizing = true
      this.isDragging = false
      document.addEventListener('touchmove', this.onCornerTouchMove)
      document.addEventListener('touchend', this.onCornerTouchEnd)
    },
    onCornerTouchMove(e) {
      if (!this.isResizing) return
      e.preventDefault()
      const touch = e.touches[0]
      this.handleResize(touch.clientX, touch.clientY)
    },
    onCornerTouchEnd() {
      this.isResizing = false
      document.removeEventListener('touchmove', this.onCornerTouchMove)
      document.removeEventListener('touchend', this.onCornerTouchEnd)
    },
    onCornerMouseDown(corner, e) {
      this.resizeStart = {
        x: e.clientX,
        y: e.clientY,
        boxX: this.cropBox.x,
        boxY: this.cropBox.y,
        boxSize: this.cropBox.size,
        corner: corner
      }
      this.isResizing = true
      this.isDragging = false
      document.addEventListener('mousemove', this.onCornerMouseMove)
      document.addEventListener('mouseup', this.onCornerMouseUp)
    },
    onCornerMouseMove(e) {
      if (!this.isResizing) return
      this.handleResize(e.clientX, e.clientY)
    },
    onCornerMouseUp() {
      this.isResizing = false
      document.removeEventListener('mousemove', this.onCornerMouseMove)
      document.removeEventListener('mouseup', this.onCornerMouseUp)
    },
    handleResize(clientX, clientY) {
      const deltaX = clientX - this.resizeStart.x
      const deltaY = clientY - this.resizeStart.y
      const corner = this.resizeStart.corner
      let newSize = this.resizeStart.boxSize
      let newX = this.resizeStart.boxX
      let newY = this.resizeStart.boxY
      const minSize = 80
      const maxSize = Math.min(this.displayWidth, this.displayHeight)
      switch (corner) {
        case 'br':
          newSize = Math.max(minSize, Math.min(maxSize, this.resizeStart.boxSize + deltaX))
          break
        case 'bl':
          newSize = Math.max(minSize, Math.min(maxSize, this.resizeStart.boxSize - deltaX))
          newX = this.resizeStart.boxX + deltaX
          break
        case 'tr':
          newSize = Math.max(minSize, Math.min(maxSize, this.resizeStart.boxSize - deltaY))
          newY = this.resizeStart.boxY + deltaY
          break
        case 'tl':
          const deltaTL = Math.max(-deltaX, -deltaY)
          newSize = Math.max(minSize, Math.min(maxSize, this.resizeStart.boxSize + deltaTL))
          newX = this.resizeStart.boxX + deltaX
          newY = this.resizeStart.boxY + deltaY
          break
      }
      newX = Math.max(this.imageOffsetX, Math.min(newX, this.imageOffsetX + this.displayWidth - newSize))
      newY = Math.max(this.imageOffsetY, Math.min(newY, this.imageOffsetY + this.displayHeight - newSize))
      this.cropBox.size = newSize
      this.cropBox.x = newX
      this.cropBox.y = newY
    },
    cancel() {
      uni.navigateBack()
    },
    confirm() {
      uni.showLoading({ title: '裁剪中...' })
      const sysInfo = uni.getSystemInfoSync()
      const isH5 = sysInfo.platform === 'h5'
      if (isH5) {
        this.cropImageH5()
      } else {
        this.cropImageMiniProgram()
      }
    },
    cropImageH5() {
      // 销毁上一次blob，释放内存
      if (this.tempBlobUrl) {
        URL.revokeObjectURL(this.tempBlobUrl)
        this.tempBlobUrl = ''
      }
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const outputSize = 150
      canvas.width = outputSize
      canvas.height = outputSize
      const img = new Image()
      img.crossOrigin = 'anonymous'
      img.src = this.imageSrc
      img.onload = () => {
        const boxInImageX = this.cropBox.x - this.imageOffsetX
        const boxInImageY = this.cropBox.y - this.imageOffsetY
        const cropX = boxInImageX / this.scale
        const cropY = boxInImageY / this.scale
        const cropSize = this.cropBox.size / this.scale
        const actualCropX = Math.max(0, cropX)
        const actualCropY = Math.max(0, cropY)
        const actualCropSize = Math.min(cropSize, img.width - actualCropX, img.height - actualCropY)
        ctx.clearRect(0, 0, outputSize, outputSize)
        ctx.drawImage(img, actualCropX, actualCropY, actualCropSize, actualCropSize, 0, 0, outputSize, outputSize)

        const quality = 0.65 // 进一步降低压缩质量
        if (canvas.toBlob) {
          canvas.toBlob((blob) => {
            this.tempBlobUrl = URL.createObjectURL(blob)
            // 清空原图img内存缓冲区
            img.src = ''
            this.handleCropResult(this.tempBlobUrl)
          }, 'image/jpeg', quality)
        } else {
          const jpgBase64 = canvas.toDataURL('image/jpeg', quality)
          const arr = jpgBase64.split(',')
          const byteStr = atob(arr[1])
          const byteArr = new Uint8Array([...byteStr].map(c => c.charCodeAt(0)))
          const blob = new Blob([byteArr], { type: 'image/jpeg' })
          this.tempBlobUrl = URL.createObjectURL(blob)
          img.src = ''
          this.handleCropResult(this.tempBlobUrl)
        }
      }
      img.onerror = () => {
        uni.hideLoading()
        uni.showToast({ title: '裁剪失败', icon: 'none' })
      }
    },
    //
    cropImageMiniProgram() {
      uni.getImageInfo({
        src: this.imageSrc,
        success: (imgInfo) => {
          const boxInImageX = this.cropBox.x - this.imageOffsetX
          const boxInImageY = this.cropBox.y - this.imageOffsetY
          const cropX = boxInImageX / this.scale
          const cropY = boxInImageY / this.scale
          const cropSize = this.cropBox.size / this.scale

          // 确保裁剪区域不超出原图边界
          const actualCropX = Math.max(0, Math.min(cropX, imgInfo.width - 1))
          const actualCropY = Math.max(0, Math.min(cropY, imgInfo.height - 1))
          const maxCropSize = Math.min(
            cropSize,
            imgInfo.width - actualCropX,
            imgInfo.height - actualCropY
          )
          const actualCropSize = Math.max(1, maxCropSize)

          // 输出尺寸等于实际裁剪区域大小，不填充，保持正方形
          const outputSize = Math.min(Math.floor(cropSize), 200)

          const ctx = uni.createCanvasContext('cropCanvas')
          ctx.clearRect(0, 0, 200, 200)
          ctx.drawImage(
            this.imageSrc,
            cropX,      // 直接使用，不再调整
            cropY,      // 直接使用，不再调整
            cropSize,
            cropSize,
            0,
            0,
            outputSize,
            outputSize
          )
          ctx.draw(false, () => {
            uni.canvasToTempFilePath({
              canvasId: 'cropCanvas',
              x: 0,
              y: 0,
              width: outputSize,
              height: outputSize,
              fileType: 'jpeg',
              quality: 0.8,
              success: (res) => {
                this.handleCropResult(res.tempFilePath)
              },
              fail: (err) => {
                uni.hideLoading()
                console.error('canvas导出失败', err)
                uni.showToast({ title: '裁剪导出图片失败', icon: 'none' })
              }
            })
          })
        },
        fail: () => {
          uni.hideLoading()
          uni.showToast({ title: '读取原图失败', icon: 'none' })
        }
      })
    },
    // 裁剪完成统一入口：先上传，成功再回传页面
    async handleCropResult(tempFilePath) {
      try {
        // 执行上传
        const obj = await this.uploadCropImage(tempFilePath)
        console.log('obj', obj)
        // 更新上一页数据
        const pages = getCurrentPages()
        if (pages.length >= 2) {
          const prevPage = pages[pages.length - 2]
          if (prevPage && prevPage.$data && prevPage.$data.baseInfo) {
            prevPage.$data.baseInfo.portraitPhotoList = [{
              url: obj.fullImgUrl,
              //fileUrl: obj.fileUrl,
              contentType: 'image/jpeg',
              type: 'jpg',
              size: obj.size
            }]
          }
        }
        uni.navigateBack()
        // 上传成功再返回线上地址给上一页
        uni.$emit('cropSuccess', uploadUrl)
        uni.navigateBack()
      } catch (err) {
        uni.hideLoading()
        uni.showToast({ title: err.message || '上传失败', icon: 'none' })
      }
    },
    async getImageFileInfo(tempPath) {
      const sysInfo = uni.getSystemInfoSync()
      const isH5 = sysInfo.platform === 'h5'

      if (isH5 && tempPath.startsWith('blob:')) {
        // H5 微信服务号：从blob读取
        const res = await fetch(tempPath)
        const blob = await res.blob()
        const size = blob.size
        const type = blob.type || 'image/png'
        const format = type.split('/')[1]
        return {
          size,
          kb: (size / 1024).toFixed(2),
          mb: (size / 1024 / 1024).toFixed(3),
          type,
          format
        }
      } else {
        // 小程序：uni.getFileInfo 读取本地临时文件
        return new Promise((resolve, reject) => {
          uni.getFileInfo({
            filePath: tempPath,
            success: async (res) => {
              // 小程序无直接type，通过图片后缀判断
              let format = 'png'
              if (tempPath.includes('.jpg') || tempPath.includes('.jpeg')) format = 'jpeg'
              if (tempPath.includes('.webp')) format = 'webp'
              const type = `image/${format}`
              const size = res.size
              resolve({
                size,
                kb: (size / 1024).toFixed(2),
                mb: (size / 1024 / 1024).toFixed(3),
                type,
                format
              })
            },
            fail: reject
          })
        })
      }
    },
    // 格式化文件大小
    formatFileSize(bytes) {
      if (bytes === 0) return '0 B'
      const k = 1024
      const sizes = ['B', 'KB', 'MB', 'GB']
      const i = Math.floor(Math.log(bytes) / Math.log(k))
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
    },
    // 【核心修复】完整上传函数，返回线上图片地址
    async uploadCropImage(tempFilePath) {
      const token = uni.getStorageSync('access-token')
      if (!token) throw new Error('登录失效，请重新登录')
      const headers = { Authorization: `tf: ${token}` }
      const sysInfo = uni.getSystemInfoSync()
      const isH5 = sysInfo.platform === 'h5'
      let uploadResult = null

      const imgInfo = await this.getImageFileInfo(tempFilePath)
      console.log('裁剪图片完整信息：', imgInfo)
      // 弹窗展示大小、格式
      // uni.showToast({
      //   title: `大小${imgInfo.kb}KB ${imgInfo.format}`,
      //   icon: 'none',
      //   duration: 1800
      // })

      // 分支1：H5 微信服务号 blob 上传
      if (isH5 && tempFilePath.startsWith('blob:')) {
        const resBlob = await fetch(tempFilePath)
        const blob = await resBlob.blob()
        const size = blob.size
        console.log('裁剪图片大小', size, this.formatFileSize(size))
        const formData = new FormData()
        formData.append('file', blob, 'crop_avatar.jpg')
        formData.append('biz', 'temp')
        const resp = await fetch(`${baseUrl}/common/uploads`, {
          method: 'POST',
          headers,
          body: formData
        })
        console.log('formData', formData)
        uploadResult = await resp.json()

      } else {
        // 分支2：微信小程序 补充完整uni.uploadFile逻辑（原代码缺失）
        uploadResult = await new Promise((resolve, reject) => {
          uni.uploadFile({
            url: `${baseUrl}/common/uploads`,
            filePath: tempFilePath,
            name: 'file',
            header: headers,
            formData: { biz: 'temp' },
            success: (res) => resolve(JSON.parse(res.data)),
            fail: reject
          })
        })
      }

      //console.log('上传结果', uploadResult)

      // 校验后端返回
      if (!uploadResult || uploadResult.code !== 200) {
        throw new Error(uploadResult?.msg || '接口返回失败')
      }
      // 拼接完整线上图片地址
      const fullImgUrl = `${baseUrl}/${uploadResult.url.replace(/^\//, '')}`
      return { fileUrl: uploadResult.url, size: imgInfo.size, fullImgUrl: fullImgUrl }
    }
  }
}
</script>

<style lang="scss" scoped>
.crop-page {
  width: 100%;
  height: 100vh;
  background: #1a1a1a;
  display: flex;
  flex-direction: column;
}

.crop-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20rpx 30rpx;
  background: #333;
}

.header-btn {
  font-size: 32rpx;
  padding: 10rpx 20rpx;

  &.cancel {
    color: #999;
  }

  &.confirm {
    color: #4CAF50;
    font-weight: bold;
  }
}

.header-title {
  font-size: 34rpx;
  color: #fff;
  font-weight: bold;
}

.crop-container {
  flex: 1;
  position: relative;
  overflow: hidden;

  .original-image {
    width: 100%;
    height: 100%;
  }

  // 裁剪用canvas（隐藏）
  .crop-canvas {
    position: absolute;
    width: 200px;
    height: 200px;
    opacity: 0;
    pointer-events: none;
  }

  // 四块半透明遮罩，框外变暗
  .mask-top,
  .mask-bottom,
  .mask-left,
  .mask-right {
    position: absolute;
    background: rgba(0, 0, 0, 0.7);
    z-index: 5;
  }

  .mask-top {
    left: 0;
    right: 0;
    top: 0;
  }

  .mask-bottom {
    left: 0;
    right: 0;
  }

  .mask-left {
    left: 0;
  }

  .mask-right {
    right: 0;
  }

  .crop-box {
    position: absolute;
    border: 2px solid #fff;
    box-sizing: border-box;
    z-index: 10;
    cursor: move;
    touch-action: none;
    user-select: none;
    background: transparent;
    filter: brightness(1.08); // 轻微提亮框内图片，增强高亮对比

    .grid-lines {
      width: 100%;
      height: 100%;
      position: relative;

      .grid-line {
        position: absolute;
        background: rgba(255, 255, 255, 0.5);

        &.horizontal {
          width: 100%;
          height: 1px;
          top: 33.33%;

          &:nth-child(2) {
            top: 66.66%;
          }
        }

        &.vertical {
          width: 1px;
          height: 100%;
          left: 33.33%;

          &:nth-child(4) {
            left: 66.66%;
          }
        }
      }
    }

    .corner-tl,
    .corner-tr,
    .corner-bl,
    .corner-br {
      position: absolute;
      width: 30rpx;
      height: 30rpx;
      border: 3px solid #fff;
      background: #333;
    }

    .corner-tl {
      top: -15rpx;
      left: -15rpx;
      border-right: none;
      border-bottom: none;
      cursor: nwse-resize;
    }

    .corner-tr {
      top: -15rpx;
      right: -15rpx;
      border-left: none;
      border-bottom: none;
      cursor: nesw-resize;
    }

    .corner-bl {
      bottom: -15rpx;
      left: -15rpx;
      border-right: none;
      border-top: none;
      cursor: nesw-resize;
    }

    .corner-br {
      bottom: -15rpx;
      right: -15rpx;
      border-left: none;
      border-top: none;
      cursor: nwse-resize;
    }
  }
}
</style>