
import maplibregl from "maplibre-gl";
import proj4 from "proj4";
import * as THREE from "three";
import { LidarControl } from "maplibre-gl-lidar";
import "maplibre-gl/dist/maplibre-gl.css";
import "maplibre-gl-lidar/style.css";

// UTM Zone 32N für Koordinatentransformation registrieren
proj4.defs("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs");

// Koordinaten des Obernkirchener Sandstein (Kartenmittelpunkt)
const center = [9.209116842757239, 52.26520546238239];

// Zustandsvariablen
let currentPointCloud = null; // aktuell geladene Potree-Punktwolke
let isVisible = true; // Punktwolke sichtbar?
let currentRenderer = "deckgl"; // Default-Renderer deck.gl"
let currentColorMode = "rgb"; // Farbmodus: "rgb" oder "elevation"
let currentPointSize = Number(document.getElementById("pointSizeSlider").value);
let currentPointcloudKey = null; // ausgewählte Punktwolke
let lidarControl = null; // Deck.gl LidarControl-Instanz
var quality = "medium"; // Qualitätsstufe


//-------------------------------- Karten-Initialisierung --------------------------------------------------
// Grundkarte: OpenFreeMap hell
const map = new maplibregl.Map({
  container: "map",
  style: "https://tiles.openfreemap.org/styles/bright",
  center: center,
  zoom: 17,
  maxZoom: 24,
});

// Karten-Bedienelemente
map.addControl(
  new maplibregl.NavigationControl({
    visualizePitch: true,
    showZoom: true,
    showCompass: true,
  }),
);


// Karten Load-Event: LidarControl anlegen, JSON-Datei laden
map.on("load", () => {
  lidarControl = new LidarControl({
    title: "LiDAR-Viewer",
    collapsed: false,
    pointSize: 2,
    colorScheme: "rgb", // 'elevation' oder 'rgb'
    pointBudget: 3000000,
    maxRequests: 32,
    pickable: false,
  });

  loadInfoJSON();
});


//---------------Potree Eigenschaften--------------------------
var pointSize = document.getElementById("pointSizeSlider").value;
const elRenderArea = document.getElementById("potree_render_area");
const viewer = new Potree.Viewer(elRenderArea, { noDragAndDrop: true });
viewer.setEDLEnabled(false);
viewer.setFOV(60);
viewer.setMinNodeSize(pointSize);
viewer.setBackground("none");
viewer.orbitControls.enabled = false;
viewer.fpControls.enabled = false;
viewer.deviceControls.enabled = false;
elRenderArea.style.display = "none";
viewer.setPointBudget(3000000);


//-------------------------- LIDARCONTROL (DECK.GL) NEU AUFBAUEN ----------------------------------------
// entfernt das bestehende LidarControl und erzeugt es mit den aktuellen Einstellungen neu
function resetLidarControl() {
  if (!lidarControl) 
    return;
  try {
    map.removeControl(lidarControl);
  } catch (e) {}
  lidarControl = null;

  lidarControl = new LidarControl({
    title: "LiDAR-Viewer",
    collapsed: false,
    pointSize: currentPointSize > 0 ? currentPointSize : 2,
    colorScheme: currentColorMode === "rgb" ? "rgb" : "elevation",
    pointBudget: 3000000,
    maxRequests: 32,
    pickable: false,
  });
  map.addControl(lidarControl, "top-right");
}


//-------------- PUNKTWOLKEN-DATENQUELLEN (Pfade je Renderer-Format) - aktuell auf Server der Jade-HS --------------------

// Punktwolkendaten im Potree-Format
function getPointCloudFilesPOTREE() {
  return {
    first: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp1_${quality}/metadata.json`,
    second: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp2_${quality}/metadata.json`,
    third: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp3_${quality}/metadata.json`,
  };
}

// Punktwolkendaten im COPC (Cloud Optimised Point Cloud)-Format für deck.gl
function getPointCloudFilesCOPC() {
  return {
    first: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp1_${quality}.copc.laz`,
    second: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp2_${quality}.copc.laz`,
    third: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp3_${quality}.copc.laz`,
  };
}


//---------------------------- PUNKTWOLKEN LADEN (je nach aktivem Renderer) -------------------------------------

