import * as THREE from "three";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.151.0/examples/jsm/controls/OrbitControls.js";

document.addEventListener("DOMContentLoaded", () => {
  const container = document.querySelector(".BlogGlobe");
  const canvas = document.getElementById("myCanvas");

  if (!container) {
    console.log("Class .BlogGlobe does not exist on this page.");
    return;
  }
  if (!canvas) {
    console.error("Canvas #myCanvas does not exist!");
    return;
  }
  console.log("All required elements are present!");

  let sizes, scene, camera, renderer, controls, raycaster, mouse;
  let movingLines = [];
  let countryDots = [];

  //* Dot color countries *//
  const primaryColor = getComputedStyle(document.documentElement)
    .getPropertyValue("--primary-color")
    .trim();
  const countries = [
    { name: "USA", lat: 37.7749, lon: -122.4194, color: primaryColor },
    { name: "UK", lat: 51.5074, lon: -0.1278, color: primaryColor },
    { name: "India", lat: 20.5937, lon: 78.9629, color: primaryColor },
    { name: "Japan", lat: 35.6895, lon: 139.6917, color: primaryColor },
    { name: "Brazil", lat: -14.235, lon: -51.9253, color: primaryColor }
  ];

  const connections = [
    { from: "USA", to: "UK" },
    { from: "UK", to: "India" },
    { from: "India", to: "Japan" },
    { from: "Japan", to: "Brazil" },
    { from: "Brazil", to: "USA" }
  ];

  const calcPosFromLatLonRad = (lon, lat, radius) => {
    const phi = (90 - lat) * (Math.PI / 180);
    const theta = (180 - lon) * (Math.PI / 180);

    const x = radius * Math.sin(phi) * Math.cos(theta);
    const y = radius * Math.cos(phi);
    const z = radius * Math.sin(phi) * Math.sin(theta);

    return new THREE.Vector3(x, y, z);
  };

  const setBaseSphere = () => {
    const baseSphere = new THREE.SphereGeometry(15, 35, 35);
    const baseMaterial = new THREE.MeshStandardMaterial({
      color: 0x999999,
      transparent: true,
      opacity: 0.88
    });
    const baseMesh = new THREE.Mesh(baseSphere, baseMaterial);
    scene.add(baseMesh);
  };

  //* The glow constructor (halo) *//
  const createGlow = (dotMesh, glowColor) => {
    const glowMaterial = new THREE.MeshBasicMaterial({
      color: glowColor,
      transparent: true,
      opacity: 1
    });

    const glowGeometry = new THREE.SphereGeometry(dotMesh.geometry.parameters.radius * 1, 10, 10);
    const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);

    //* Place glow around the dot *//
    glowMesh.position.copy(dotMesh.position);
    return glowMesh;
  };

  // Function that creates national dots
  const createCountryDots = () => {
    countries.forEach((country) => {
      const position = calcPosFromLatLonRad(country.lon, country.lat, 15);

      const normalizedPosition = position.clone().normalize().multiplyScalar(15);

      const dotGeometry = new THREE.SphereGeometry(0.1, 10, 10);
      const dotMaterial = new THREE.MeshBasicMaterial({
        color: country.color,
        emissive: country.color,
        emissiveIntensity: 0,
        visible: false,
        transparent: true
      });
      const dot = new THREE.Mesh(dotGeometry, dotMaterial);
      dot.position.copy(normalizedPosition);

      const glow = createGlow(dot, country.color);

      scene.add(dot);
      scene.add(glow);

      countryDots.push({
        country: country.name,
        dotMesh: dot,
        glowSphere: glow,
        position: normalizedPosition,
        isActive: false,
        dotScale: 1,
        dotOpacity: 1,
        animationProgress: 0
      });
    });
  };

  const createConnections = () => {
    connections.forEach((connection) => {
      const fromCountry = countries.find((c) => c.name === connection.from);
      const toCountry = countries.find((c) => c.name === connection.to);

      if (fromCountry && toCountry) {
        const radius = 15;
        const curveHeight = 0.5;

        const start = calcPosFromLatLonRad(fromCountry.lon, fromCountry.lat, radius);
        const end = calcPosFromLatLonRad(toCountry.lon, toCountry.lat, radius);

        const curvePoints = [];
        const divisions = 200;

        for (let i = 0; i <= divisions; i++) {
          const t = i / divisions;

          const intermediate = new THREE.Vector3().lerpVectors(start, end, t);

          intermediate.normalize().multiplyScalar(radius + curveHeight * Math.sin(Math.PI * t));
          curvePoints.push(intermediate);
        }

        const curveGeometry = new THREE.BufferGeometry();
        const positions = [];
        const colors = [];

        const customColor = { r: 184 / 255, g: 184 / 255, b: 184 / 255 }; //* Chuẩn hóa giá trị màu sắc *//

        curvePoints.forEach((point) => {
          positions.push(point.x, point.y, point.z);
          colors.push(customColor.r, customColor.g, customColor.b); // Giá trị đã chuẩn hóa
        });

        curveGeometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
        curveGeometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));

        const curveMaterial = new THREE.LineBasicMaterial({
          vertexColors: true,
          linewidth: 2
        });

        const curveLine = new THREE.Line(curveGeometry, curveMaterial);
        scene.add(curveLine);

        movingLines.push({
          curvePoints,
          curveGeometry,
          curveMaterial,
          progress: 0,
          direction: 1,
          speed: Math.random() * (0.005 - 0.002) + 0.002,
          color: customColor
        });
      }
    });
  };

  const easeOutElastic = (t) => {
    const c4 = (2 * Math.PI) / 3;
    return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
  };

  const updateMovingLines = () => {
    movingLines.forEach((lineData) => {
      const divisions = lineData.curvePoints.length - 1;

      const cubicEase = (t) => t * t * t * (t * (6 * t - 15) + 10);

      lineData.progress += lineData.speed * lineData.direction;

      if (lineData.progress >= 1) {
        lineData.progress = 1;
        lineData.direction = -1;
      }

      if (lineData.progress <= 0) {
        lineData.progress = 0;
        lineData.direction = 1;
      }

      const easedProgress = cubicEase(lineData.progress);

      const index = Math.min(
        Math.max(Math.floor(easedProgress * divisions), 0),
        lineData.curvePoints.length - 1
      );

      if (!lineData.curvePoints[index]) {
        console.error(`curvePoints[${index}] is undefined.`);
        return;
      }

      const colors = lineData.curveGeometry.attributes.color.array;

      for (let i = 0; i < lineData.curvePoints.length; i++) {
        const colorIndex = i * 3;
        const opacity = Math.max(0, 0 - Math.abs(lineData.progress - i / divisions) * 0.5);

        colors[colorIndex] = lineData.color.r * opacity; // R
        colors[colorIndex + 1] = lineData.color.g * opacity; // G
        colors[colorIndex + 2] = lineData.color.b * opacity; // B
      }

      if (lineData.direction === 1) {
        const activePoint = Math.floor(easedProgress * divisions);

        for (let i = 0; i <= activePoint; i++) {
          const colorIndex = i * 3;
          const opacity = Math.sin(((i / activePoint) * Math.PI) / 2);

          colors[colorIndex] = lineData.color.r; // R
          colors[colorIndex + 1] = lineData.color.g; // G
          colors[colorIndex + 2] = lineData.color.b; // B
        }
      } else {
        const activePoint = Math.floor((1 - easedProgress) * divisions);

        for (let i = divisions; i >= activePoint; i--) {
          const colorIndex = i * 3;
          const opacity = Math.sin((((divisions - i) / (divisions - activePoint)) * Math.PI) / 2);

          colors[colorIndex] = lineData.color.r; // R
          colors[colorIndex + 1] = lineData.color.g; // G
          colors[colorIndex + 2] = lineData.color.b; // B
        }
      }

      lineData.curveGeometry.attributes.color.needsUpdate = true;
    });
  };

  const setDotsFromImage = (imageData, radius) => {
    const width = imageData.width;
    const height = imageData.height;
    const step = 1; // Only sample every 4th pixel
    const positions = [];

    for (let y = 0; y < height; y += step) {
      for (let x = 0; x < width; x += step) {
        const pixelIndex = (y * width + x) * 4;
        const r = imageData.data[pixelIndex] / 255;
        const g = imageData.data[pixelIndex + 1] / 255;
        const b = imageData.data[pixelIndex + 2] / 255;

        if (r > 0.8 && g > 0.8 && b > 0.8) continue;

        const lon = (x / width) * 360 - 180;
        const lat = 90 - (y / height) * 180;

        const position = calcPosFromLatLonRad(lon, lat, radius);
        positions.push(position.x, position.y, position.z);
      }
    }

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));

    const material = new THREE.PointsMaterial({
      size: 0.1,
      color: 0x888888
    });

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

  const setScene = () => {
    sizes = { width: container.offsetWidth, height: container.offsetHeight };

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(30, sizes.width / sizes.height, 1, 1000);
    camera.position.z = 60;

    renderer = new THREE.WebGLRenderer({ canvas, antialias: false, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

    setControls();
    setBaseSphere();

    const mapImgUrl = container.getAttribute("data-map-img");
    const image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = () => {
      const imageCanvas = document.createElement("canvas");
      imageCanvas.width = image.width;
      imageCanvas.height = image.height;
      const context = imageCanvas.getContext("2d");
      context.drawImage(image, 0, 0);

      const imageData = context.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
      setDotsFromImage(imageData, 15);
    };
    image.src = mapImgUrl;

    createConnections();
    createCountryDots();
    resize();
    render();
  };

  raycaster = new THREE.Raycaster();
  mouse = new THREE.Vector2();

  container.addEventListener("mousemove", (event) => {
    const rect = container.getBoundingClientRect();

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

  const checkIntersection = () => {
    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.children, true);

    if (intersects.length > 0) {
      const hoveredObject = intersects[0].object;

      if (hoveredObject.geometry instanceof THREE.SphereGeometry) {
        container.style.cursor = "pointer";
      } else {
        container.style.cursor = "default";
      }
    } else {
      container.style.cursor = "default";
    }
  };

  const setControls = () => {
    controls = new OrbitControls(camera, renderer.domElement);
    controls.autoRotate = true;
    controls.autoRotateSpeed = 1.2;
    controls.enableDamping = true;
    controls.enablePan = false;
    controls.enableZoom = false;
  };

  const resize = () => {
    sizes = { width: container.offsetWidth, height: container.offsetHeight };
    camera.aspect = sizes.width / sizes.height;
    camera.updateProjectionMatrix();
    renderer.setSize(sizes.width, sizes.height);
  };

  const render = () => {
    controls.update();
    updateMovingLines();
    checkIntersection();
    renderer.render(scene, camera);
    window.requestAnimationFrame(render);
  };

  setScene();
});
