import {
  useState,
  useEffect,
  useMemo,
  useRef,
  Suspense,
  Component,
  type ReactNode,
} from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, ContactShadows, useGLTF } from '@react-three/drei'
import {
  Move3D,
  Pause,
  Play,
  RotateCcw,
  Loader2,
  Smartphone,
  Wifi,
  Sparkles,
  ChevronDown,
} from 'lucide-react'
import * as THREE from 'three'
import PageHeader from '@/components/layout/PageHeader'
import {
  GALLERY_ITEMS,
  GALLERY_CATEGORIES,
  type GalleryItem,
} from '@/data/gallery'
import { detectDevice, preloadModel, type DeviceInfo } from '@/utils/deviceDetect'
import { useFrameMonitor, useVisibilityPause, tryRegisterContext, unregisterContext } from '@/hooks/usePerformanceMonitor'
import { CachedGLB } from '@/components/gallery/CachedGLB'

// ============== 类型定义 ==============
type LoadState = 'idle' | 'loading' | 'loaded' | 'error'

// ============== 工具函数 ==============
/** 根据设备档位选择最优模型 URL */
function pickModelUrl(item: GalleryItem, device: DeviceInfo | null): string | null {
  if (!item.modelUrl) return null
  if (!item.modelUrls) return item.modelUrl
  if (!device) return item.modelUrls.desktop || item.modelUrl
  return (
    item.modelUrls[device.tier] ||
    item.modelUrls.mobile ||
    item.modelUrls.desktop ||
    item.modelUrl
  )
}

/** 根据档位选择 Canvas 渲染参数 */
function getCanvasConfig(device: DeviceInfo | null) {
  if (!device) {
    return {
      dpr: [1, 1.5] as [number, number],
      shadows: false,
      antialias: true,
      cameraFov: 38,
      cameraPos: [0, 0.3, 5.5] as [number, number, number],
    }
  }
  switch (device.tier) {
    case 'low':
      return {
        dpr: [1, 1] as [number, number],
        shadows: false,
        antialias: false,
        cameraFov: 40,
        cameraPos: [0, 0.3, 5.5] as [number, number, number],
      }
    case 'mobile':
      return {
        dpr: [1, 1.5] as [number, number],
        shadows: false,
        antialias: true,
        cameraFov: 38,
        cameraPos: [0, 0.3, 5.5] as [number, number, number],
      }
    default:
      return {
        dpr: [1, 2] as [number, number],
        shadows: true,
        antialias: true,
        cameraFov: 32,
        cameraPos: [0, 0.4, 6] as [number, number, number],
      }
  }
}

// ============== 错误边界 ==============
class ViewerErrorBoundary extends Component<
  { children: ReactNode; fallback: ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false }
  static getDerivedStateFromError() {
    return { hasError: true }
  }
  componentDidCatch(error: Error) {
    console.warn('[Gallery 3D] 错误:', error.message)
  }
  render() {
    if (this.state.hasError) return this.props.fallback
    return this.props.children
  }
}

// ============== GLB 模型组件 ==============
function GLBModel({
  url,
  autoRotate,
  resetSignal,
  isVisible,
}: {
  url: string
  autoRotate: boolean
  resetSignal: number
  isVisible: boolean
}) {
  const ref = useRef<THREE.Group>(null)
  const fittedRef = useRef<THREE.Object3D | null>(null)
  const gltf = useGLTF(url)
  useFrame((_, dt) => {
    // 视口外 / 不可见时停止旋转，省电
    if (autoRotate && isVisible && ref.current) ref.current.rotation.y += dt * 0.3
  })

  const fitted = useMemo(() => {
    const obj = (gltf as any).scene.clone(true)
    const box = new THREE.Box3().setFromObject(obj)
    const size = box.getSize(new THREE.Vector3())
    const center = box.getCenter(new THREE.Vector3())
    const maxDim = Math.max(size.x, size.y, size.z) || 1
    const scale = 2.4 / maxDim
    obj.scale.setScalar(scale)
    obj.position.x = -center.x * scale
    obj.position.y = -center.y * scale
    obj.position.z = -center.z * scale
    obj.traverse((c: any) => {
      if (c.isMesh) {
        c.castShadow = true
        c.receiveShadow = true
        // 释放未使用的几何体引用（GC）
        if (c.geometry) c.geometry.computeBoundingSphere()
        if (c.material && c.material.isMeshStandardMaterial) {
          c.material.roughness = Math.max(c.material.roughness, 0.5)
        }
      }
    })
    fittedRef.current = obj
    return obj
  }, [gltf])

  // 组件卸载时释放 Three.js 资源（防止内存泄漏）
  useEffect(() => {
    return () => {
      const obj = fittedRef.current
      if (obj) {
        obj.traverse((c: any) => {
          if (c.isMesh) {
            if (c.geometry) c.geometry.dispose()
            if (c.material) {
              if (Array.isArray(c.material)) c.material.forEach((m: any) => m.dispose())
              else c.material.dispose()
            }
          }
        })
        fittedRef.current = null
      }
    }
  }, [])

  useEffect(() => {
    if (ref.current) ref.current.rotation.set(0, 0, 0)
  }, [resetSignal])

  return (
    <group ref={ref}>
      <primitive object={fitted} />
    </group>
  )
}

