<template>
  <view class="scan-page">
    <!-- H5 端摄像头预览容器(由 JS 动态创建 video) -->
    <!-- #ifdef H5 -->
    <view id="h5-video-wrap" class="h5-video-wrap"></view>
    <!-- #endif -->

    <!-- 顶部黑色遮罩 -->
    <view class="overlay top-overlay"></view>
    <view class="overlay bottom-overlay"></view>
    <view class="overlay left-overlay"></view>
    <view class="overlay right-overlay"></view>

    <!-- 扫码框 -->
    <view class="scan-area">
      <view class="scan-corner tl"></view>
      <view class="scan-corner tr"></view>
      <view class="scan-corner bl"></view>
      <view class="scan-corner br"></view>
      <view class="scan-line"></view>
    </view>

    <view class="scan-tip">
      <text>{{ tipText }}</text>
    </view>

    <!-- 操作按钮组 -->
    <view class="scan-btn-row scan-btn-group">
      <button class="btn btn-default" @tap="onManualInput">手动输入</button>
      <button v-if="showPhotoScan" class="btn btn-default" @tap="onPhotoScan">拍照扫码</button>
    </view>

    <!-- 隐藏的 file input(用于微信等不支持 getUserMedia 的浏览器) -->
    <!-- #ifdef H5 -->
    <input
      ref="fileInputRef"
      type="file"
      accept="image/*"
      capture="environment"
      style="display:none"
      @change="onFileSelected"
    />
    <!-- #endif -->

    <!-- 点击重试按钮(仅 iOS getUserMedia 因手势问题失败时显示) -->
    <view v-if="scanRetry" class="scan-retry-overlay" @tap="onRetryScan">
      <view class="scan-retry-btn">
        <text class="scan-retry-icon">📷</text>
        <text class="scan-retry-text">点击重试扫码</text>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
  calculateDistance,
  getCurrentLocation,
  ensureLocationAuth
} from '@/utils/location'
import { verifyQualityQrCode, getScanRangeMeter } from '@/api/qualityInspection'
// jsQR:纯 JS QR 识别库,作为 BarcodeDetector 不可用时的 fallback
import jsQR from 'jsqr'

const taskId = ref<number>(0)
const scanResult = ref<string>('')
const tipText = ref<string>('请将摄像头对准品控巡检点位的二维码')
const scanRetry = ref<boolean>(false)
const showPhotoScan = ref<boolean>(false)
const fileInputRef = ref<any>(null)
let scanTimer: any = null
let h5Stream: MediaStream | null = null
let h5RafId: number | null = null

// 缓存任务点位经纬度(进入页时从 storage 取,真实项目可从任务详情接口返回)
let taskLng = 0
let taskLat = 0
// 每日多时段任务的当前时间段(从列表传入)
const slot = ref<string>('')

onLoad((q) => {
  taskId.value = Number(q?.taskId || 0)
  slot.value = String(q?.slot || '')
  taskLng = Number(uni.getStorageSync(`qtask_lng_${taskId.value}`) || 0)
  taskLat = Number(uni.getStorageSync(`qtask_lat_${taskId.value}`) || 0)
  // 自动启动扫码(大部分设备直接可用)
  // iOS Safari 若因手势问题失败,会显示"点击重试"按钮
  startScan()
})

// H5:拍照扫码(通过 file input 拍照,适用于微信等不支持 getUserMedia 的浏览器)
function onPhotoScan() {
  if (fileInputRef.value) {
    fileInputRef.value.value = '' // 清空以允许重复选择同一文件
    fileInputRef.value.click()
  }
}

// H5:file input 选择图片后的回调
async function onFileSelected(e: any) {
  const file = e?.target?.files?.[0]
  if (!file) return
  try {
    tipText.value = '正在识别二维码…'
    const img = await loadImageFromFile(file)
    const code = decodeQrFromImage(img)
    if (code) {
      h5StopScanner()
      scanResult.value = code
      onScanSuccess(code)
    } else {
      tipText.value = '未识别到二维码,请重新拍照'
      uni.showToast({ title: '未识别到二维码', icon: 'none' })
    }
  } catch (err) {
    tipText.value = '图片识别失败,请重试'
    console.warn('[拍照扫码] 识别失败', err)
  }
}

// H5:从 File 加载 Image 对象
function loadImageFromFile(file: File): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file)
    const img = new Image()
    img.onload = () => { URL.revokeObjectURL(url); resolve(img) }
    img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('图片加载失败')) }
    img.src = url
  })
}

