<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

const props = withDefaults(
  defineProps<{
    showGrid?: boolean
    showAxis?: boolean
  }>(),
  {
    showGrid: true,
    showAxis: true,
  }
)

const containerRef = ref<HTMLDivElement | null>(null)

let scene: THREE.Scene
let camera: THREE.PerspectiveCamera
let renderer: THREE.WebGLRenderer
let animationFrameId: number
let resizeObserver: ResizeObserver
let gridHelper: THREE.GridHelper
let axisHelper: THREE.AxesHelper
let controls: OrbitControls

function initScene() {
  if (!containerRef.value) return

  // Create scene
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0x1a1a2e)

  // Create camera
  const { clientWidth: width, clientHeight: height } = containerRef.value
  camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
  camera.position.set(5, 5, 5)
  camera.lookAt(0, 0, 0)

  // Create renderer
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(width, height)
  renderer.setPixelRatio(window.devicePixelRatio)
  containerRef.value.appendChild(renderer.domElement)

  // Setup orbit controls
  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05

  // Add lighting
  addLighting()

  // Add grid helper
  addGridHelper()

  // Add axis helper
  addAxisHelper()

  // Add a test cube to verify rendering
  addTestCube()

  // Setup resize observer
  setupResizeObserver()

  // Start render loop
  animate()
}

function addGridHelper() {
  gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x333333)
  gridHelper.visible = props.showGrid
  scene.add(gridHelper)
}

watch(
  () => props.showGrid,
  (visible) => {
    if (gridHelper) gridHelper.visible = visible
  }
)

function addAxisHelper() {
  axisHelper = new THREE.AxesHelper(5)
  axisHelper.visible = props.showAxis
  scene.add(axisHelper)
}

watch(
  () => props.showAxis,
  (visible) => {
    if (axisHelper) axisHelper.visible = visible
  }
)

function addLighting() {
  // Ambient light for base illumination
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.4)
  scene.add(ambientLight)

  // Primary directional light (top-front-right)
  const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8)
  directionalLight1.position.set(5, 10, 7)
  scene.add(directionalLight1)

  // Secondary directional light (back-left, softer)
  const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.3)
  directionalLight2.position.set(-5, 5, -5)
  scene.add(directionalLight2)
}

function addTestCube() {
  const geometry = new THREE.BoxGeometry(1, 1, 1)
  const material = new THREE.MeshStandardMaterial({ color: 0x4a90d9 })
  const cube = new THREE.Mesh(geometry, material)
  scene.add(cube)
}

function setupResizeObserver() {
  if (!containerRef.value) return

  resizeObserver = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const { width, height } = entry.contentRect
      if (width > 0 && height > 0) {
        camera.aspect = width / height
        camera.updateProjectionMatrix()
        renderer.setSize(width, height)
      }
    }
  })

  resizeObserver.observe(containerRef.value)
}

function animate() {
  animationFrameId = requestAnimationFrame(animate)
  controls.update()
  renderer.render(scene, camera)
}

function fitToSelection(objects: THREE.Object3D[]) {
  if (objects.length === 0) return

  const box = new THREE.Box3()
  for (const obj of objects) {
    box.expandByObject(obj)
  }

  const center = box.getCenter(new THREE.Vector3())
  const size = box.getSize(new THREE.Vector3())
  const maxDim = Math.max(size.x, size.y, size.z)
  const fov = camera.fov * (Math.PI / 180)
  const distance = maxDim / (2 * Math.tan(fov / 2)) * 1.5

  const direction = camera.position.clone().sub(controls.target).normalize()
  camera.position.copy(center).add(direction.multiplyScalar(distance))
  controls.target.copy(center)
  controls.update()
}

function cleanup() {
  if (animationFrameId) {
    cancelAnimationFrame(animationFrameId)
  }

  if (resizeObserver) {
    resizeObserver.disconnect()
  }

  if (controls) {
    controls.dispose()
  }

  if (renderer) {
    renderer.dispose()
    renderer.domElement.remove()
  }
}

onMounted(() => {
  initScene()
})

onBeforeUnmount(() => {
  cleanup()
})

defineExpose({
  fitToSelection,
})
</script>

<template>
  <div ref="containerRef" class="viewport"></div>
</template>

<style scoped>
.viewport {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>