// ============== 程序化几何体（GLB 缺失时的兜底） ==============
function ProceduralFallback({ item }: { item: GalleryItem }) {
  const ref = useRef<THREE.Group>(null)
  useFrame((_, dt) => {
    if (ref.current) ref.current.rotation.y += dt * 0.3
  })
  const c = item.gradient
  return (
    <group ref={ref}>
      <mesh castShadow>
        <torusKnotGeometry args={[0.7, 0.22, 100, 16]} />
        <meshStandardMaterial color={c[1]} metalness={0.6} roughness={0.3} />
      </mesh>
      <mesh position={[0, -1.2, 0]}>
        <cylinderGeometry args={[0.7, 0.7, 0.08, 32]} />
        <meshStandardMaterial color={c[0]} metalness={0.3} roughness={0.5} />
      </mesh>
    </group>
  )
}

// ============== 单个展品卡片（一行一个） ==============
function GalleryCard({
  item,
  device,
  shouldMount,
  index,
}: {
  item: GalleryItem
  device: DeviceInfo | null
  shouldMount: boolean
  index: number
}) {
  const [autoRotate, setAutoRotate] = useState(true)
  const [resetSignal, setResetSignal] = useState(0)
  const [loadState, setLoadState] = useState<LoadState>('idle')
  const [loadProgress, setLoadProgress] = useState(0)
  const [isVisible, setIsVisible] = useState(true)
  const cardRef = useRef<HTMLDivElement>(null)
  const canvasRef = useRef<HTMLCanvasElement>(null)

  // FPS 监测（仅 desktop 档位开启）
  const fpsMetrics = useFrameMonitor(canvasRef, loadState === 'loaded' && device?.tier === 'desktop')

  // 视口可见性 + Page Visibility 暂停
  useVisibilityPause(cardRef, (visible) => {
    setIsVisible(visible)
  })

  const modelUrl = pickModelUrl(item, device)
  const hasModel = !!modelUrl
  const canvasConfig = getCanvasConfig(device)

  // WebGL 上下文池管理（防止过多并发）
  useEffect(() => {
    if (!shouldMount || !hasModel) return
    const key = `gallery-${item.id}`
    const allowed = tryRegisterContext(key)
    if (!allowed) {
      console.warn(`[Gallery] 上下文池已满，跳过 #${item.id}`)
    }
    return () => unregisterContext(key)
  }, [shouldMount, hasModel, item.id])

  // 预加载模型（仅在 shouldMount 为 true 时才下载）
  useEffect(() => {
    if (!shouldMount || !modelUrl) {
      setLoadState(hasModel ? 'idle' : 'loaded')
      return
    }
    let cancelled = false
    setLoadState('loading')
    setLoadProgress(0)
    preloadModel(modelUrl, (p) => {
      if (!cancelled) setLoadProgress(p)
    })
      .then(() => {
        if (!cancelled) setLoadState('loaded')
      })
      .catch((e) => {
        if (!cancelled) {
          console.warn('[Gallery] 模型加载失败:', e.message)
          setLoadState('error')
        }
      })
    return () => {
      cancelled = true
    }
  }, [shouldMount, modelUrl, hasModel])

  // 设备徽标
  const deviceLabel = useMemo(() => {
    if (!device) return null
    if (device.tier === 'desktop') return '桌面'
    if (device.tier === 'mobile') return '移动'
    return '低配'
  }, [device])

  const isReady = shouldMount && loadState === 'loaded'
  // 视口外时切到 demand 模式（仅响应用户输入时渲染）
  const effectiveFrameloop =
    device?.tier === 'low' ? 'demand' : (isVisible ? 'always' : 'demand')

  return (
    <article
      ref={cardRef}
      data-item-id={item.id}
      className="group relative card-ink overflow-hidden border border-gold/20 hover:border-gold/50 transition-all duration-500"
    >
      {/* 3D 视口 - 70vh 大尺寸展示 */}
      <div className="relative w-full h-[70vh] min-h-[480px] bg-ink-deep overflow-hidden">
        {/* 高清加载背景图（多尺寸 srcset + 渐进式模糊到位） */}
        {item.loadingImage ? (
          <picture>
            <source
              type="image/webp"
              srcSet={item.loadingImage.srcset}
              sizes={item.loadingImage.sizes}
            />
            <img
              src={item.loadingImage.base}
              alt={item.name}
              width="1320"
              height="1190"
              className={`absolute inset-0 w-full h-full object-cover transition-all duration-1000 ease-out ${
                shouldMount && loadState === 'loaded'
                  ? 'opacity-0 scale-105 blur-md'
                  : 'opacity-100 scale-100 blur-0'
              }`}
              loading={index < 2 ? 'eager' : 'lazy'}
              decoding="async"
              fetchPriority={index < 2 ? 'high' : 'auto'}
            />
          </picture>
        ) : (
          <img
            src={item.localPhoto}
            alt={item.name}
            className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-700 ${
              shouldMount && loadState === 'loaded' ? 'opacity-0' : 'opacity-100'
            }`}
            loading="lazy"
            decoding="async"
          />
        )}

        {/* 暗色蒙层（增加文字可读性 + 加载中氛围） */}
        <div
          className={`absolute inset-0 bg-gradient-to-b from-black/30 via-black/10 to-black/40 transition-opacity duration-700 ${
            shouldMount && loadState === 'loaded' ? 'opacity-0' : 'opacity-100'
          }`}
        />

        {/* 加载中：左侧呼吸光带（动效） */}
        {shouldMount && loadState === 'loading' && (
          <div className="absolute inset-0 pointer-events-none overflow-hidden z-10">
            <div
              className="absolute -inset-1 bg-gradient-to-r from-transparent via-gold/15 to-transparent"
              style={{
                animation: 'gallery-shimmer 2.4s ease-in-out infinite',
                transform: 'translateX(-100%)',
              }}
            />
          </div>
        )}

        {/* Canvas - 仅在 shouldMount 时挂载（自动视口触发） */}
        {shouldMount && hasModel && (
          <ViewerErrorBoundary
            fallback={
              <div className="absolute inset-0 flex items-center justify-center bg-ink/70 text-readable-muted text-sm">
                3D 暂不可用
              </div>
            }
          >
            <div
              className={`absolute inset-0 transition-opacity duration-700 ${
                loadState === 'loaded' ? 'opacity-100' : 'opacity-0'
              }`}
            >
              <Canvas
                shadows={canvasConfig.shadows}
                camera={{
                  position: canvasConfig.cameraPos,
                  fov: canvasConfig.cameraFov,
                }}
                dpr={canvasConfig.dpr}
                gl={{
                  antialias: canvasConfig.antialias,
                  powerPreference: 'high-performance',
                  preserveDrawingBuffer: false,
                  stencil: false,
                  depth: true,
                  alpha: false,
                }}
                frameloop={effectiveFrameloop}
                performance={{ min: 0.5 }}
                onCreated={({ gl }) => {
                  // 捕获 canvas DOM 用于 FPS 监测
                  if (canvasRef.current !== gl.domElement) {
                    ;(canvasRef as any).current = gl.domElement
                  }
                }}
              >
                <ambientLight intensity={0.9} />
                <directionalLight
                  position={[5, 5, 5]}
                  intensity={device?.tier === 'desktop' ? 1.6 : 1.2}
                  castShadow={canvasConfig.shadows}
                  shadow-mapSize-width={1024}
                  shadow-mapSize-height={1024}
                />
                {device?.tier !== 'low' && (
                  <>
                    <directionalLight position={[-4, 3, 2]} intensity={0.8} color="#FFE4B5" />
                    <directionalLight position={[0, -2, -3]} intensity={0.4} color="#C9A66B" />
                    <pointLight position={[0, 4, 0]} intensity={0.6} color="#FFE4B5" />
                    <pointLight position={[-3, 0, 3]} intensity={0.4} color="#9EBDB5" />
                  </>
                )}
                <Suspense fallback={null}>
                  {loadState === 'loaded' && (
                    <GLBModel
                      url={modelUrl}
                      autoRotate={autoRotate}
                      resetSignal={resetSignal}
                      isVisible={isVisible}
                    />
                  )}
                </Suspense>
                {canvasConfig.shadows && (
                  <ContactShadows
                    position={[0, -1.4, 0]}
                    opacity={0.55}
                    scale={8}
                    blur={2.8}
                    far={2.5}
                    color="#000000"
                  />
                )}
                <OrbitControls
                  enablePan={false}
                  enableZoom
                  enableDamping
                  dampingFactor={0.08}
                  rotateSpeed={device?.tier === 'low' ? 0.6 : 0.8}
                  zoomSpeed={0.6}
                  minDistance={2.8}
                  maxDistance={9}
                  minPolarAngle={Math.PI / 6}
                  maxPolarAngle={Math.PI - Math.PI / 6}
                  autoRotate={autoRotate && loadState === 'loaded'}
                  autoRotateSpeed={device?.tier === 'low' ? 1.0 : 1.5}
                  touches={{ ONE: 0, TWO: 2 }}
                />
              </Canvas>
            </div>
          </ViewerErrorBoundary>
        )}

        {/* Canvas - 程序化兜底（无 GLB 时） */}
        {shouldMount && !hasModel && (
          <ViewerErrorBoundary fallback={null}>
            <div className="absolute inset-0">
              <Canvas
                camera={{ position: canvasConfig.cameraPos, fov: canvasConfig.cameraFov }}
                dpr={canvasConfig.dpr}
                gl={{ antialias: canvasConfig.antialias, powerPreference: 'high-performance' }}
              >
                <ambientLight intensity={0.9} />
                <directionalLight position={[5, 5, 5]} intensity={1.4} />
                <directionalLight position={[-4, 3, 2]} intensity={0.6} color="#FFE4B5" />
                <Suspense fallback={null}>
                  <ProceduralFallback item={item} />
                </Suspense>
                <ContactShadows position={[0, -1.4, 0]} opacity={0.55} scale={8} blur={2.8} far={2.5} />
                <OrbitControls
                  enablePan={false}
                  enableZoom
                  enableDamping
                  dampingFactor={0.08}
                  minDistance={2.5}
                  maxDistance={8}
                  autoRotate={autoRotate}
                  autoRotateSpeed={1.5}
                />
              </Canvas>
            </div>
          </ViewerErrorBoundary>
        )}

        {/* 加载进度条 */}
        {shouldMount && loadState === 'loading' && (
          <div className="absolute inset-0 flex flex-col items-center justify-center bg-ink/85 backdrop-blur-sm z-20">
            <Loader2 className="text-gold mb-4 animate-spin" size={44} />
            <div className="text-gold text-sm tracking-widest mb-3">3D 加载中</div>
            <div className="w-48 h-1 bg-paper/10 rounded-full overflow-hidden">
              <div
                className="h-full bg-gradient-to-r from-vermilion to-gold transition-all duration-300"
                style={{ width: `${Math.round(loadProgress * 100)}%` }}
              />
            </div>
            <div className="text-readable-muted text-[11px] tracking-widest mt-2">
              {Math.round(loadProgress * 100)}%
            </div>
          </div>
        )}

        {/* 错误提示 */}
        {shouldMount && loadState === 'error' && (
          <div className="absolute inset-0 flex items-center justify-center bg-ink/80 z-20">
            <div className="text-readable-muted text-sm">模型加载失败</div>
          </div>
        )}

        {/* 顶部徽标层 */}
        <div className="absolute top-5 left-5 z-10 flex flex-col gap-2 pointer-events-none">
          {item.isUnesco && (
            <div className="seal" style={{ width: 34, height: 34, fontSize: 12 }}>
              遗
            </div>
          )}
          {hasModel && (
            <div className="px-2.5 py-1 bg-black/60 border border-gold/40 text-gold text-[10px] tracking-widest flex items-center gap-1.5 backdrop-blur-sm">
              <Move3D size={10} /> 3D
            </div>
          )}
        </div>

        {/* 设备档位 + 画质（加载完成时） */}
        {shouldMount && loadState === 'loaded' && deviceLabel && (
          <div className="absolute top-5 right-5 z-10 flex flex-col gap-1.5 pointer-events-none">
            <div className="px-2.5 py-1 bg-black/60 border border-gold/40 text-gold text-[10px] tracking-widest flex items-center gap-1.5 backdrop-blur-sm">
              <Smartphone size={10} />
              {deviceLabel}
            </div>
            {device?.saveData && (
              <div className="px-2.5 py-1 bg-vermilion/30 border border-vermilion/60 text-vermilion text-[10px] tracking-widest flex items-center gap-1.5 backdrop-blur-sm">
                <Wifi size={10} /> 省流
              </div>
            )}
          </div>
        )}

        {/* 浮层控制按钮（仅加载完成时显示） */}
        {shouldMount && loadState === 'loaded' && (
          <div className="absolute bottom-5 right-5 z-10 flex flex-col gap-2">
            <button
              onClick={(e) => {
                e.stopPropagation()
                setAutoRotate((v) => !v)
              }}
              className={`px-4 py-2 rounded text-[11px] font-bold tracking-widest transition-all backdrop-blur-md ${
                autoRotate
                  ? 'bg-vermilion text-paper shadow-lg shadow-vermilion/30'
                  : 'bg-black/60 border border-gold/40 text-gold hover:bg-gold/20'
              }`}
              aria-label={autoRotate ? '停止自转' : '开始自转'}
            >
              {autoRotate ? (
                <>
                  <Pause size={11} className="inline mr-1 -mt-0.5" /> 停止自转
                </>
              ) : (
                <>
                  <Play size={11} className="inline mr-1 -mt-0.5" /> 自动旋转
                </>
              )}
            </button>
            <button
              onClick={(e) => {
                e.stopPropagation()
                setResetSignal((n) => n + 1)
              }}
              className="px-4 py-2 rounded text-[11px] font-bold tracking-widest bg-black/60 border border-gold/40 text-gold hover:bg-gold/20 backdrop-blur-md"
              aria-label="重置视角"
            >
              <RotateCcw size={11} className="inline mr-1 -mt-0.5" /> 重置视角
            </button>
          </div>
        )}

        {/* 左下角：序号 */}
        <div className="absolute bottom-5 left-5 z-10 pointer-events-none">
          <div className="flex items-center gap-2 px-3 py-1.5 bg-black/60 border border-gold/40 text-gold text-[11px] tracking-widest backdrop-blur-sm">
            <Sparkles size={11} />
            <span>NO.{(index + 1).toString().padStart(2, '0')}</span>
          </div>
        </div>
      </div>

      {/* 信息面板 - 卡片底部 */}
      <div className="p-7 lg:p-9">
        <div className="grid lg:grid-cols-3 gap-6 lg:gap-8">
          {/* 主信息 */}
          <div className="lg:col-span-2">
            <div className="flex items-center gap-3 mb-3 text-gold text-[11px] tracking-widest">
              <span className="opacity-80">{item.category}</span>
              <span className="opacity-40">·</span>
              <span className="opacity-80">{item.era}</span>
              <span className="opacity-40">·</span>
              <span className="opacity-80">{item.origin}</span>
              {item.isUnesco && (
                <>
                  <span className="opacity-40">·</span>
                  <span className="text-vermilion font-bold">UNESCO 人类非遗</span>
                </>
              )}
            </div>
            <h3 className="font-serif font-black text-paper text-2xl lg:text-3xl xl:text-4xl leading-tight tracking-wider mb-4">
              {item.name}
            </h3>
            <p className="text-readable-muted text-sm lg:text-base leading-relaxed mb-5">
              {item.description}
            </p>
            {item.story && (
              <div className="pt-4 border-t border-gold/15">
                <div className="text-gold text-[11px] tracking-widest mb-2 flex items-center gap-1.5">
                  <span className="w-1.5 h-1.5 bg-gold rounded-full" />
                  典藏故事
                </div>
                <p className="text-readable-muted text-sm leading-relaxed italic">
                  {item.story}
                </p>
              </div>
            )}
          </div>

          {/* 副信息：工艺 / 传承人 / 元数据 */}
          <div className="space-y-5">
            {item.craftFeatures && item.craftFeatures.length > 0 && (
              <div>
                <div className="text-gold text-[11px] tracking-widest mb-2.5 flex items-center gap-1.5">
                  <span className="w-1.5 h-1.5 bg-gold rounded-full" />
                  工艺特征
                </div>
                <div className="flex flex-wrap gap-1.5">
                  {item.craftFeatures.map((f) => (
                    <span
                      key={f}
                      className="px-2.5 py-1 text-[11px] tracking-wider text-paper bg-gold/10 border border-gold/30"
                    >
                      {f}
                    </span>
                  ))}
                </div>
              </div>
            )}
            {item.inheritor && (
              <div>
                <div className="text-gold text-[11px] tracking-widest mb-2 flex items-center gap-1.5">
                  <span className="w-1.5 h-1.5 bg-gold rounded-full" />
                  代表性传承人
                </div>
                <p className="text-readable-muted text-xs leading-relaxed">{item.inheritor}</p>
              </div>
            )}
            <div className="grid grid-cols-2 gap-3 pt-3 border-t border-gold/15">
              <div className="border-l-2 border-vermilion/60 pl-3">
                <div className="text-readable-subtle text-[10px] tracking-widest">产地</div>
                <div className="text-paper text-sm font-serif mt-0.5">{item.origin}</div>
              </div>
              <div className="border-l-2 border-vermilion/60 pl-3">
                <div className="text-readable-subtle text-[10px] tracking-widest">年代</div>
                <div className="text-paper text-sm font-serif mt-0.5">{item.era}</div>
              </div>
            </div>
          </div>
        </div>

        {/* 交互提示 */}
        {shouldMount && loadState === 'loaded' && (
          <div className="mt-6 pt-4 border-t border-gold/10 flex flex-wrap items-center gap-x-5 gap-y-2 text-[11px] text-readable-subtle tracking-widest">
            <span className="flex items-center gap-1.5 text-gold">
              <Move3D size={11} /> 鼠标拖拽
            </span>
            <span>·</span>
            <span>滚轮 / 双指 缩放</span>
            <span>·</span>
            <span>移动端单指旋转</span>
            <span className="ml-auto text-gold/60">360° 自由视角</span>
          </div>
        )}
      </div>
    </article>
  )
}

// ============== 分类筛选条 ==============
function FilterBar({
  filter,
  setFilter,
  total,
  glbCount,
}: {
  filter: string
  setFilter: (v: string) => void
  total: number
  glbCount: number
}) {
  return (
    <div className="mb-10 lg:mb-14">
      <div className="flex items-end justify-between flex-wrap gap-4 mb-6">
        <div>
          <div className="text-gold text-xs tracking-widest mb-2 flex items-center gap-2">
            <span className="w-8 h-px bg-gold" />
            CURATED · 典藏筛选
          </div>
          <h2 className="font-serif text-3xl lg:text-4xl xl:text-5xl text-paper font-black tracking-wider leading-tight">
            {total} 件珍品
            <span className="text-gold ml-3 text-2xl lg:text-3xl xl:text-4xl">·</span>
            <span className="text-gold ml-3 text-2xl lg:text-3xl xl:text-4xl">
              {glbCount} 件 3D
            </span>
          </h2>
          <p className="text-readable-muted text-sm tracking-wide mt-3 max-w-xl leading-relaxed">
            滚动浏览，3D 模型随视口进入自动加载并完整呈现，无需点击触发
          </p>
        </div>
      </div>
      <div className="flex flex-wrap gap-2">
        {GALLERY_CATEGORIES.map((c) => (
          <button
            key={c.value}
            onClick={() => setFilter(c.value)}
            className={`px-5 py-2.5 text-xs tracking-widest font-serif transition-all border ${
              filter === c.value
                ? 'bg-vermilion text-paper border-vermilion shadow-md shadow-vermilion/30'
                : 'bg-transparent text-readable-muted border-gold/30 hover:border-gold hover:text-gold'
            }`}
          >
            {c.label}
          </button>
        ))}
      </div>
    </div>
  )
}

// ============== 主页面 ==============
export default function Gallery() {
  const [filter, setFilter] = useState('all')
  const [device, setDevice] = useState<DeviceInfo | null>(null)
  /** 已挂载 Canvas 的卡片 ID 集合（视口触发后挂载，永久保留避免重新加载） */
  const [mountedIds, setMountedIds] = useState<Set<number>>(new Set())
  const sectionRef = useRef<HTMLElement>(null)

  // 设备检测
  useEffect(() => {
    setDevice(detectDevice())
  }, [])

  // 过滤展品
  const filteredItems = useMemo(() => {
    if (filter === 'all') return GALLERY_ITEMS
    return GALLERY_ITEMS.filter((i) => i.category === filter)
  }, [filter])

  const glbCount = useMemo(
    () => GALLERY_ITEMS.filter((i) => i.modelUrl).length,
    []
  )

  // IntersectionObserver - 视口进入即挂载 Canvas（一次性触发，不卸载）
  useEffect(() => {
    if (!sectionRef.current) return
    const obs = new IntersectionObserver(
      (entries) => {
        setMountedIds((prev) => {
          let changed = false
          const next = new Set(prev)
          for (const e of entries) {
            if (e.isIntersecting) {
              const id = Number((e.target as HTMLElement).dataset.itemId)
              if (!Number.isNaN(id) && !next.has(id)) {
                next.add(id)
                changed = true
              }
            }
          }
          return changed ? next : prev
        })
      },
      { threshold: [0, 0.05], rootMargin: '300px 0px 300px 0px' }
    )
    const cards = sectionRef.current.querySelectorAll('[data-item-id]')
    cards.forEach((c) => obs.observe(c))
    return () => obs.disconnect()
  }, [filteredItems])

  return (
    <div className="bg-ink-deep min-h-screen">
      <PageHeader
        cn="数字展厅"
        en="DIGITAL GALLERY"
        subtitle="穿越时空的对话，每一件非遗珍品都在这里向世界讲述中华文明的故事。"
        decoration="paper"
      />

      <section ref={sectionRef} className="relative py-12 lg:py-20">
        <div className="max-w-[1280px] mx-auto px-6 lg:px-10">
          <FilterBar
            filter={filter}
            setFilter={setFilter}
            total={GALLERY_ITEMS.length}
            glbCount={glbCount}
          />

          {/* 单列布局：一行一个，最大化视觉展示 */}
          <div className="flex flex-col gap-12 lg:gap-16">
            {filteredItems.map((item, idx) => (
              <GalleryCard
                key={item.id}
                item={item}
                device={device}
                shouldMount={mountedIds.has(item.id)}
                index={idx}
              />
            ))}
          </div>

          {/* 底部引导 */}
          <div className="mt-20 text-center">
            <div className="inline-flex flex-col items-center gap-3 text-readable-subtle">
              <ChevronDown className="text-gold animate-bounce" size={22} />
              <p className="text-xs tracking-widest leading-loose max-w-md">
                滚动浏览 · 3D 模型自动加载 · 鼠标拖拽或触屏滑动即可 360° 观赏
                <br />
                移动端单指旋转 · 双指缩放
              </p>
            </div>
          </div>
        </div>
      </section>
    </div>
  )
}