// wählt automatisch Potree- oder Deck.gl-Renderer und lädt die Punktwolke
function loadPointCloud() {
  if (!currentPointcloudKey) 
    return;
  if (currentRenderer === "deckgl") {
    loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]);
  } else {
    loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]);
  }
}


//---------------------- Punktwolken laden über den deck.gl-Renderer ----------------------
function loadDeckGL(path) {
  if (!lidarControl || !path) 
    return;
  resetLidarControl();
  lidarControl.loadPointCloudStreaming(path);
  setTimeout(() => applyDeckGLSettings(), 500);
}

// Punktgröße, Farbschema und Colormap auf LidarControl anwenden
function applyDeckGLSettings() {
  if (!lidarControl) 
    return;
  try {
    lidarControl.setPointSize(currentPointSize > 0 ? currentPointSize : 2);
    lidarControl.setColorScheme(currentColorMode === "rgb" ? "rgb" : "elevation",);
    lidarControl.setColormap("jet");
  } catch (e) {}
}


//------------------------- Renderer zwischen deck.gl und potree umstellen -----------------------------------
function switchRenderer(renderer) {
  currentRenderer = renderer;
  const slider = document.getElementById("pointSizeSlider");
  if (renderer === "deckgl") {
    elRenderArea.style.display = "none";
    if (currentPointCloud){
        currentPointCloud.visible = false;
    }
    currentPointSize = 2;
    slider.min = 1;
    slider.max = 10;
    slider.value = 2;
    if (lidarControl && currentPointcloudKey){
      loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]);
    }
    document.getElementById("rendererToggle").dataset.active = "deckgl";
    document.getElementById("rendererLabel").textContent = "Renderer: Deck.gl";
  } else {
    resetLidarControl();
    elRenderArea.style.display = "block";

    currentPointSize = 0;
    slider.min = 0;
    slider.max = 1000;
    slider.value = 0;

    if (currentPointcloudKey){
      loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]);
    }
    document.getElementById("rendererToggle").dataset.active = "potree";
    document.getElementById("rendererLabel").textContent = "Renderer: Potree";
  }
  applyVisibility();
}


// Sichtbarkeit der Punktwolke je nach aktivem Renderer setzen
function applyVisibility() {
  if (currentRenderer === "potree" && currentPointCloud) {
    currentPointCloud.visible = isVisible;
  }

  if (currentRenderer === "deckgl") {
    // Trick: Punktgröße auf 0 setzen
    lidarControl.setPointSize(isVisible ? 2 : 0);
  }
}

// Farbmodus (rgb/elevation) auf die Potree-Punktwolke anwenden
function applyColorModePotree() {
  if (!currentPointCloud) 
    return;
  let mat = currentPointCloud.material;
  mat.activeAttributeName = currentColorMode === "rgb" ? "rgba" : "elevation";
  viewer.renderer.resetState();
  viewer.render();
  map.triggerRepaint();
}


// Farbmodus je nach aktivem Renderer anwenden
function applyColorMode() {
  if (currentRenderer === "potree") {
    applyColorModePotree();
  } else {
    applyDeckGLSettings();
  }
}


//-------------------- INFO-JSON laden (Texte/Bilder zu den Punktwolken) -----------------
function loadInfoJSON() {
  fetch("info.json").then((r) => r.json()).then((data) => {
      pointCloudInfo = data;
    })
    .catch((e) => console.error("Fehler beim Laden der JSON:", e));
}


//------------------------ Punktwolke im Potree Format laden -----------------------------
function loadPointCloudPotree(path) {
  viewer.scene.view.yaw = 0;
  viewer.scene.view.pitch = 0;

  // vorhandene Punktwolke aus der Szene entfernen
  if (currentPointCloud) {
    const index = viewer.scene.pointclouds.indexOf(currentPointCloud);
    if (index !== -1) 
        viewer.scene.pointclouds.splice(index, 1);
    if (currentPointCloud.parent){
      currentPointCloud.parent.remove(currentPointCloud);
    }
    currentPointCloud = null;
    viewer.render();
  }

  if (!path || !isVisible)
    return;

  Potree.loadPointCloud(path, "punktwolke", function (e) {
    currentPointCloud = e.pointcloud;
    viewer.scene.addPointCloud(currentPointCloud);

    let material = currentPointCloud.material;
    material.size = 1;
    material.pointSizeType = Potree.PointSizeType.ADAPTIVE;

    // 1. Three.js zwingen, die Wolke sofort in der 3D-Welt zu platzieren
    currentPointCloud.updateMatrixWorld(true);

    // 2. POTREE WECKRUF
    viewer.update(viewer.clock.getDelta(), Number.MAX_VALUE);

    // --- HIER IST DIE KORREKTUR ---
    // Erzwinge den aktuell ausgewählten Farbmodus (z.B. elevation)
    // auf das frisch generierte Material der neuen Punktwolke!
    applyColorMode();

    // 3. Jetzt die Kamera berechnen und setzen
    syncCamera();

    // 4. Ein zweites Mal synchronisieren
    requestAnimationFrame(() => {
      syncCamera();
    });
  });
}


