import {
  useState,
  useEffect,
  useMemo,
  useRef,
  useCallback,
  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)

  // 历史文物板块：性能优先模式（降低 dpr/阴影/特效，确保 30fps+）
  const isHistorical = item.section === '历史文物'

  // FPS 监测（仅 desktop 档位开启；历史文物始终监测以确保流畅）
  const fpsMetrics = useFrameMonitor(
    canvasRef,
    loadState === 'loaded' && (isHistorical || 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">
        {/* 高清加载背景图（多尺寸 webp + 原图兜底） */}
        {item.loadingImage ? (
          <picture>
            <source
              type="image/webp"
              srcSet={item.loadingImage.srcset}
              sizes={item.loadingImage.sizes}
            />
            <img
              src={item.loadingImage.fallback || 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"
              {...(index < 2 ? { fetchpriority: 'high' } : {})}
            />
          </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 && !isHistorical}
                camera={{
                  position: canvasConfig.cameraPos,
                  fov: canvasConfig.cameraFov,
                }}
                dpr={isHistorical ? [1, 1] : canvasConfig.dpr}
                gl={{
                  antialias: isHistorical ? false : canvasConfig.antialias,
                  powerPreference: 'high-performance',
                  preserveDrawingBuffer: false,
                  stencil: false,
                  depth: true,
                  alpha: false,
                }}
                frameloop={
                  isHistorical
                    ? isVisible
                      ? 'always'
                      : 'demand'
                    : effectiveFrameloop
                }
                performance={{ min: isHistorical ? 0.7 : 0.5 }}
                onCreated={({ gl }) => {
                  // 捕获 canvas DOM 用于 FPS 监测
                  if (canvasRef.current !== gl.domElement) {
                    ;(canvasRef as any).current = gl.domElement
                  }
                }}
              >
                <ambientLight intensity={isHistorical ? 1.2 : 0.9} />
                <directionalLight
                  position={[5, 5, 5]}
                  intensity={isHistorical ? 1.4 : (device?.tier === 'desktop' ? 1.6 : 1.2)}
                  castShadow={canvasConfig.shadows && !isHistorical}
                  shadow-mapSize-width={isHistorical ? 512 : 1024}
                  shadow-mapSize-height={isHistorical ? 512 : 1024}
                />
                {!isHistorical && 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" />
                  </>
                )}
                {isHistorical && (
                  // 历史文物仅加一束暖色补光（避免玉器/牙雕暗部死黑）
                  <directionalLight position={[-3, 2, 4]} intensity={0.5} color="#FFE4B5" />
                )}
                <Suspense fallback={null}>
                  {loadState === 'loaded' && (
                    <GLBModel
                      url={modelUrl}
                      autoRotate={autoRotate}
                      resetSignal={resetSignal}
                      isVisible={isVisible}
                    />
                  )}
                </Suspense>
                {!isHistorical && 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={isHistorical ? 0.12 : 0.08}
                  rotateSpeed={isHistorical ? 0.6 : (device?.tier === 'low' ? 0.6 : 0.8)}
                  zoomSpeed={isHistorical ? 0.4 : 0.6}
                  minDistance={2.8}
                  maxDistance={isHistorical ? 7 : 9}
                  minPolarAngle={Math.PI / 6}
                  maxPolarAngle={Math.PI - Math.PI / 6}
                  autoRotate={autoRotate && loadState === 'loaded'}
                  autoRotateSpeed={isHistorical ? 0.8 : (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,
  title,
  subtitle,
}: {
  filter: string
  setFilter: (v: string) => void
  total: number
  glbCount: number
  title: string
  subtitle: string
}) {
  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" />
            {title}
          </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">
            {subtitle}
          </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>
  )
}

// ============== 板块大标题 ==============
function SectionHeader({
  cn,
  en,
  desc,
  accentColor = '#C9A66B',
}: {
  cn: string
  en: string
  desc: string
  accentColor?: string
}) {
  return (
    <div className="relative mb-10 lg:mb-14 reveal-on-scroll">
      <div className="flex items-center gap-4 mb-4">
        <div
          className="w-12 h-12 sm:w-16 sm:h-16 shrink-0 flex items-center justify-center font-serif font-black text-paper text-2xl sm:text-3xl border-2"
          style={{
            background: `linear-gradient(135deg, ${accentColor}, ${accentColor}AA)`,
            borderColor: accentColor,
          }}
        >
          {cn.charAt(0)}
        </div>
        <div>
          <div className="font-display text-xs tracking-[0.3em] text-gold mb-1">{en}</div>
          <h2 className="font-serif font-black text-paper text-3xl sm:text-4xl lg:text-5xl tracking-wider">
            {cn}
          </h2>
        </div>
      </div>
      <div className="flex items-center gap-3 mb-4">
        <span className="h-px flex-1 max-w-[120px]" style={{ background: accentColor }} />
        <span
          className="font-display text-[10px] tracking-[0.4em] font-semibold"
          style={{ color: accentColor }}
        >
          {en.split(' · ')[1] || en}
        </span>
        <span className="h-px flex-1 max-w-[120px]" style={{ background: accentColor }} />
      </div>
      <p className="text-readable-muted text-sm lg:text-base leading-relaxed max-w-3xl">{desc}</p>
    </div>
  )
}

// ============== 板块导航 Tabs ==============
const SECTION_KEYS = ['非遗珍品', '历史文物'] as const
type SectionKey = (typeof SECTION_KEYS)[number]
const SECTION_META: Record<SectionKey, { en: string; color: string; count: number }> = {
  非遗珍品: { en: 'INTANGIBLE HERITAGE', color: '#C9A66B', count: 0 },
  历史文物: { en: 'HISTORICAL ARTIFACTS', color: '#C03B2B', count: 0 },
}

function SectionNav({
  active,
  onJump,
  counts,
  scrollOffset = 88,
}: {
  active: SectionKey
  onJump: (key: SectionKey) => void
  counts: Record<SectionKey, number>
  scrollOffset?: number
}) {
  return (
    <div
      className="sticky top-[68px] lg:top-[80px] z-30 -mx-6 lg:-mx-10 mb-10 lg:mb-14 backdrop-blur-md bg-ink-deep/85 border-y border-gold/20"
      style={{ paddingTop: 'env(safe-area-inset-top, 0px)' }}
    >
      <div className="px-6 lg:px-10 py-3 lg:py-4 max-w-[1280px] mx-auto">
        <div className="flex items-center gap-2 sm:gap-3 overflow-x-auto scrollbar-hide">
          <span className="hidden sm:inline-flex items-center gap-2 text-gold text-[10px] tracking-[0.3em] font-display shrink-0 pr-2 border-r border-gold/20">
            <span className="w-1.5 h-1.5 bg-gold rounded-full animate-pulse" />
            NAVIGATE
          </span>
          {SECTION_KEYS.map((key) => {
            const isActive = active === key
            const color = SECTION_META[key].color
            return (
              <button
                key={key}
                onClick={() => onJump(key)}
                aria-pressed={isActive}
                className={[
                  'group relative shrink-0 px-4 sm:px-6 py-2.5 sm:py-3 transition-all duration-300',
                  'border font-serif font-semibold tracking-widest text-sm sm:text-base',
                  isActive
                    ? 'text-paper shadow-lg'
                    : 'text-readable-muted hover:text-paper border-gold/25 hover:border-gold/60',
                ].join(' ')}
                style={
                  isActive
                    ? {
                        background: `linear-gradient(135deg, ${color}, ${color}CC)`,
                        borderColor: color,
                        boxShadow: `0 8px 20px -8px ${color}99`,
                      }
                    : undefined
                }
              >
                <span className="flex items-center gap-2 sm:gap-3">
                  <span
                    className="w-1.5 h-1.5 sm:w-2 sm:h-2 rounded-full transition-all"
                    style={{
                      background: isActive ? '#F8F4ED' : color,
                      boxShadow: isActive ? `0 0 12px ${color}` : 'none',
                    }}
                  />
                  <span>{key}</span>
                  <span
                    className={`text-[10px] sm:text-xs font-display tracking-wider px-1.5 sm:px-2 py-0.5 border ${
                      isActive ? 'border-paper/30 text-paper' : 'border-gold/30 text-readable-subtle'
                    }`}
                  >
                    {counts[key]}
                  </span>
                </span>
                {isActive && (
                  <span
                    className="absolute -bottom-[1px] left-0 right-0 h-[2px]"
                    style={{ background: color }}
                  />
                )}
              </button>
            )
          })}
        </div>
      </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())
  /** 当前活跃板块（用于顶部导航 tab 高亮 + 单板块视图切换） */
  const [activeSection, setActiveSection] = useState<SectionKey>('非遗珍品')
  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,
    []
  )

  // 按 section 分组
  const itemsBySection = useMemo(() => {
    const groups: Record<SectionKey, typeof filteredItems> = {
      '非遗珍品': [],
      '历史文物': [],
    }
    for (const item of filteredItems) {
      groups[item.section].push(item)
    }
    return groups
  }, [filteredItems])

  // 当前板块下的展品（单板块视图：只显示一个板块的内容）
  const currentSectionItems = itemsBySection[activeSection]

  // 板块元信息
  const SECTION_CONTENT: Record<SectionKey, {
    title: string
    en: string
    desc: string
    accent: string
    pageSubtitle: string
  }> = {
    '非遗珍品': {
      title: '非遗珍品',
      en: 'INTANGIBLE HERITAGE · 活态传承',
      desc: '从缂丝、刺绣、雕版到青瓷、木雕、漆器⋯⋯这些凝结着世代匠人心血的非遗代表作，每一项都承载着"见人见物见生活"的活态传承理念。3D 模型支持 360° 自由观赏。',
      accent: '#C9A66B',
      pageSubtitle: '穿越时空的对话，每一件珍品都在这里向世界讲述中华文明的故事。',
    },
    '历史文物': {
      title: '历史文物',
      en: 'HISTORICAL ARTIFACTS · 宫廷珍藏',
      desc: '宫廷造办处御用之物，玉器、牙雕等珍贵历史文物，承载着特定历史时期的工艺巅峰与文化记忆。3D 高精度模型让这些难得一见的瑰宝以数字形态永续保存。',
      accent: '#C03B2B',
      pageSubtitle: '宫廷造办处御用之物，玉器、牙雕等历史珍品的数字典藏，承载特定历史时期的工艺巅峰。',
    },
  }

  const currentMeta = SECTION_CONTENT[activeSection]

  // 切换板块时：清空已挂载集合（让新板块的 3D 模型按视口重新触发）+ 滚动到顶部 + 重置筛选
  useEffect(() => {
    setMountedIds(new Set())
    setFilter('all')
    // 平滑滚动到页面顶部（板块标题位置）
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }, [activeSection])

  // 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()
  }, [currentSectionItems])

  // 切换板块 tab
  const handleSectionJump = useCallback((key: SectionKey) => {
    setActiveSection(key)
  }, [])

  return (
    <div className="bg-ink-deep min-h-screen">
      <PageHeader
        cn="数字展厅"
        en="DIGITAL GALLERY"
        subtitle={currentMeta.pageSubtitle}
        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={currentSectionItems.length}
            glbCount={currentSectionItems.filter((i) => i.modelUrl).length}
            title={`${activeSection} · CURATED`}
            subtitle={currentMeta.desc}
          />

          {/* 板块导航 Tabs（sticky 跟随滚动） */}
          <SectionNav
            active={activeSection}
            onJump={handleSectionJump}
            counts={{
              '非遗珍品': GALLERY_ITEMS.filter((i) => i.section === '非遗珍品').length,
              '历史文物': GALLERY_ITEMS.filter((i) => i.section === '历史文物').length,
            }}
          />

          {/* 单板块视图：仅显示当前活跃板块的内容 */}
          <div key={activeSection} className="animate-section-fade">
            <SectionHeader
              cn={currentMeta.title}
              en={currentMeta.en}
              desc={currentMeta.desc}
              accentColor={currentMeta.accent}
            />
            {currentSectionItems.length > 0 ? (
              <div className="flex flex-col gap-12 lg:gap-16">
                {currentSectionItems.map((item, idx) => (
                  <GalleryCard
                    key={item.id}
                    item={item}
                    device={device}
                    shouldMount={mountedIds.has(item.id)}
                    index={idx}
                  />
                ))}
              </div>
            ) : (
              <div className="text-center py-20 text-readable-subtle border border-dashed border-paper/15">
                {filter !== 'all'
                  ? `该分类下暂无${activeSection}`
                  : `暂无${activeSection}`}
              </div>
            )}
          </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>
  )
}
