<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { useResizeObserver, useMouseInElement } from '@vueuse/core'
import * as THREE from 'three'
import { DomainInfo, GeoData } from '@/types/api/domain'
import { gcsToVec3, parseGCS, renderMarkers } from '@/assets/planet'

const props = defineProps<{
  data: GeoData[]
  selected: string
}>()

const wrapper = ref<HTMLElement | undefined>(undefined)
const canvas = ref<HTMLCanvasElement | undefined>(undefined)
let renderer: THREE.WebGLRenderer | undefined
let camera: THREE.PerspectiveCamera | undefined
let scene: THREE.Scene | undefined
let light: THREE.SpotLight | undefined
let planet: THREE.Mesh | undefined

// Resize handler
useResizeObserver(wrapper, (entries) => {
  const entry = entries[0]
  const { width, height } = entry.contentRect
  if (renderer && camera) {
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(width, height);
  }
})

// Moving light
// const mouseData = useMouseInElement(wrapper)
// watch([mouseData.elementX, mouseData.elementY], ([x, y]) => {
//   const lx = (x - mouseData.elementWidth.value / 2) / mouseData.elementWidth.value
//   const ly = (y - mouseData.elementHeight.value / 2) / mouseData.elementHeight.value
//   light?.position.set(camera!.position.x + lx, camera!.position.y - ly, camera!.position.z)
// })

// Reactive markers
watch(() => props.data, () => {
  if (planet) {
    renderMarkers(props.data, planet)
  }
})

// Reactive selection and camera
const selectedGCS = ref<THREE.Vector3>()
const selectedInfo = ref<GeoData>()
const viewingMarker = ref(false)

watch(() => props.selected, (ip) => {
  if (props.data) {
    const data = props.data
    const gd = data.find((x) => x.ip === ip)
    if (gd) {
      selectedInfo.value = gd
      const [lat, lon] = gd.loc ? parseGCS(gd.loc) : [0, 0]
      selectedGCS.value = gcsToVec3(lat, lon)
    }
  }
})

//

onMounted(() => {
  // Setup THREE things (ha ha)
  scene = new THREE.Scene()
  renderer = new THREE.WebGLRenderer({
    canvas: canvas.value,
    antialias: true,
    alpha: true
  })
  camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
  // Create a ball
  let targetScale = new THREE.Vector3(0, 0, 0)
  planet = new THREE.Mesh(
    new THREE.SphereGeometry(0.5, 50, 50),
    new THREE.MeshPhongMaterial({
      map: new THREE.TextureLoader().load('3D/earth_green_2k.jpg', () => { targetScale = new THREE.Vector3(1, 1, 1) }),
      specularMap: new THREE.TextureLoader().load('3D/earth_spec_2k.jpg')
    })
  )
  planet.scale.set(0, 0, 0)
  scene.add(planet)
  camera.position.z = 1.1
  // Create a light
  light = new THREE.SpotLight(0xffffff, 1, 0, 10, 2)
  light.position.set(1, 0, 10)
  scene.add(light)
  // Setup markers on load
  renderMarkers(props.data, planet)
  // Setup targeting check
  function getCameraDelta() {
    if (selectedGCS.value && camera) {
      const targetCameraPos = selectedGCS.value.normalize().multiplyScalar(1.1)
      const dist = camera.position.distanceTo(targetCameraPos)
      return dist
    } else return 0
  }
  // Setup render loop
  function render() {
    requestAnimationFrame(render)
    if (renderer && scene && camera) {
      renderer.render(scene, camera)
      planet?.scale.lerp(targetScale, 0.1)
      if (planet && light && selectedGCS.value) {
        camera.position.lerp(selectedGCS.value, 0.1).normalize().multiplyScalar(1.1)
        light.position.lerp(selectedGCS.value, 0.025).normalize()
        camera.lookAt(planet.position)
        // show info sheet
        const camDist = getCameraDelta()
        viewingMarker.value = camDist < 0.1
      }
    }
  }
  render()
})
</script>

<template>
  <div class="canvas-wrapper" :class="{'canvas-invisible': selected === '' || !selectedInfo?.loc}" ref="wrapper">
    <canvas ref="canvas"/>
    <transition name="info" mode="out-in">
      <div
        v-show="viewingMarker && selected !== '' && selectedInfo?.loc"
        class="info"
      >
        <div class="info-name">{{ selectedInfo?.ip }}</div>
        <div class="info-org">{{ selectedInfo?.org }}</div>
        <div class="info-location">
          {{ selectedInfo?.city }}, 
          {{ selectedInfo?.region }}, 
          {{ selectedInfo?.country }}
        </div>
        <div class="info-gcs">At {{ selectedInfo?.loc }}</div>
      </div>
    </transition>
  </div>
</template>

<style scoped>
.canvas-wrapper {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 100%;
  transition: opacity 0.5s;
}

.canvas-invisible {
  opacity: 0.1;
}

canvas {
  display: block;
  position: absolute;
}

.info {
  position: absolute;
  inset: 50% 50%;
  width: max-content;
  height: max-content;
  transform: translate(-50%, 25%);
  padding: 0.5em 1em;
  background-color: #fff;
  color: #222;
  z-index: 10;
  box-shadow: 0 0.75em 3em black;
}
.info::before {
  content: '';
  position: absolute;
  width: 0.75em;
  height: 0.75em;
  background-color: inherit;
  top: 0;
  inset-inline-start: 50%;
  transform: translateX(-100%) rotate(45deg) translateY(-75%);
  z-index: 0;
}

.info-name {
  font-weight: bold;
  font-size: 1.1em;
}
.info-gcs {
  font-size: 0.8em;
}
.info-org {
  font-style: oblique;
  margin-block-end: 0.5em;
}

.info-enter-active {
  transition: all 0.6s 0.1s cubic-bezier(0.16, 1, 0.3, 1);
}
.info-leave-active {
  transition: none;
}
.info-enter-from {
  opacity: 0;
  transform: translate(-50%, 70%);
}
</style>