// H5:用 jsQR 从图片中解码二维码
function decodeQrFromImage(img: HTMLImageElement): string | null {
  const canvas = document.createElement('canvas')
  // 限制最大尺寸,防止大图处理过慢
  const MAX = 1024
  let w = img.naturalWidth
  let h = img.naturalHeight
  if (w > MAX || h > MAX) {
    const ratio = Math.min(MAX / w, MAX / h)
    w = Math.round(w * ratio)
    h = Math.round(h * ratio)
  }
  canvas.width = w
  canvas.height = h
  const ctx = canvas.getContext('2d', { willReadFrequently: true })
  if (!ctx) return null
  ctx.drawImage(img, 0, 0, w, h)
  const imageData = ctx.getImageData(0, 0, w, h)
  const code = jsQR(imageData.data, imageData.width, imageData.height, {
    inversionAttempts: 'dontInvert'
  })
  return code?.data || null
}

// H5:用户点击重试扫码(确保 getUserMedia 在用户手势上下文中调用)
function onRetryScan() {
  scanRetry.value = false
  startScan()
}

function startScan() {
  // #ifdef APP-PLUS || MP-WEIXIN
  scanTimer = setTimeout(() => {
    uni.scanCode({
      onlyFromCamera: true,
      scanType: ['qrCode'],
      success: (res) => {
        scanResult.value = res.result
        onScanSuccess(res.result)
      },
      fail: () => {
        // 扫码失败:用户取消
      }
    })
  }, 300)
  // #endif

  // #ifdef H5
  h5StartBarcodeScanner()
  // #endif
}

// ============================== H5 端扫码实现 ==============================
async function h5StartBarcodeScanner() {
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    // 微信 X5 内核等不支持 getUserMedia,提供拍照扫码与手动输入
    showPhotoScan.value = true
    tipText.value = '当前浏览器不支持实时扫码,请使用拍照扫码或手动输入'
    return
  }
  try {
    h5Stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: { ideal: 'environment' } },
      audio: false
    })
  } catch (e: any) {
    if (e?.name === 'NotAllowedError' || e?.name === 'PermissionDeniedError') {
      // iOS Safari getUserMedia 需要用户手势,此时显示重试按钮
      scanRetry.value = true
      showPhotoScan.value = true
      tipText.value = '点击下方按钮开始扫码'
    } else if (e?.name === 'NotFoundError') {
      showPhotoScan.value = true
      tipText.value = '未找到可用摄像头,请使用拍照扫码或手动输入'
    } else {
      showPhotoScan.value = true
      tipText.value = '摄像头启动失败,请使用拍照扫码或手动输入'
    }
    console.warn('[H5 扫码] getUserMedia 失败', e)
    return
  }
  const wrap = document.getElementById('h5-video-wrap')
  if (!wrap) return
  wrap.innerHTML = ''
  const video = document.createElement('video')
  video.setAttribute('playsinline', 'true')
  video.setAttribute('autoplay', 'true')
  video.setAttribute('muted', 'true')
  video.muted = true
  video.style.cssText =
    'position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;z-index:0;'
  video.srcObject = h5Stream
  wrap.appendChild(video)
  try {
    await video.play()
  } catch (e) {
    console.warn('[H5 扫码] video.play 失败', e)
  }
  // 直接走 jsQR(纯 JS,iOS Safari 11+ 全支持)
  // 弃用 BarcodeDetector:iOS Safari 16.4 早期版本有 bug,实际识别率差
  tipText.value = '正在识别中…'
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d', { willReadFrequently: true })
  if (!ctx) {
    tipText.value = '当前浏览器不支持 QR 码识别,请点击下方"手动输入"'
    return
  }
  const tick = () => {
    if (!h5Stream) return
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
      canvas.width = video.videoWidth
      canvas.height = video.videoHeight
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
      try {
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
        const code = jsQR(imageData.data, imageData.width, imageData.height, {
          inversionAttempts: 'dontInvert'
        })
        if (code && code.data) {
          h5StopScanner()
          scanResult.value = code.data
          onScanSuccess(code.data)
          return
        }
      } catch (e) {
        // ignore
      }
    }
    h5RafId = requestAnimationFrame(tick)
  }
  h5RafId = requestAnimationFrame(tick)
}

function h5StopScanner() {
  if (h5RafId != null) {
    cancelAnimationFrame(h5RafId)
    h5RafId = null
  }
  if (h5Stream) {
    h5Stream.getTracks().forEach((t) => t.stop())
    h5Stream = null
  }
  const wrap = typeof document !== 'undefined' ? document.getElementById('h5-video-wrap') : null
  if (wrap) wrap.innerHTML = ''
}

onUnmounted(() => {
  if (scanTimer) clearTimeout(scanTimer)
  h5StopScanner()
  scanRetry.value = false
})