// Variable, um Endlosschleifen zu verhindern
let isSyncing = false;

//------------------ Kamera von Potree und MapLibre syncronisieren ------------------------------------
function syncCamera() {
  if (!currentPointCloud || isSyncing || currentRenderer !== "potree") 
    return;
  isSyncing = true;
  const transform = map.transform;

  // 1. Dimensionen auf den Pixel genau angleichen
  if (viewer.renderer) {
    viewer.renderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight,);
  }

  // 2. Mathematische Parameter aus dem MapLibre-Kern extrahieren
  const pitch = (transform.pitch * Math.PI) / 180;
  const bearing = (transform.bearing * Math.PI) / 180;
  const mapCenter = map.getCenter();
  const [utmX, utmY] = proj4("EPSG:4326", "EPSG:25832", [mapCenter.lng, mapCenter.lat,]);

  // MapLibres exakte mathematische Kameradistanz nutzen
  const distanceInMeters = transform.cameraToCenterDistance / transform.pixelsPerMeter;
  const target = new THREE.Vector3(utmX, utmY, 0);
  const offset = new THREE.Vector3(0, -distanceInMeters * Math.sin(pitch), distanceInMeters * Math.cos(pitch),);
  offset.applyAxisAngle(new THREE.Vector3(0, 0, 1), -bearing);
  const cameraPosition = target.clone().add(offset);

  // 3. Potree-Kamera absolut starr setzen
  viewer.scene.view.position.copy(cameraPosition);
  viewer.scene.view.lookAt(target);
  viewer.setFOV(transform.fov);

  // 4. Potree rendern
  viewer.renderer.resetState();
  viewer.render();

  isSyncing = false;
}


// Kartenbewegungs-Events: Kamera-Synchronisation
map.on("movestart", () => {
  map.getCanvasContainer().style.cursor = "grabbing";
});

// bei jeder Kartenbewegung die Kameras synchronisieren
map.on("zoom", syncCamera);
map.on("move", syncCamera);
map.on("rotate", syncCamera);
map.on("pitch", syncCamera);
map.on("draw", syncCamera);

// Potree-Canvas soll keine Mausereignisse abfangen, nur die MapLibre-Canvas
elRenderArea.style.pointerEvents = "none";
map.getCanvas().style.pointerEvents = "auto";


