<!-- 数字人交互页面：包含3D模型展示和智能问答功能 -->
<template>
  <!-- 外层容器，控制整体内边距 -->
  <div class="ai-chat-container">
    <!-- 主卡片容器，使用Element Plus的el-card组件 -->
    <el-card shadow="always">
      <!-- 3D模型渲染容器，通过ref获取DOM元素用于Three.js渲染 -->
      <div class="model-viewer" ref="modelContainer"></div>

      <!-- 聊天输入区域，包含输入框和发送按钮 -->
      <div class="chat-section">
        <!-- 输入框：双向绑定问题内容，支持回车发送 -->
        <el-input
          v-model="question" 
          placeholder="请输入问题"
          @keyup.enter="handleAsk"
        />
        <!-- 发送按钮：触发提问逻辑 -->
        <el-button type="primary" @click="handleAsk">发送</el-button>
      </div>

      <!-- 回答显示区域：当有回答时展示 -->
      <div class="answer-box" v-if="answer">
        <!-- 使用Element Plus的el-tag标记回答来源 -->
        <el-tag type="success" effect="dark">数字人回答：</el-tag>
        <!-- 显示具体回答内容 -->
        <p>{{ answer }}</p>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
// 导入Vue组合式API和Three.js相关模块
import { ref, onMounted, onUnmounted } from 'vue'
import * as THREE from 'three'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'

// -------------- 状态变量定义 --------------
// 3D模型渲染容器的DOM引用
const modelContainer = ref<HTMLDivElement>()
// 用户输入的问题内容
const question = ref('')
// 数字人返回的回答内容
const answer = ref('')
// Three.js核心对象声明
let renderer: THREE.WebGLRenderer       // 渲染器
let scene: THREE.Scene                  // 场景对象
let camera: THREE.PerspectiveCamera     // 透视相机
let model: THREE.Object3D               // 加载的3D模型对象
let mixer: THREE.AnimationMixer         // 动画混合器（控制模型动画）

// -------------- Three.js初始化 --------------
// 初始化3D场景的核心函数
const initThree = () => {
  // 1. 创建场景对象
  scene = new THREE.Scene()

  // 2. 设置场景背景（全景图）
  const textureLoader = new THREE.TextureLoader()
  // 加载背景纹理（假设图片已放置在public目录）
  const backgroundTexture = textureLoader.load('/niudunBackground2.png')
  // 设置全景图映射方式（等距矩形反射映射）
  backgroundTexture.mapping = THREE.EquirectangularReflectionMapping
  // 启用纹理平铺并设置重复次数（适配背景图尺寸）
  backgroundTexture.wrapS = THREE.RepeatWrapping
  backgroundTexture.wrapT = THREE.RepeatWrapping
  backgroundTexture.repeat.set(2, 2)
  scene.background = backgroundTexture

  // 3. 创建透视相机（视角75度，近截面0.1，远截面1000）
  camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,  // 宽高比
    0.1,
    1000
  )
  camera.position.z  = 5  // 相机位置：z轴5单位

  // 4. 初始化WebGL渲染器（启用抗锯齿）
  renderer = new THREE.WebGLRenderer({ antialias: true })
  // 设置渲染器尺寸（宽度与容器一致，高度固定600px）
  renderer.setSize(modelContainer.value!.offsetWidth, 600)
  // 将渲染器的canvas添加到模型容器DOM中
  modelContainer.value!.appendChild(renderer.domElement)

  // 5. 添加光照（环境光+方向光）
  // 环境光：全局照亮场景（强度1.2增强亮度）
  const ambientLight = new THREE.AmbientLight(0xffffff, 1.2)
  scene.add(ambientLight)
  // 方向光：模拟自然光（位置[10,10,5]，增强纹理细节）
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0)
  directionalLight.position.set(10, 10, 5)
  scene.add(directionalLight)

  // 6. 加载GLB格式3D模型（示例路径，实际应动态配置）
  const loader = new GLTFLoader()
  loader.load(
    '/niudun.glb',  // 模型路径
    (gltf) => {  // 加载成功回调
      console.log('模型加载完成:', gltf)
      model = gltf.scene  // 获取模型根对象
      model.scale.set(3.0, 3.0, 3.0)  // 模型缩放（3倍）

      // 遍历模型所有子对象，检查材质配置（调试用）
      model.traverse((child) => {
        if (child.isMesh && child.material) {
          // 仅处理标准材质（支持PBR光照）
          if (child.material instanceof THREE.MeshStandardMaterial) {
            // 检查基础颜色纹理（决定模型基本颜色）
            if (child.material.map) {
              console.log(`检测到基础颜色纹理，尺寸：${child.material.map.image?.width}x${child.material.map.image?.height}`)
            } else {
              console.warn('警告：当前网格材质未绑定基础颜色纹理（map属性），可能导致模型颜色单一')
            }
            // 检查法线纹理（增强表面细节）
            if (child.material.normalMap) {
              console.log(`检测到法线纹理，尺寸：${child.material.normalMap.image?.width}x${child.material.normalMap.image?.height}`)
            } else {
              console.warn('提示：建议为材质添加法线纹理（normalMap）以增强表面细节')
            }
          } else {
            console.warn(`警告：检测到非标准材质类型${child.material.type}，可能无法正确显示纹理`)
          }
        }
      })

      scene.add(model)  // 将模型添加到场景

      // 7. 处理模型动画（如果有动画数据）
      if (gltf.animations && gltf.animations.length > 0) {
        console.log(`模型包含 ${gltf.animations.length} 个动画轨道`)
        mixer = new THREE.AnimationMixer(model)  // 初始化动画混合器
        const action = mixer.clipAction(gltf.animations[0])  // 获取第一个动画轨道
        action.play()  // 播放动画
        animate()  // 启动动画循环
      } else {
        console.error('模型未包含有效动画数据，当前模型动画数组为空')
      }
    },
    (error) => {  // 加载失败回调
      console.error('模型加载失败:', error)
    }
  )
}

