<template>
  <view class="holo-container">
    <view class="holo-frame" :style="frameStyle">
      <!-- WebGL 渲染层 —— Canvas 缓冲 = CSS × DPR，Mipmap + MSAA -->
      <canvas
        v-show="webglReady && !hasError"
        ref="webglCanvas"
        class="holo-canvas"
        :style="canvasStyle"
        @touchstart.stop="onTouchStart"
        @touchmove.stop.prevent="onTouchMove"
        @touchend.stop="onTouchEnd"
        @touchcancel.stop="onTouchEnd"
      />

      <!-- CSS 降级层（WebGL 不可用时自动启用） -->
      <view v-if="!webglReady || hasError" class="holo-fallback" :style="fallbackStyle">
        <view class="holo-fallback-card" :style="fallbackCardStyle">
          <image
            v-if="baseImageSrc"
            class="holo-fallback-img"
            :src="baseImageSrc"
            mode="aspectFill"
          />
          <view class="holo-fallback-shimmer" />
          <view class="holo-fallback-rim" />
        </view>
      </view>

      <view v-if="showHint && hintText" class="holo-hint" :class="{ 'holo-hint--hidden': !showHint }">
        <text class="holo-hint-icon">↻</text>
        <text class="holo-hint-text">{{ hintText }}</text>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { HolographicEngine } from '@/utils/webgl/holographic-engine.js'
import { loadTextureImage } from '@/utils/laser-card/laserPreviewWebgl.js'

const props = defineProps({
  baseImageSrc: { type: String, default: '' },
  aspectRatio: { type: Number, default: 3 / 4 },
  maxWidth: { type: Number, default: 420 },
  cornerRadius: { type: Number, default: 24 },
  effectIntensity: { type: Number, default: 0.85 },
  dispersionStrength: { type: Number, default: 1.0 },
  diffractionScale: { type: Number, default: 0.7 },
  highlightSpeed: { type: Number, default: 0.8 },
  highlightWidth: { type: Number, default: 1.0 },
  fresnelPower: { type: Number, default: 3.5 },
  noiseScale: { type: Number, default: 1.0 },
  noiseOctaves: { type: Number, default: 6 },
  safeZoneRadius: { type: Number, default: 0.35 },
  safeZoneSoftness: { type: Number, default: 0.15 },
  viewX: { type: Number, default: 0 },
  viewY: { type: Number, default: 0 },
  viewZ: { type: Number, default: 0 },
  hintText: { type: String, default: '移动光标或倾斜设备' },
  showHint: { type: Boolean, default: true },
  maxDPR: { type: Number, default: 2 },
  skipBuiltInTouch: { type: Boolean, default: false },
})

const emit = defineEmits(['simulate', 'ready', 'error', 'fpsUpdate'])

const webglCanvas = ref(null)
const webglReady = ref(false)
const hasError = ref(false)
let engine = null
let resizeObserver = null
let fpsInterval = null

function resolveSize() {
  const vw = typeof window !== 'undefined' ? window.innerWidth : 375
  const w = Math.min(props.maxWidth, vw * 0.92)
  const h = w / props.aspectRatio
  return { w, h }
}

const frameStyle = computed(() => {
  const { w, h } = resolveSize()
  return { width: `${w}px`, height: `${h}px` }
})
const canvasStyle = computed(() => {
  const { w, h } = resolveSize()
  return { width: `${w}px`, height: `${h}px` }
})
const fallbackStyle = computed(() => {
  const { w, h } = resolveSize()
  return { width: `${w}px`, height: `${h}px` }
})
const fallbackCardStyle = computed(() => {
  const vx = props.viewX || 0
  const vy = props.viewY || 0
  return {
    borderRadius: `${props.cornerRadius}px`,
    transform: `perspective(1000px) rotateY(${vx * 15}deg) rotateX(${-vy * 8}deg)`,
  }
})

function dispatchTouch(clientX, clientY) {
  if (props.skipBuiltInTouch) return
  const canvas = webglCanvas.value
  if (!canvas) return
  const rect = canvas.getBoundingClientRect()
  if (!rect.width || !rect.height) return
  const cx = rect.left + rect.width / 2
  const cy = rect.top + rect.height / 2
  emit('simulate',
    Math.max(-1, Math.min(1, (clientX - cx) / (rect.width / 2) * 1.1)),
    Math.max(-1, Math.min(1, (clientY - cy) / (rect.height / 2) * 1.1))
  )
}

function getT(e) {
  if (e.touches && e.touches[0]) return e.touches[0]
  if (e.changedTouches && e.changedTouches[0]) return e.changedTouches[0]
  return null
}
function onTouchStart(e) { onTouchMove(e) }
function onTouchMove(e) {
  if (props.skipBuiltInTouch) return
  const t = getT(e)
  if (t) dispatchTouch(t.clientX, t.clientY)
}
function onTouchEnd() {
  if (props.skipBuiltInTouch) return
  emit('simulate', 0, 0)
}

