<script setup lang="ts">
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { useLoop, useTresContext } from "@tresjs/core";
import * as THREE from "three";

const props = defineProps<{
  topScreenCanvas: HTMLCanvasElement | null;
  bottomScreenCanvas: HTMLCanvasElement | null;
}>();

const emit = defineEmits<{
  topScreenClick: [x: number, y: number];
  bottomScreenClick: [x: number, y: number];
}>();

const { state: model } = useLoader(
  GLTFLoader,
  "/models/nintendo-ds/scene.gltf",
);

const scene = computed(() => model.value?.scene);

let topScreenTexture: THREE.CanvasTexture | null = null;
let bottomScreenTexture: THREE.CanvasTexture | null = null;
let topScreenMesh: THREE.Mesh | null = null;
let bottomScreenMesh: THREE.Mesh | null = null;

const { camera, renderer } = useTresContext();

watch(
  () => [props.topScreenCanvas, props.bottomScreenCanvas],
  () => {
    if (!props.topScreenCanvas || !props.bottomScreenCanvas) return;

    topScreenTexture = new THREE.CanvasTexture(props.topScreenCanvas);
    topScreenTexture.minFilter = THREE.NearestFilter;
    topScreenTexture.magFilter = THREE.NearestFilter;
    topScreenTexture.flipY = false;

    bottomScreenTexture = new THREE.CanvasTexture(props.bottomScreenCanvas);
    bottomScreenTexture.minFilter = THREE.NearestFilter;
    bottomScreenTexture.magFilter = THREE.NearestFilter;
    bottomScreenTexture.flipY = false;
  },
  { immediate: true },
);

watch(scene, () => {
  if (!scene.value) return;

  scene.value.traverse((child) => {
    if (child instanceof THREE.Mesh) {
      const material = child.material as THREE.Material;

      if (material.name?.includes("screen_up") && topScreenTexture) {
        topScreenTexture.repeat.set(1, 1024 / 404);
        topScreenTexture.offset.set(0, -4 / 1024);
        child.material = new THREE.MeshStandardMaterial({
          map: topScreenTexture,
          emissive: new THREE.Color(0x222222),
          emissiveIntensity: 0.5,
        });
        topScreenMesh = child;
      } else if (
        material.name?.includes("screen_down") &&
        bottomScreenTexture
      ) {
        bottomScreenTexture.repeat.set(1, 1024 / 532);
        bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
        child.material = new THREE.MeshStandardMaterial({
          map: bottomScreenTexture,
          emissive: new THREE.Color(0x222222),
          emissiveIntensity: 0.5,
        });
        bottomScreenMesh = child;
      }
    }
  });
});

const { onRender } = useLoop();

onRender(() => {
  if (topScreenTexture) topScreenTexture.needsUpdate = true;
  if (bottomScreenTexture) bottomScreenTexture.needsUpdate = true;
});

const handleClick = (event: MouseEvent) => {
  const domElement = renderer.instance.domElement;
  const rect = domElement.getBoundingClientRect();

  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(
    new THREE.Vector2(
      ((event.clientX - rect.left) / rect.width) * 2 - 1,
      -((event.clientY - rect.top) / rect.height) * 2 + 1,
    ),
    camera.activeCamera.value,
  );

  if (topScreenMesh) {
    const intersects = raycaster.intersectObject(topScreenMesh);
    if (intersects[0]) {
      const uv = intersects[0].uv;
      if (uv) {
        const x = Math.floor(uv.x * 256);
        const y = Math.floor(uv.y * (1024 / 404) * 192);
        emit("topScreenClick", x, y);
        return;
      }
    }
  }

  if (bottomScreenMesh) {
    const intersects = raycaster.intersectObject(bottomScreenMesh);
    if (intersects[0]) {
      const uv = intersects[0].uv;
      if (uv) {
        const x = Math.floor(uv.x * 256);
        const y = Math.floor(192 - (1 - uv.y) * (1024 / 532) * 192);
        emit("bottomScreenClick", x, y);
      }
    }
  }
};

onMounted(() => {
  if (renderer) {
    renderer.instance.domElement.addEventListener("click", handleClick);
  }
});

onUnmounted(() => {
  if (renderer) {
    renderer.instance.domElement.removeEventListener("click", handleClick);
  }
  topScreenTexture?.dispose();
  bottomScreenTexture?.dispose();
});
</script>

<template>
  <primitive v-if="scene" :object="scene" />
</template>
