import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { data } from "./data.js";
import { createCelestialBody, createStarField } from "./constructors.js";

import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";

const main = function () {
  // Scene Setup
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000);
  camera.position.y = 120;
  camera.position.z = 150;

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.shadowMap.enabled = true;
  document.body.appendChild(renderer.domElement);

  const composer = new EffectComposer(renderer);

  const renderPass = new RenderPass(scene, camera);
  composer.addPass(renderPass);

  const outputPass = new OutputPass();
  composer.addPass(outputPass);

  // Basic Controls
  const cameraDamping = 0.05;
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = cameraDamping;
  controls.minDistance = 5;
  controls.maxDistance = 6000;

  // Responsive Design
  const onWindowResize = function () {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
  };

  window.addEventListener("resize", onWindowResize, false);

  // Objects
  const ambientLight = new THREE.AmbientLight(0x404040);
  scene.add(ambientLight);

  const sun = createCelestialBody(data, undefined, 0);
  let selectedPlanet = sun;
  let hasFocused = false;
  scene.add(sun);

  const starField = createStarField();
  scene.add(starField);

  // Movement
  const movement = {
    forward: false,
    backward: false,
    left: false,
    right: false,
  };
  const velocity = 0.5;

  const onKeyDown = function (event) {
    switch (event.code) {
      case "KeyW":
        movement.forward = true;
        break;
      case "KeyS":
        movement.backward = true;
        break;
      case "KeyA":
        movement.left = true;
        break;
      case "KeyD":
        movement.right = true;
        break;
    }
  };

  const onKeyUp = function (event) {
    switch (event.code) {
      case "KeyW":
        movement.forward = false;
        break;
      case "KeyS":
        movement.backward = false;
        break;
      case "KeyA":
        movement.left = false;
        break;
      case "KeyD":
        movement.right = false;
        break;
    }
  };

  document.addEventListener("keydown", onKeyDown);
  document.addEventListener("keyup", onKeyUp);

  // Selection
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();

  const updateInfoBox = function () {
    const infoWindow = document.getElementById("infoWindow");

    if (!selectedPlanet || !selectedPlanet.content) {
      infoWindow.style.display = "none";
      return;
    }

    const infoTitle = document.getElementById("infoTitle");
    const infoContent = document.getElementById("infoContent");

    infoTitle.innerText = selectedPlanet.name;
    infoContent.innerHTML = selectedPlanet.content;
    infoWindow.style.display = "block";
  };

  const updateDropdown = function () {
    const dropdown = document.getElementById("dropdown");
    selectedPlanet ? (dropdown.value = selectedPlanet.uuid) : (dropdown.value = "");
  };

  const onRightClick = function (event) {
    event.preventDefault();
    selectedPlanet = undefined;
    hasFocused = false;
    updateDropdown();
    updateInfoBox();
  };

  document.addEventListener("contextmenu", onRightClick);

  const onDoubleLeftClick = function (event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.isCelestialBody);

    if (intersects.length === 0) return;
    selectedPlanet = intersects[0].object;
    hasFocused = false;
    updateDropdown();
    updateInfoBox();
  };

  document.addEventListener("dblclick", onDoubleLeftClick);

  const onDropdownChange = function (event) {
    selectedPlanet = event.target.value ? scene.getObjectByProperty("uuid", event.target.value) : undefined;
    hasFocused = false;
    updateInfoBox();
  };

  document.getElementById("dropdown").addEventListener("change", onDropdownChange);

  // `Music
  const music = [
    "assets/music/track1.mp3",
    "assets/music/track2.mp3",
    "assets/music/track3.mp3",
    "assets/music/track4.mp3",
    "assets/music/track5.mp3",
    "assets/music/track6.mp3",
    "assets/music/track7.mp3",
  ];

  let currentMusicIndex = 0;
  const player = document.getElementById("backgroundMusic");

  const playNextTrack = function () {
    if (currentMusicIndex === music.length - 1) currentMusicIndex = 0;
    player.src = music[currentMusicIndex];
    player.play();
    currentMusicIndex++;
  };

  player.addEventListener("ended", playNextTrack);
  playNextTrack();

  // Animation
  let simulationSpeed = 1;

  const onSimulationSpeedChange = function (event) {
    simulationSpeed = parseFloat(event.target.value);
    document.getElementById("speedValue").textContent = simulationSpeed;
  };

  document.getElementById("speedSlider").addEventListener("input", onSimulationSpeedChange);

  const updateCelestial = function (celestial) {
    const time = Date.now() * 0.0001 * simulationSpeed;
    const angle = time * celestial.orbitSpeed;

    // Orbit
    if (celestial.orbitRadius) {
      celestial.position.x = celestial.orbitRadius * Math.cos(angle);
      celestial.position.z = celestial.orbitRadius * Math.sin(angle) * Math.cos(celestial.orbitInclination || 0);
      celestial.position.y = celestial.orbitRadius * Math.sin(angle) * -Math.sin(celestial.orbitInclination || 0);
      celestial.orbit.rotation.y = -angle;
    }

    // Rotation
    if (celestial.rotationSpeed) celestial.rotation.y += celestial.rotationSpeed;

    // Recursion
    if (celestial.children) {
      celestial.children.forEach((child) => {
        if (child.isCelestialBody || child.isCelestialParticles) updateCelestial(child);
      });
    }
  };

  const animate = function () {
    requestAnimationFrame(animate);

    // Celestial Movement
    let startingPosition = selectedPlanet ? selectedPlanet.getWorldPosition(new THREE.Vector3()) : undefined;
    updateCelestial(sun, undefined);
    let endingPosition = selectedPlanet ? selectedPlanet.getWorldPosition(new THREE.Vector3()) : undefined;

    // Camera Movement
    let direction = new THREE.Vector3();
    camera.getWorldDirection(direction);

    let forward = direction.multiplyScalar(velocity);
    let right = new THREE.Vector3().crossVectors(camera.up, direction).normalize().multiplyScalar(velocity);

    if (movement.forward) camera.position.add(forward);
    if (movement.backward) camera.position.sub(forward);
    if (movement.left) camera.position.sub(right);
    if (movement.right) camera.position.add(right);

    // Camera Focus
    if (selectedPlanet && camera.position.distanceTo(selectedPlanet.getWorldPosition(new THREE.Vector3())) > 10000) {
      hasFocused = false;
    }

    if (selectedPlanet) {
      const target = selectedPlanet.getWorldPosition(new THREE.Vector3());

      let cameraTarget;

      if (selectedPlanet.name === "Sun") {
        cameraTarget = new THREE.Vector3(0, 30, 60);
      } else {
        const parent = selectedPlanet.parent;
        const parentPosition = parent.getWorldPosition(new THREE.Vector3());
        const planetPosition = selectedPlanet.getWorldPosition(new THREE.Vector3());

        let direction;
        if (parent.name === "Sun") {
          direction = new THREE.Vector3().subVectors(parentPosition, planetPosition).normalize();
        } else {
          direction = new THREE.Vector3().subVectors(planetPosition, parentPosition).normalize();
        }

        cameraTarget = new THREE.Vector3().copy(planetPosition).add(direction.multiplyScalar(60)).add(new THREE.Vector3(0, 30, 0));
      }

      if (controls.target.distanceTo(target) < 1 && camera.position.distanceTo(cameraTarget) < 100) {
        hasFocused = true;
      }

      if (hasFocused) {
        const delta = new THREE.Vector3().subVectors(endingPosition, startingPosition);
        camera.position.add(delta);
        controls.target.add(delta);
      } else {
        if (simulationSpeed < 30) {
          camera.position.lerp(cameraTarget, cameraDamping * simulationSpeed);
          controls.target.lerp(target, cameraDamping * simulationSpeed);
        } else {
          camera.position.copy(cameraTarget);
          controls.target.copy(target);
          hasFocused = true;
        }
      }
    }

    // Camera Limits
    if (camera.position.x > 10000) camera.position.x = 10000;
    if (camera.position.x < -10000) camera.position.x = -10000;
    if (camera.position.y > 10000) camera.position.y = 10000;
    if (camera.position.y < -10000) camera.position.y = -10000;
    if (camera.position.z > 10000) camera.position.z = 10000;
    if (camera.position.z < -10000) camera.position.z = -10000;

    // Render
    controls.update();
    composer.render();
  };
  animate();
};

main();