function syncEngineParams() {
  if (!engine) return
  engine.setEffectParams({
    effectIntensity: props.effectIntensity,
    dispersionStrength: props.dispersionStrength,
    diffractionScale: props.diffractionScale,
    highlightSpeed: props.highlightSpeed,
    highlightWidth: props.highlightWidth,
    fresnelPower: props.fresnelPower,
    noiseScale: props.noiseScale,
    noiseOctaves: Math.round(props.noiseOctaves),
    cardCornerRadius: props.cornerRadius,
    safeZoneRadius: props.safeZoneRadius,
    safeZoneSoftness: props.safeZoneSoftness,
  })
  engine.setViewAngle(props.viewX, props.viewY, props.viewZ)
}

watch(() => [
  props.effectIntensity, props.dispersionStrength, props.diffractionScale,
  props.highlightSpeed, props.highlightWidth, props.fresnelPower,
  props.noiseScale, props.noiseOctaves, props.cornerRadius,
  props.safeZoneRadius, props.safeZoneSoftness,
  props.viewX, props.viewY, props.viewZ,
], syncEngineParams)

watch(() => props.baseImageSrc, async (src) => {
  if (!engine || !src) return
  try {
    const img = await loadTextureImage(src)
    engine.uploadBaseImage(img)
  } catch (e) {
    console.warn('[HolographicCard] load baseImage failed:', e)
  }
})

function initWebGL() {
  if (!HolographicEngine.isSupported()) {
    hasError.value = true
    emit('error', new Error('WebGL not supported'))
    return
  }
  const canvas = webglCanvas.value
  if (!canvas) return
  try {
    const { w, h } = resolveSize()
    engine = new HolographicEngine(canvas, {
      alpha: true,
      antialias: true,
      useFBO: false,
    })
    engine.setDPR(Math.min(window.devicePixelRatio || 1, props.maxDPR))
    if (!engine.init()) {
      hasError.value = true
      emit('error', new Error('WebGL init failed'))
      return
    }
    syncEngineParams()
    engine.start()
    webglReady.value = true
    emit('ready')

    if (typeof ResizeObserver !== 'undefined') {
      resizeObserver = new ResizeObserver((entries) => {
        for (const e of entries) {
          const { width, height } = e.contentRect
          if (engine && width > 0 && height > 0) engine.resize(width, height)
        }
      })
      resizeObserver.observe(canvas.parentElement || canvas)
    }

    fpsInterval = setInterval(() => {
      if (engine) emit('fpsUpdate', engine.getFPS())
    }, 1000)

    if (props.baseImageSrc) {
      loadTextureImage(props.baseImageSrc)
        .then(img => engine.uploadBaseImage(img))
        .catch(e => console.warn('[HolographicCard] init load:', e))
    }
  } catch (e) {
    console.error('[HolographicCard] init error:', e)
    hasError.value = true
    emit('error', e)
  }
}

onMounted(() => { nextTick(initWebGL) })

onUnmounted(() => {
  if (fpsInterval) { clearInterval(fpsInterval); fpsInterval = null }
  if (resizeObserver) { resizeObserver.disconnect(); resizeObserver = null }
  if (engine) { engine.destroy(); engine = null }
})
</script>

<style scoped>
.holo-container { width: 100%; display: flex; align-items: center; justify-content: center; }
.holo-frame { position: relative; max-width: 100%; }
.holo-canvas { display: block; border-radius: inherit; }

/* ---- CSS 降级 ---- */
.holo-fallback { position: relative; display: flex; align-items: center; justify-content: center; perspective: 1000px; }
.holo-fallback-card {
  width: 100%; height: 100%; overflow: hidden; position: relative;
  background-color: #060e20; box-shadow: 0 20px 50px rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.18);
  will-change: transform; transform-style: preserve-3d;
}
.holo-fallback-img { width: 100%; height: 100%; position: absolute; top: 0; left: 0; }
.holo-fallback-shimmer {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(135deg, rgba(221,183,255,0.08) 0%, rgba(100,200,255,0.15) 25%, transparent 50%, rgba(255,180,220,0.12) 75%, rgba(76,215,246,0.08) 100%);
}
.holo-fallback-rim {
  position: absolute; inset: 0;
  border-top: 1px solid rgba(221,183,255,0.42);
  box-shadow: inset 0 0 40px rgba(255,255,255,0.05);
  pointer-events: none;
}

/* ---- 提示 ---- */
.holo-hint {
  position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 6px;
  padding: 8px 16px; background: rgba(0,0,0,0.55); backdrop-filter: blur(12px);
  border-radius: 20px; transition: opacity 0.4s ease; pointer-events: none;
}
.holo-hint--hidden { opacity: 0; }
.holo-hint-icon { font-size: 16px; color: rgba(255,255,255,0.8); }
.holo-hint-text { font-size: 12px; color: rgba(255,255,255,0.75); }
</style>