//---------------------- Hintergrundkarte ändern ---------------------------------------------------
function changeBaseMap(newMap) {
  var basemapStyle;
  switch (newMap) {
    // dunkle Karte
    case "openfree_dark":
      basemapStyle = "https://tiles.openfreemap.org/styles/dark";
      break;
    
    // helle Karte
    case "openfree_bright":
      basemapStyle = "https://tiles.openfreemap.org/styles/bright";
      break;
    
    // mit LOD1-GEbäudemodellen
    case "openfree_liberty":
      basemapStyle = "https://tiles.openfreemap.org/styles/liberty";
      break;

    // Satellitenbild
    case "satellite":
      basemapStyle = {
        version: 8,
        sources: {
          "raster-tiles": {
            type: "raster",
            tiles: [
              "https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH",
            ],
            tileSize: 256,
            attribution: "&copy; MapTiler",
          },
        },
        layers: [
          {
            id: "satellite-layer",
            type: "raster",
            source: "raster-tiles",
          },
        ],
      };
      break;

    // Gelände
    case "terrain":
      basemapStyle = {
        version: 8,
        sources: {
          topo: {
            type: "raster",
            url: "https://api.maptiler.com/maps/topo-v4/tiles.json?key=6mG881AthmTTWyLvFyjH",
            tileSize: 256,
          },
          terrainSource: {
            type: "raster-dem",
            url: "https://tiles.mapterhorn.com/tilejson.json",
          },
          hillshadeSource: {
            type: "raster-dem",
            url: "https://tiles.mapterhorn.com/tilejson.json",
          },
        },
        layers: [
          {
            id: "topo",
            type: "raster",
            source: "topo",
          },
          {
            id: "hills",
            type: "hillshade",
            source: "hillshadeSource",
            layout: {
              visibility: "visible",
            },
            paint: {
              "hillshade-shadow-color": "#473B24",
            },
          },
        ],
        terrain: {
          source: "terrainSource",
          exaggeration: 1,
        },
        sky: {},
      };
      break;

    // Gelände mit Satellitenbild
    case "satellite_terrain":
      basemapStyle = {
        version: 8,
        sources: {
          "raster-tiles": {
            type: "raster",
            tiles: [
              "https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH",
            ],
            tileSize: 256,
            attribution: "&copy; MapTiler",
          },
          terrainSource: {
            type: "raster-dem",
            url: "https://tiles.mapterhorn.com/tilejson.json",
          },
          hillshadeSource: {
            type: "raster-dem",
            url: "https://tiles.mapterhorn.com/tilejson.json",
          },
        },
        layers: [
          {
            id: "raster-tiles",
            type: "raster",
            source: "raster-tiles",
          },
          {
            id: "hills",
            type: "hillshade",
            source: "hillshadeSource",
            layout: {
              visibility: "visible",
            },
            paint: {
              "hillshade-shadow-color": "#473B24",
            },
          },
        ],
        terrain: {
          source: "terrainSource",
          exaggeration: 1,
        },
        sky: {},
      };
      break;
  }
  map.setStyle(basemapStyle);
}



//----------------------------------Action-Listener und Event-Handler--------------------------------------

// Punktwolke über Button aus/einblenden
document.querySelector("#disable").addEventListener("click", function () {
  isVisible = !isVisible;
  applyVisibility();

  if (isVisible) {
    this.textContent = "Punktwolke ausblenden";
    this.classList.add("active-state");
    this.classList.remove("inactive-state");
  } else {
    this.textContent = "Punktwolke anzeigen";
    this.classList.add("inactive-state");
    this.classList.remove("active-state");
  }
});


// Koordinaten an der Mausposition darstellen (auf 5 Nachkommastellen begrenzt)
const coordinatesDiv = document.getElementById("coordinates");
map.on("mousemove", (e) => {
  coordinatesDiv.innerHTML = `Lon: ${e.lngLat.lng.toFixed(5)}° | Lat: ${e.lngLat.lat.toFixed(5)}°`;
});


// Hintergrundkarte ändern, wenn anderes Element im DropDown gewählt wird
document.querySelector('select[name="basemap"]').addEventListener("change", (e) => {
    changeBaseMap(e.target.value)
});

// Punktwolke austauschen, wenn anderes Element im DropDown gewählt wird
document.querySelector('select[name="pointcloud"]').addEventListener("change", (e) => {
    currentPointcloudKey = e.target.value;
    loadPointCloud();
});


// Kartenausschnitt auf Ursprung zurücksetzen
document.getElementById("location").addEventListener("click", () => map.flyTo({ center, zoom: 17 }));


// Punktgröße mit Slider anpassen
document.getElementById("pointSizeSlider").oninput = function () {
  currentPointSize = Number(this.value);
  if (currentRenderer === "potree") {
    viewer.setMinNodeSize(currentPointSize);
  } else if (lidarControl) {
    try {
      lidarControl.setPointSize(currentPointSize);
    } catch (e) {}
  }
};



//--------------------------- Sidepannel ein/ausklappen ----------------------------------------------------
const closeButton = document.getElementById("closeSideBarButton");
const sidebar = document.getElementById("sidebar");
const openOuter = document.getElementById("openButtonOuter");
if (closeButton) {
  closeButton.addEventListener("click", () => {
    const rect = closeButton.getBoundingClientRect();
    sidebar.style.display = "none";
    openOuter.innerHTML = "<button class='sideBarButtons' id='openSideBarButton' title='Menü ausklappen'>>></button>";
    const openButton = document.getElementById("openSideBarButton");
    openButton.style.top = `${rect.top + window.scrollY - 2}px`;
    openButton.addEventListener("click", () => {
      sidebar.style.display = "flex";
      openOuter.innerHTML = "";
    });
  });
}