async function onScanSuccess(qrContent: string) {
  uni.showLoading({ title: '校验中…' })
  try {
    // 1. 校验二维码与任务绑定关系
    const valid = await verifyQualityQrCode(taskId.value, qrContent)
    if (!valid) {
      uni.hideLoading()
      uni.showToast({ title: '二维码与任务不匹配', icon: 'none' })
      return
    }

    // 2. 校验位置
    const auth = await ensureLocationAuth()
    if (!auth) {
      uni.hideLoading()
      return
    }
    const loc = await getCurrentLocation()

    // 任务点位经纬度(若未设置则跳过距离校验,仅获取位置坐标带到打卡页)
    if (taskLng && taskLat) {
      const limitMeter = await getScanRangeMeter()  // 系统配置
      const dist = calculateDistance(taskLat, loc.longitude, taskLng, loc.latitude)
      if (dist > limitMeter) {
        uni.hideLoading()
        uni.showToast({
          title: `距点位 ${Math.round(dist)}m,超出 ${limitMeter}m 范围`,
          icon: 'none',
          duration: 3000
        })
        return
      }
    }

    uni.hideLoading()
    // 3. 校验通过:跳转到品控打卡页
    //    每日多时段任务:带上当前 slot
    const slotQuery = slot.value ? `&slot=${encodeURIComponent(slot.value)}` : ''
    uni.redirectTo({
      url: `/pages/qualityInspection/check/index?taskId=${taskId.value}&lng=${loc.longitude}&lat=${loc.latitude}${slotQuery}`
    })
  } catch (e) {
    uni.hideLoading()
    uni.showToast({ title: '校验失败', icon: 'none' })
  }
}

function onManualInput() {
  uni.showModal({
    title: '手动输入任务码',
    editable: true,
    placeholderText: '请输入任务编号或二维码内容',
    success: (m) => {
      if (m.confirm && m.content) {
        onScanSuccess(m.content)
      }
    }
  })
}
</script>

<style lang="scss" scoped>
@import "@/static/styles/variables.scss";

.scan-page {
  position: relative;
  width: 100%;
  height: 100vh;
  background: #000000;
  overflow: hidden;
}
.h5-video-wrap {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
  background: #000000;
}
.overlay {
  position: absolute;
  background: rgba(0, 0, 0, 0.6);
  z-index: 2;
  pointer-events: none;
}
.top-overlay {
  top: 0;
  left: 0;
  right: 0;
  height: calc(50vh - 200rpx);
}
.bottom-overlay {
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(50vh - 200rpx);
}
.left-overlay {
  top: calc(50vh - 200rpx);
  left: 0;
  width: calc(50vw - 200rpx);
  height: 400rpx;
}
.right-overlay {
  top: calc(50vh - 200rpx);
  right: 0;
  width: calc(50vw - 200rpx);
  height: 400rpx;
}
.scan-area {
  position: absolute;
  top: calc(50vh - 200rpx);
  left: calc(50vw - 200rpx);
  width: 400rpx;
  height: 400rpx;
  background: transparent;
  overflow: hidden;
}
.scan-corner {
  position: absolute;
  width: 40rpx;
  height: 40rpx;
  border: 4rpx solid $primary;
  &.tl { top: 0; left: 0; border-right: none; border-bottom: none; }
  &.tr { top: 0; right: 0; border-left: none; border-bottom: none; }
  &.bl { bottom: 0; left: 0; border-right: none; border-top: none; }
  &.br { bottom: 0; right: 0; border-left: none; border-top: none; }
}
.scan-line {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4rpx;
  background: linear-gradient(to right, transparent, $primary, transparent);
  animation: scan 2s linear infinite;
}
@keyframes scan {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(400rpx); }
  100% { transform: translateY(0); }
}
.scan-tip {
  position: absolute;
  top: calc(50vh + 240rpx);
  left: 0;
  right: 0;
  text-align: center;
  color: #FFFFFF;
  font-size: $font-sm;
}
.scan-btn-row {
  position: absolute;
  bottom: 80rpx;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  &.scan-btn-group {
    gap: 24rpx;
    padding: 0 32rpx;
    .btn { flex: 1; max-width: 280rpx; }
  }
  .btn {
    width: 280rpx;
    height: 80rpx;
    background: rgba(255, 255, 255, 0.2);
    color: #FFFFFF;
    border: 1rpx solid rgba(255, 255, 255, 0.4);
    border-radius: 80rpx;
    &::after { border: none; }
  }
}

/* 点击重试按钮(仅 iOS 手势问题失败时显示) */
.scan-retry-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}
.scan-retry-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 240rpx;
  height: 240rpx;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  border: 2rpx solid rgba(255, 255, 255, 0.4);
}
.scan-retry-icon {
  font-size: 72rpx;
  margin-bottom: 12rpx;
}
.scan-retry-text {
  font-size: 28rpx;
  color: #FFFFFF;
}
</style>
