<template>
  <div class="model-preview" :style="{ width: width, height: height }">
    <div v-if="loading" class="loading-mask">
      <n-spin size="large" />
      <p class="loading-text">模型加载中...</p>
    </div>
    <div   ref="canvasRef" class="canvas-container"></div>

    <div  class="cad-model-viewer__toolbar">
      <n-button size="tiny" quaternary @click="resetView">复位</n-button>
      <n-button size="tiny" quaternary v-if="glbDownloadUrl" @click="openSelectFolderDownload(glbDownloadUrl)">
        下载GLB文件
      </n-button>
      <n-button size="tiny" quaternary v-if="stpDownloadUrl" @click="openSelectFolderDownload(stpDownloadUrl)">
        下载源文件
      </n-button>
    
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as THREE from 'three'
import { STLLoader } from 'three/addons/loaders/STLLoader.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

const props = defineProps({
  modelUrl: {
    type: String,
  },
  glbDownloadUrl:{
    type: String
  },
  stpDownloadUrl:{
    type: String
  },
  width: {
    type: String,
    default: '100%'
  },
  height: {
    type: String,
    default: '600px'
  }
})

const emit = defineEmits(['load-success', 'load-error'])

const canvasRef = ref(null)
const loading = ref(true)
const error = ref('')


let scene, camera, renderer, controls
let animationId = null
let currentModel = null
let dracoLoader
let gltfLoader
let resetCameraPos = new THREE.Vector3()
let resetLookTarget = new THREE.Vector3()
let grid = new THREE.GridHelper(200,20,0x666666,0xdcdcdc)



function resolveUrl(modelUrl) {
  if (modelUrl.startsWith('http://') || url.startsWith('https://')) return modelUrl
  if (modelUrl.startsWith('/')) return `${window.location.origin}${modelUrl}`
  return modelUrl
}

const initScene = () => {
  const container = canvasRef.value
  if (!container) return

  const w = container.clientWidth
  const h = container.clientHeight

  scene = new THREE.Scene() //舞台
  scene.background = new THREE.Color('#FFFFFF') //背景色

  //相机
  //决定从哪个角度观察3D世界，常用两种:
  // 1.PerspectiveCamera 透视相机(人眼视觉 近大远小)
  // 2.OrthographicCamera 正交相机(人眼视觉 大大缩放)

  camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 10000)
  // camera = new THREE.OrthographicCamera(45, w / h, 0.1, 10000)
  camera.position.set(0, 0, 200)

  // 大模型性能降级：关闭抗锯齿、限制像素比、关闭阴影
  // Renderer 渲染器
  // 连接场景+相机 输出画面到Canvas画布 调用显卡渲染
  // WebGLRenderer 硬件加速
  // WebGPURenderer 新一代高性能渲染
  renderer = new THREE.WebGLRenderer({
    antialias: false,
    preserveDrawingBuffer: true,
    powerPreference:"low-power" //适应手机端 开启兼容低性能WebGL
  })
  renderer.setSize(w, h)
  renderer.setPixelRatio(1) //手机强制1倍像素，避免高DPI爆现存卡顿
  // renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1))
  renderer.shadowMap.enabled = false
  container.appendChild(renderer.domElement)

  // 光照
  const ambientLight = new THREE.AmbientLight(0xffffff, 2.50)//环境光 均匀漫射 无阴影
  scene.add(ambientLight)
  const mainLight = new THREE.DirectionalLight(0xffffff, 1.00)//平行光 太阳光，支持阴影
  mainLight.position.set(200, 300, 200)
  scene.add(mainLight)
  const fillLight = new THREE.DirectionalLight(0xffffff, 0.35)
  fillLight.position.set(-200, -100, -200)
  scene.add(fillLight)
  // scene.add(grid)

  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05
  controls.enablePan = true
  controls.minDistance = 1
  controls.maxDistance = 10000

  // 初始化Draco解码器绑定GLTFLoader
  dracoLoader = new DRACOLoader()
  dracoLoader.setDecoderPath('https://unpkg.com/three@0.160.0/examples/jsm/libs/draco/')
  dracoLoader.setDecoderConfig({ type: 'js' })
  gltfLoader = new GLTFLoader()
  gltfLoader.setDRACOLoader(dracoLoader)
  loadModel()
}

const clearOldModel = () => {
  if (!scene) return
  if (currentModel) {
    scene.remove(currentModel)
    currentModel.traverse((child) => {
      if (child.geometry) child.geometry.dispose()
      if (child.material) {
        if (Array.isArray(child.material)) child.material.forEach(m => m.dispose())
        else child.material.dispose()
      }
    })
    currentModel = null
  }
}

const fitModelToView = (model) => {
  const box = new THREE.Box3().setFromObject(model)
  const center = box.getCenter(new THREE.Vector3())
  const size = box.getSize(new THREE.Vector3())
  model.position.sub(center)
  const maxDim = Math.max(size.x, size.y, size.z)
  const fov = camera.fov * (Math.PI / 180)
  const cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2)) * 2.2
  camera.position.set(0, 0, cameraZ)
  camera.lookAt(0, 0, 0)
  camera.updateProjectionMatrix()
  controls.target.set(0, 0, 0)
  controls.update()

  // 网格尺寸取模型最大尺寸的 2.5 倍，保证四周留白
  const gridSize = Math.ceil(maxDim * 2.5)
  // 分段数按尺寸自适应，保证每格宽度合理
  const gridDivisions = Math.max(10, Math.floor(gridSize / 10))
  grid = new THREE.GridHelper(gridSize, gridDivisions, 0x666666, 0xdcdcdc)
  scene.add(grid)

  resetCameraPos.copy(camera.position)
  resetLookTarget.copy(controls.target)
}