// -------------- 动画循环 --------------
// 动画更新函数（通过requestAnimationFrame持续调用）
const animate = () => {
  requestAnimationFrame(animate)  // 递归调用实现动画循环
  mixer && mixer.update(0.016)    // 更新动画混合器（每帧间隔约16ms）
  renderer.render(scene, camera)  // 渲染场景
}

// -------------- 用户交互处理 --------------
// 处理用户提问逻辑
const handleAsk = async () => {
  if (!question.value.trim()) return  // 空问题不处理

  // 模拟API调用获取回答（实际应替换为真实接口）
  answer.value = '这是李白的回答：' + question.value

  // 触发回答相关动画（示例：播放第二个动画轨道）
  if (mixer && model.animations[1]) {
    mixer.clipAction(model.animations[1]).play()
  }

  question.value = ''  // 清空输入框
}

// -------------- 生命周期钩子 --------------
// 组件挂载时初始化Three.js场景并添加窗口监听
onMounted(() => {

/* 回答显示区样式：设置背景色和内边距 */

/* 聊天输入区样式：flex布局排列输入框和按钮 */

/* 模型查看器样式：固定高度，设置边框和圆角 */
/* 外层容器样式：控制整体内边距 */
  camera.aspect = modelContainer.value!.offsetWidth / 400  // 更新相机宽高比
  camera.updateProjectionMatrix()                         // 更新相机投影矩阵
  renderer.setSize(modelContainer.value!.offsetWidth, 600) // 调整渲染器尺寸
// -------------- 窗口尺寸适配 --------------
// 窗口大小变化时调整相机和渲染器参数
  modelContainer.value?.removeChild(renderer.domElement)  // 移除渲染器canvas
  window.removeEventListener('resize', onWindowResize)   // 移除窗口监听
// 组件卸载时清理资源（防止内存泄漏）
  initThree()
  window.addEventListener('resize', onWindowResize)
})

onUnmounted(() => {
  modelContainer.value?.removeChild(renderer.domElement)
  window.removeEventListener('resize', onWindowResize)
})

// 窗口尺寸调整
const onWindowResize = () => {
  camera.aspect = modelContainer.value!.offsetWidth / 400
  camera.updateProjectionMatrix()
  renderer.setSize(modelContainer.value!.offsetWidth, 600)
}
</script>

<style scoped>
.ai-chat-container {
  padding: 20px;
}
.model-viewer {
  width: 100%;
  height: 600px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  margin-bottom: 20px;
}
.chat-section {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}
.answer-box {
  padding: 15px;
  background: #f8f9fa;
  border-radius: 4px;
}
</style>
