<script lang="ts">
import { defineComponent, SetupContext, ref, onMounted, Ref, watch } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { useRouter } from "vue-router";

type driver = {
  name: string
  id: number
}

type Route = {
  name: string;
  id: number;
  time: Date;
  driver: { id: number, name: string }
};


export default defineComponent({
  emits: ['close', 'response'],
  name: 'settings',
  props: {
    routes: {
      type: Array as () => Route[],
      default: () => [], // Standard: leeres Array
      required: true,
    },
  },
  setup(props, { emit }: SetupContext) {


    const canvasRef: Ref<HTMLCanvasElement | null> = ref(null);
    const tooltipRef: Ref<HTMLDivElement | null> = ref(null);
    const tooltipText = ref("");
    const router = useRouter();
    const legend = ref<Record<string, string>>({});
    const driverColors = ref<Record<string, string>>({});
    var drivers: driver[] = [];
    const points: Route[] = props.routes;

    // values for UI Information distribution
    const messageType: Ref<string> = ref("None");
    const message: Ref<string> = ref("");

    console.log(props)
    if (!props.routes || props.routes.length <= 0) {

      alert("no points to show")
      messageType.value = "error";
      message.value = `no points to show`;
    }

    // handles sending webrequests to the backend
    const getDrivers = async () => {

      const headers: Headers = new Headers()

      headers.set('Content-Type', 'application/json')
      headers.set('Accept', 'application/json')

      const request: RequestInfo = new Request("http://localhost:5000/driver", {
        method: "GET",
        headers: headers
      })

      var response = await fetch(request)

      // make sure the request was successfull
      if (response.ok) {

        var jsonBody = await response.json()

        // convert vehicles from json response to processable data
        for (let i = 0; i < jsonBody.length; i++) {
          let driver = jsonBody[i]

          drivers.push({ id: driver["id"], name: driver["name"] })
          const color = new THREE.Color(`hsl(${(i * 360) / drivers.length}, 70%, 50%)`);
          driverColors.value[driver["id"]] = color.getStyle();
          console.log(color.getStyle())
          legend.value[driver["id"]] = driver["name"];
        }
      } else {
        
        alert(`failed to get drivers with error: ${await response.statusText}`)
        console.log(await response.text())
        messageType.value = "error";
        message.value = `failed to get drivers with error: ${await response.statusText}`;
      }
    }

    // assigns a driver to a point based on the points driver id
    const assignDriverToPoint = (points: Route[]) => {
      return points.reduce((acc, point) => {

        const driverId = point.driver.id;
        console.log(`driverid: ${driverId}`)
        acc[driverId] = acc[driverId] || [];
        acc[driverId].push(point);
        return acc;
      }, {} as Record<number, Route[]>);
    }


    // runs only when component has fully mounted
    onMounted(async () => {
      await getDrivers();

      if (!canvasRef.value) return;

      console.log(`routes: ${props.routes}`)

      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true, });

      renderer.setSize(window.innerWidth, window.innerHeight);
      const controls = new OrbitControls(camera, renderer.domElement);
      const raycaster = new THREE.Raycaster();
      const mouse = new THREE.Vector2();

      const groupedPoints = assignDriverToPoint(points)

      const geometry = new THREE.BufferGeometry();
      const vertices: number[] = [];
      const colors: number[] = [];

      const totalPoints = points.length;
      const offsetXStep = 2;

      let offsetX = 0;
      let sumX = 0, sumY = 0, sumZ = 0;

      const getColorForDriver = (driverId: number) => {
        const color = driverColors.value[driverId];
        if (!color) {
          console.error(`No color found for driver ID ${driverId}`);
          return "#FFFFFF";  // Rückfallfarbe, wenn keine Farbe gefunden wurde
        }
        return color;
      };

      /**
       * complicated math things (i am scared of this part)
       * 
       * calculates a points position and ensures that all points grouped by the same driver are placed together
       * a point is a uploaded track and during this step it gets applied its color material which was generated whiles getting drivers
       */
      Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => {
        const driverId = parseInt(driverIdStr, 10);
        console.log("Driver Colors:", driverColors.value[driverIdStr]);

        if (!driverColors.value[driverId]) {
          console.error(`Missing color for Driver ID: ${driverId}`);
        }

        const color = new THREE.Color(driverColors.value[driverId]);
        console.log(`Color for Driver ${driverId}:`, driverColors.value[driverId]);

        // calculate offset base on driverid
        offsetX += offsetXStep;

        driverPoints.forEach(() => {
          // calculate random position in the area of a driver
          const x = offsetX + (Math.random() - 0.5) * 2;
          const y = (Math.random() - 0.5) * 6;
          const z = (Math.random() - 0.5) * 6;

          // calculate center
          sumX += x;
          sumY += y;
          sumZ += z;

          vertices.push(x, y, z);
          colors.push(color.r, color.g, color.b);
        });
      });

      // calculate clouds centerpoint
      const centerX = sumX / totalPoints;
      const centerY = sumY / totalPoints;
      const centerZ = sumZ / totalPoints;

      // put cloud centered on the canvase
      const offset = new THREE.Vector3(-centerX, -centerY, -centerZ);
      for (let i = 0; i < vertices.length; i += 3) {
        vertices[i] += offset.x;
        vertices[i + 1] += offset.y;
        vertices[i + 2] += offset.z;
      }

      geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
      geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));

      const material = new THREE.PointsMaterial({
        vertexColors: true,
        size: 0.3,
      });

      const pointCloud = new THREE.Points(geometry, material);
      scene.add(pointCloud);

      // animate camera
      camera.position.set(10, 10, 25);
      controls.update();

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

      /**
       * handles mouse events and moves 3D space
       * @param event the event triggered when the mouse is moved inside the canvas
       */
      const onMouseMove = (event: MouseEvent) => {
        if (!canvasRef.value || !tooltipRef.value) return;

        const rect = canvasRef.value.getBoundingClientRect();
        mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
        mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObject(pointCloud);

        if (intersects.length > 0) {
          const intersectIndex = Math.floor(intersects[0].index || 0);
          const point = points[intersectIndex];

          tooltipRef.value.style.display = "block";


          /**
           * getting coordinates and calculating 2D coordinates from them
           * this is done for the tooltip to be positions at the cursor
           */
          const positionArray = pointCloud.geometry.attributes.position.array;
          const x = positionArray[intersectIndex * 3];
          const y = positionArray[intersectIndex * 3 + 1];
          const z = positionArray[intersectIndex * 3 + 2];

          const screenPosition = new THREE.Vector3(x, y, z);
          screenPosition.project(camera);

          const tooltipX = (screenPosition.x * 0.5 + 0.5) * window.innerWidth;
          const tooltipY = (screenPosition.y * -0.5 + 0.5) * window.innerHeight;

          tooltipRef.value.style.left = `${tooltipX + 10}px`;
          tooltipRef.value.style.top = `${tooltipY + 10}px`;

          tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`;
        } else {
          tooltipRef.value.style.display = "none";
        }
      }

      /**
       * Handles mouse clicks to navigate to the specific track page
       * @param event MouseEvent triggered on click
       */
      const onMouseClick = (event: MouseEvent) => {
        if (!canvasRef.value) return;

        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObject(pointCloud);

        if (intersects.length > 0) {
          const intersectIndex = Math.floor(intersects[0].index || 0);
          const point = points[intersectIndex];

          console.log(point.id)

          // Redirect to /track/:id using Vue Router
          router.push(`/route/${point.id}`);
        }
      };

      // Add event listeners for mouse actions
      canvasRef.value.addEventListener("mousemove", onMouseMove);
      canvasRef.value.addEventListener("click", onMouseClick);

      // Fenstergröße anpassen
      window.addEventListener("resize", () => {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
      });
    })

    return {
      canvasRef,
      tooltipRef,
      tooltipText,
      legend,
      driverColors,
      message,
      messageType
    };
  },
});
</script>


<template>
  <Message v-if="type != 'None'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"
    style="z-index: -1;"></Message>
  <div class="pointcloud-container relative">
    <canvas ref="canvasRef"></canvas>
    <div ref="tooltipRef" class="tooltip hidden fixed z-50 px-2 py-1 bg-base-200 text-base-content rounded">
      {{ tooltipText }}
    </div>
    <!-- Legende -->
    <div class="legend absolute top-10 left-10 bg-base-200 p-4 rounded shadow-lg z-50">
      <h3 class="font-bold mb-2">Drivers</h3>
      <ul>
        <li v-for="(driverName, driverId) in legend" :key="driverId" class="flex items-center mb-1">
          <div class="w-4 h-4" :style="{ backgroundColor: driverColors[driverId] }"></div>
          <span class="ml-2">{{ driverName }}</span>
        </li>
      </ul>
    </div>
  </div>
</template>

<style scoped>
.pointcloud-container {
  width: 100%;
  height: calc(90vh);
  overflow: hidden;
}

canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.tooltip {
  display: none;
  position: absolute;
  z-index: 50;
  padding: 2px 8px;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  border-radius: 4px;
  pointer-events: none;
}

.legend {
  width: auto;
  max-width: 250px;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 10px;
  border-radius: 8px;
  font-size: 14px;
}

.legend ul {
  padding: 0;
  list-style-type: none;
}

.legend li {
  display: flex;
  align-items: center;
}

.legend .color-box {
  width: 15px;
  height: 15px;
  margin-right: 10px;
}
</style>