const loadModel = () => {

  if (!props.modelUrl || !gltfLoader) return

  loading.value = true
  clearOldModel()
  const url = props.modelUrl
  const ext = url.split('.').pop().toLowerCase()

  if (ext === 'stl') {
    const loader = new STLLoader()

    loader.load(
        url,
        (geometry) => {
          loading.value = false
          geometry.computeVertexNormals()
          geometry.center()
          const material = new THREE.MeshStandardMaterial({
            color: 0xb8bcc2, metalness: 0.25, roughness: 0.75, side: THREE.DoubleSide
          })
          const mesh = new THREE.Mesh(geometry, material)
          currentModel = mesh
          scene.add(mesh)
          fitModelToView(mesh)
          emit('load-success', mesh)
        },
        (xhr) => console.log(`STL加载 ${(xhr.loaded/xhr.total*100).toFixed(1)}%`),
        (err) => { loading.value = false; console.error('STL错误', err); emit('load-error', err) }
    )
  } else if (ext === 'glb' || ext === 'gltf') {
    gltfLoader.load(
        url,
        (gltf) => {
          console.log('✅ 成功进入解析回调，开始处理模型')
          loading.value = false
          const model = gltf.scene
        //   model.traverse((obj) => {
        //   if (obj.isMesh) {
        //     // 给不同部件手动指定材质颜色
        //     if(obj.name.includes("platform")){
        //       obj.material = new THREE.MeshStandardMaterial({color:0xaaaaaa, metalness:0.2, roughness:0.7})
        //     }else if(obj.name.includes("scissor")){
        //       obj.material = new THREE.MeshStandardMaterial({color:0x666666, metalness:0.3, roughness:0.6})
        //     }else if(obj.name.includes("motor")){
        //       obj.material = new THREE.MeshStandardMaterial({color:0x333333, metalness:0.5, roughness:0.4})
        //     }else{
        //       obj.material = new THREE.MeshStandardMaterial({color:0x888888})
        //     }
        //     obj.material.needsUpdate = true
        //   }
        // })
          currentModel = model
          fitModelToView(model)
          scene.add(model)
          emit('load-success', model)
        },
        // 打印下载进度，判断是否下载卡住
        (xhr) => console.log(`GLB下载进度 ${(xhr.loaded/xhr.total*100).toFixed(1)}%`),
        (err) => {
          loading.value = false
          console.error('GLB加载失败完整报错：', err)
          emit('load-error', err)
        }
    )
  } else {
    loading.value = false
    emit('load-error', new Error('仅支持 stl / glb / gltf'))
  }
}

const animate = () => {
  animationId = requestAnimationFrame(animate)
  controls?.update()
  renderer?.render(scene, camera)
}

const onWindowResize = () => {
  if (!camera || !renderer || !canvasRef.value) return
  const w = canvasRef.value.clientWidth
  const h = canvasRef.value.clientHeight
  camera.aspect = w / h
  camera.updateProjectionMatrix()
  renderer.setSize(w, h)
}


function resetView() {
  if (!camera || !controls) return
  camera.position.copy(resetCameraPos)
  controls.target.copy(resetLookTarget)
  camera.updateProjectionMatrix()
  controls.update()
}


async function openSelectFolderDownload(url) {
  if(!url) return alert('暂无可下载的文件，请先完成模型转换')

  try{
    const dirHandle = await window.showDirectoryPicker()
    const res = await fetch(url)
    const blob = await res.blob()

    const urlParams = new URLSearchParams(url.split("?")[1])
    const materialCode = urlParams.get('materialCode')
    const suffix = urlParams.get("suffix")
    const fileName = `${materialCode}.${suffix}`

    const fileHandle = await dirHandle.getFileHandle(fileName,{create:true})
    const writable = await fileHandle.createWritable()
     
    await writable.write(blob)
    await writable.close()

    alert(`保存成功！文件已写入你选中的文件夹：${fileName}`)
  }catch(err) {
    console.log('文件夹选择取消/浏览器不支持，降级普通下载', err)
    window.open(url, '_blank')
  }
  
}

watch(() => props.modelUrl, (newVal) => newVal && loadModel(), { immediate: true })

onMounted(() => {
  initScene()
  animate()
  window.addEventListener('resize', onWindowResize)
})

onUnmounted(() => {
  if (dracoLoader) dracoLoader.dispose()
  cancelAnimationFrame(animationId)
  window.removeEventListener('resize', onWindowResize)
  clearOldModel()
  renderer?.dispose()
  scene = camera = renderer = controls = dracoLoader = gltfLoader = null
})
</script>

<style scoped>
.model-preview {
  position: relative;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  overflow: hidden;
  background: #f0f2f5;
}
.canvas-container {
  width: 100%;
  height: 100%;
}
.loading-mask {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255,255,255,0.85);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 10;
}
.loading-text {
  margin-top: 12px;
  color: #666;
  font-size: 14px;
}
.cad-model-viewer__toolbar {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
}
</style>