<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">
      <button class="btn btn-default" @tap="onManualInput">手动输入</button>
    </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 { verifyQrCode } from '@/api/inspection'
// jsQR:纯 JS QR 识别库,作为 BarcodeDetector 不可用时的 fallback(iOS Safari < 17 / 老 WebView)
import jsQR from 'jsqr'

const taskId = ref<number>(0)
const scanResult = ref<string>('')
const tipText = ref<string>('请将摄像头对准巡检点位的二维码')

onLoad((q) => {
  taskId.value = Number(q?.taskId || 0)
  startScan()
})

let scanTimer: any = null
let h5Stream: MediaStream | null = null
let h5RafId: number | null = null

function startScan() {
  // #ifdef APP-PLUS || MP-WEIXIN
  // APP/小程序:调起原生扫码(uni.scanCode 会自动开摄像头 + 弹原生 UI)
  scanTimer = setTimeout(() => {
    uni.scanCode({
      onlyFromCamera: true,
      scanType: ['qrCode'],
      success: (res) => {
        scanResult.value = res.result
        onScanSuccess(res.result)
      },
      fail: () => {
        // 用户取消
      }
    })
  }, 300)
  // #endif

  // #ifdef H5
  // H5 端:用浏览器原生 BarcodeDetector + <video> 预览
  // 浏览器不支持(iOS Safari < 16.4)→ 提示降级到"手动输入"
  h5StartBarcodeScanner()
  // #endif
}

// ============================== H5 端扫码实现 ==============================
async function h5StartBarcodeScanner() {
  // 1. 检测摄像头权限 API
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    tipText.value = '浏览器不支持获取摄像头,请点击下方"手动输入"'
    return
  }
  try {
    // 2. 申请摄像头(优先后置)
    h5Stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: { ideal: 'environment' } },
      audio: false
    })
  } catch (e: any) {
    if (e?.name === 'NotAllowedError' || e?.name === 'PermissionDeniedError') {
      tipText.value = '摄像头权限被拒绝,请在浏览器设置中允许后重试'
    } else if (e?.name === 'NotFoundError') {
      tipText.value = '未找到可用摄像头,请点击下方"手动输入"'
    } else {
      tipText.value = '摄像头启动失败,请点击下方"手动输入"'
    }
    console.warn('[H5 扫码] getUserMedia 失败', e)
    return
  }

  // 3. 创建 <video> 元素并播放
  const wrap = document.getElementById('h5-video-wrap')
  if (!wrap) return
  wrap.innerHTML = ''
  const video = document.createElement('video')
  video.setAttribute('playsinline', 'true') // iOS 防止自动全屏
  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)
  }

  // 4. 选 QR 识别方案:
  //    优先 BarcodeDetector(Chrome/Edge/iOS 17+ 性能好),
  //    不可用时 fallback jsQR(纯 JS,兼容老浏览器但稍慢)
  const BarcodeDetectorCtor: any = (window as any).BarcodeDetector
  if (typeof BarcodeDetectorCtor === 'function') {
    let detector: any
    try {
      detector = new BarcodeDetectorCtor({ formats: ['qr_code'] })
    } catch (e) {
      // ignore
    }
    if (detector) {
      const tick = async () => {
        if (!h5Stream) return
        try {
          const codes = await detector.detect(video)
          if (codes && codes.length && codes[0].rawValue) {
            h5StopScanner()
            scanResult.value = codes[0].rawValue
            onScanSuccess(codes[0].rawValue)
            return
          }
        } catch (e) {
          // 单帧失败忽略
        }
        h5RafId = requestAnimationFrame(tick)
      }
      h5RafId = requestAnimationFrame(tick)
      return
    }
  }

  // 5. Fallback:jsQR(用 canvas 抽帧识别)
  tipText.value = '正在用 jsQR 识别中…'
  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)
      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
      }
    }
    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()
})

async function onScanSuccess(qrContent: string) {
  uni.showLoading({ title: '校验中…' })
  try {
    // 1. 校验二维码与任务绑定关系
    // 走统一 request 后:data 即后端 R.data(这里后端直接返回 boolean)
    const valid = await verifyQrCode(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()

    // 任务点位经纬度（实际项目从任务详情中获取）
    const taskLng = uni.getStorageSync(`task_lng_${taskId.value}`) || 0
    const taskLat = uni.getStorageSync(`task_lat_${taskId.value}`) || 0
    if (taskLng && taskLat) {
      const dist = calculateDistance(taskLat, loc.longitude, taskLng, loc.latitude)
      const limitMeter = uni.getStorageSync('scan_limit_meter') || 100 // 系统配置
      if (dist > limitMeter) {
        uni.hideLoading()
        uni.showToast({
          title: `距点位 ${Math.round(dist)}m，超出 ${limitMeter}m 范围`,
          icon: 'none',
          duration: 3000
        })
        return
      }
    }

    uni.hideLoading()
    // 3. 校验通过：跳转到打卡页
    uni.redirectTo({
      url: `/pages/inspection/check/index?taskId=${taskId.value}&lng=${loc.longitude}&lat=${loc.latitude}`
    })
  } 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>
.scan-page {
  position: relative;
  width: 100%;
  height: 100vh;
  background: #000000;
  overflow: hidden;
}
/* H5 端:video 预览容器铺满底层,扫码框在上层 */
.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;
  z-index: 3;
}
.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-right: 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;
  z-index: 4;
}
.scan-btn-row {
  position: absolute;
  bottom: 80rpx;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  z-index: 4;
  .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; }
  }
}
</style>