//------------------------- QUALITÄTS- UND FARB-BUTTONS ----------------------------------------------------
// Qualitätsstufe wählen
document.querySelectorAll(".qualityButtons").forEach((btn) => {
  btn.addEventListener("click", () => {
    quality = btn.id;
    if (currentPointcloudKey) 
        loadPointCloud();
    document.querySelectorAll(".qualityButtons").forEach((b) => b.classList.remove("active"));
    btn.classList.add("active");
  });
});


// Farbmodus (rgb/elevation) wählen
document.querySelectorAll(".colorButtons").forEach((btn) => {
  btn.addEventListener("click", () => {
    document.querySelectorAll(".colorButtons").forEach((b) => b.classList.remove("active"));
    btn.classList.add("active");
    currentColorMode = btn.id;
    applyColorMode();
  });
});


// Sobald die Karte das allererste Mal stabil steht, Kamera abgleichen
map.once("idle", () => {
  syncCamera();
});



//--------------- INFO-PANEL (Texte & Bilder zu den Punktwolken) -------------------------------
let pointCloudInfo = {};
const infoPanel = document.getElementById("info-panel");
const infoPanelTitle = document.getElementById("info-panel-title");
const infoPanelText = document.getElementById("info-panel-text");
const openInfoPanel = document.getElementById("openInfoPanel");
const closeInfoPanel = document.getElementById("closeInfoPanel");
let currentImageIndex = 0;

// Info-Panel mit Titel, Text und Bild/Bildern der gewählten Punktwolke befüllen
function updateInfoPanel() {
  const key = document.querySelector('select[name="pointcloud"]').value;
  const info = pointCloudInfo[key];
  currentImageIndex = 0;

  if (info) {
    infoPanelTitle.textContent = info.title;
    infoPanelText.textContent = info.text;
    updateImage(info);
  } else {
    infoPanelTitle.textContent = "Info";
    infoPanelText.textContent = "Bitte wählen Sie eine Punktwolke aus.";
    document.getElementById("info-panel-image").style.display = "none";
    document.getElementById("info-image-nav").style.display = "none";
  }
}


// aktuelles Bild + Bildzähler im Info-Panel anzeigen
function updateImage(info) {
  const img = document.getElementById("info-panel-image");
  const nav = document.getElementById("info-image-nav");
  const counter = document.getElementById("info-image-counter");

  img.src = info.images[currentImageIndex];
  img.style.display = "block";

  if (info.images.length > 1) {
    nav.style.display = "flex";
    counter.textContent = `${currentImageIndex + 1} / ${info.images.length}`;
  } else {
    nav.style.display = "none";
  }
}


// vorheriges Bild im Info-Panel anzeigen
document.getElementById("info-img-prev").addEventListener("click", () => {
  const key = document.querySelector('select[name="pointcloud"]').value;
  const info = pointCloudInfo[key];
  if (!info) 
    return;
  currentImageIndex = (currentImageIndex - 1 + info.images.length) % info.images.length;
  updateImage(info);
});


// nächstes Bild im Info-Panel anzeigen
document.getElementById("info-img-next").addEventListener("click", () => {
  const key = document.querySelector('select[name="pointcloud"]').value;
  const info = pointCloudInfo[key];
  if (!info) 
    return;
  currentImageIndex = (currentImageIndex + 1) % info.images.length;
  updateImage(info);
});


// Info-Panel öffnen
openInfoPanel.addEventListener("click", () => {
  infoPanel.classList.add("open");
});


// Info-Panel schließen
closeInfoPanel.addEventListener("click", () => {
  infoPanel.classList.remove("open");
  openInfoPanel.style.display = "flex";
});


// Info-Panel aktualisieren, wenn Punktwolke gewechselt wird
document.querySelector('select[name="pointcloud"]').addEventListener("change", () => {
    updateInfoPanel();
  });


//Renderer Toggle Button
document.getElementById("rendererToggle").addEventListener("click", function () {
    const next = currentRenderer === "deckgl" ? "potree" : "deckgl";
    switchRenderer(next);
  });