<script lang="ts">
	import { onMount, createEventDispatcher } from "svelte";
	import maplibregl from "maplibre-gl";
	import type { Map as MapLibreMap, Marker, LngLatBoundsLike } from "maplibre-gl";
	import "maplibre-gl/dist/maplibre-gl.css";
	import WindVisualization from "$lib/components/WindVisualisation.svelte";
	import { distHaversine } from "$lib/mathutil";
	import type { Prediction, Telemetry } from "$lib/types";

	export let mode: "prediction" | "telemetry" = "prediction";
	export let data: Prediction | Telemetry | null = null;

	let map: MapLibreMap;
	let mapContainer: HTMLDivElement;
	let markers: Marker[] = [];
	let animatedMarker: Marker | null = null;
	let mouseLat = 0;
	let mouseLng = 0;
	let isSelecting = false;

	let windData: any;

	const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();

	onMount(async () => {
		if (!mapContainer) return;

		map = new maplibregl.Map({
			container: mapContainer,
			style: {
				version: 8,
				sources: {
					osm: {
						type: "raster",
						tiles: ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"],
						tileSize: 256,
						attribution:
							'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
					},
				},
				layers: [
					{
						id: "osm",
						type: "raster",
						source: "osm",
						minzoom: 0,
						maxzoom: 19,
					},
				],
			},
			center: [-0.09, 51.505],
			zoom: 13,
		});

		// Add navigation control (zoom buttons)
		map.addControl(new maplibregl.NavigationControl(), "bottom-left");

		// Add scale control
		map.addControl(new maplibregl.ScaleControl({ maxWidth: 100, unit: "metric" }), "bottom-right");

		const response = await fetch("src/routes/testVelo.json");
		windData = await response.json();

		map.on("mousemove", (e: maplibregl.MapMouseEvent) => {
			mouseLat = e.lngLat.lat;
			mouseLng = e.lngLat.lng;
		});

		map.on("click", (e: maplibregl.MapMouseEvent) => {
			if (isSelecting) {
				dispatch("coordinatesSelected", { lat: e.lngLat.lat, lng: e.lngLat.lng });
				stopSelection();
			}
		});
	});

	$: if (map && data) {
		plotData(data);
	} else if (map) {
		clearMapLayers();
	}

	export const startSelection = () => {
		isSelecting = true;
		if (mapContainer) mapContainer.style.cursor = "crosshair";
	};

	export const stopSelection = () => {
		isSelecting = false;
		if (mapContainer) mapContainer.style.cursor = "";
	};

	export const plotData = (plotData: Prediction | Telemetry) => {
		if (mode === "prediction") {
			plotPrediction(plotData as Prediction);
		} else if (mode === "telemetry") {
			plotTelemetry(plotData as Telemetry);
		}
	};

	export const clearMapLayers = () => {
		// Remove all markers
		markers.forEach((marker) => marker.remove());
		markers = [];

		// Remove animated marker
		removeAnimatedMarker();

		// Remove all layers and sources related to flight paths
		if (map && map.getLayer("flight-path")) {
			map.removeLayer("flight-path");
		}
		if (map && map.getSource("flight-path")) {
			map.removeSource("flight-path");
		}
		if (map && map.getLayer("telemetry-path")) {
			map.removeLayer("telemetry-path");
		}
		if (map && map.getSource("telemetry-path")) {
			map.removeSource("telemetry-path");
		}
	};

	const createMarker = (
		lng: number,
		lat: number,
		color: string,
		iconUrl: string,
		title: string,
	) => {
		const el = document.createElement("div");
		el.className = "custom-marker";
		el.style.backgroundImage = `url(${iconUrl})`;
		el.style.width = "10px";
		el.style.height = "10px";
		el.style.backgroundSize = "100%";
		el.title = title;

		// Create popup with coordinates
		const popup = new maplibregl.Popup({ offset: 25, closeButton: false }).setHTML(
			`<b>${title}</b><br>Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}`,
		);

		const marker = new maplibregl.Marker({ element: el })
			.setLngLat([lng, lat])
			.setPopup(popup)
			.addTo(map);

		// Show popup on hover
		el.addEventListener("mouseenter", () => {
			marker.togglePopup();
		});
		el.addEventListener("mouseleave", () => {
			marker.togglePopup();
		});

		markers.push(marker);
		return marker;
	};

	const createBurstMarker = (lng: number, lat: number, title: string) => {
		const el = document.createElement("div");
		el.className = "custom-marker";
		el.style.backgroundImage = `url(pop-marker.png)`;
		el.style.width = "16px";
		el.style.height = "16px";
		el.style.backgroundSize = "100%";
		el.title = title;

		// Create popup with coordinates
		const popup = new maplibregl.Popup({ offset: 25, closeButton: false }).setHTML(
			`<b>${title}</b><br>Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}`,
		);

		const marker = new maplibregl.Marker({ element: el })
			.setLngLat([lng, lat])
			.setPopup(popup)
			.addTo(map);

		// Show popup on hover
		el.addEventListener("mouseenter", () => {
			marker.togglePopup();
		});
		el.addEventListener("mouseleave", () => {
			marker.togglePopup();
		});

		markers.push(marker);
		return marker;
	};

	const plotPrediction = (prediction: Prediction) => {
		clearMapLayers();

		const { launch, landing, burst, flight_path, flight_time } = prediction;

		const range = distHaversine(launch.latlng, landing.latlng, 1);
		const f_hours = Math.floor(flight_time / 3600);
		const f_minutes = Math.floor((flight_time % 3600) / 60)
			.toString()
			.padStart(2, "0");
		const flighttime = `${f_hours}hr${f_minutes}`;

		// Helper to extract lat/lng from either format
		const getLat = (latlng: any) => (Array.isArray(latlng) ? latlng[0] : latlng.lat);
		const getLng = (latlng: any) => (Array.isArray(latlng) ? latlng[1] : latlng.lng);

		// Create markers (MapLibre uses [lng, lat] order)
		createMarker(getLng(launch.latlng), getLat(launch.latlng), "#0000ff", "target-blue.png", "Launch");
		createMarker(getLng(landing.latlng), getLat(landing.latlng), "#ff0000", "target-red.png", "Landing");
		createBurstMarker(getLng(burst.latlng), getLat(burst.latlng), "Burst");

		// Add flight path as a line (convert [lat, lng] to [lng, lat] for MapLibre)
		const coordinates = flight_path.map((coord) => {
			if (Array.isArray(coord)) {
				return [coord[1], coord[0]]; // [lat, lng, alt?] -> [lng, lat]
			} else {
				return [coord.lng, coord.lat]; // {lat, lng} -> [lng, lat]
			}
		});

		map.addSource("flight-path", {
			type: "geojson",
			data: {
				type: "Feature",
				properties: {},
				geometry: {
					type: "LineString",
					coordinates: coordinates,
				},
			},
		});

		map.addLayer({
			id: "flight-path",
			type: "line",
			source: "flight-path",
			layout: {
				"line-join": "round",
				"line-cap": "round",
			},
			paint: {
				"line-color": "#000000",
				"line-width": 3,
			},
		});

		// Fit bounds to show entire path
		const bounds = coordinates.reduce(
			(bounds, coord) => {
				return bounds.extend(coord as [number, number]);
			},
			new maplibregl.LngLatBounds(coordinates[0] as [number, number], coordinates[0] as [number, number]),
		);

		map.fitBounds(bounds as LngLatBoundsLike, { padding: 50 });
	};

	const plotTelemetry = (telemetry: Telemetry) => {
		clearMapLayers();

		// Helper to extract lat/lng from either format
		const getLat = (latlng: any) => (Array.isArray(latlng) ? latlng[0] : latlng.lat);
		const getLng = (latlng: any) => (Array.isArray(latlng) ? latlng[1] : latlng.lng);

		// Launch marker (MapLibre uses [lng, lat] order)
		createMarker(
			getLng(telemetry.launch.latlng),
			getLat(telemetry.launch.latlng),
			"#0000ff",
			"target-blue.png",
			"Launch",
		);

		// Telemetry point markers with popups
		telemetry.datapoints.forEach((point) => {
			const el = document.createElement("div");
			el.className = "custom-marker";
			el.style.backgroundImage = `url(marker-sm-red.png)`;
			el.style.width = "10px";
			el.style.height = "10px";
			el.style.backgroundSize = "100%";

			const popup = new maplibregl.Popup({ offset: 25 }).setHTML(
				`<b>Telemetry Point</b><br>Lat: ${point.latitude.toFixed(6)}<br>Lon: ${point.longitude.toFixed(6)}`,
			);

			const marker = new maplibregl.Marker({ element: el })
				.setLngLat([point.longitude, point.latitude])
				.setPopup(popup)
				.addTo(map);

			markers.push(marker);
		});

		// Add flight path as a line (convert [lat, lng] to [lng, lat] for MapLibre)
		const coordinates = telemetry.flight_path.map((coord) => {
			if (Array.isArray(coord)) {
				return [coord[1], coord[0]]; // [lat, lng, alt?] -> [lng, lat]
			} else {
				return [coord.lng, coord.lat]; // {lat, lng} -> [lng, lat]
			}
		});

		map.addSource("telemetry-path", {
			type: "geojson",
			data: {
				type: "Feature",
				properties: {},
				geometry: {
					type: "LineString",
					coordinates: coordinates,
				},
			},
		});

		map.addLayer({
			id: "telemetry-path",
			type: "line",
			source: "telemetry-path",
			layout: {
				"line-join": "round",
				"line-cap": "round",
			},
			paint: {
				"line-color": "#000000",
				"line-width": 3,
			},
		});

		// Fit bounds to show entire path
		const bounds = coordinates.reduce(
			(bounds, coord) => {
				return bounds.extend(coord as [number, number]);
			},
			new maplibregl.LngLatBounds(coordinates[0] as [number, number], coordinates[0] as [number, number]),
		);

		map.fitBounds(bounds as LngLatBoundsLike, { padding: 50 });
	};

	export const panTo = (lat: number, lng: number) => {
		if (map) {
			map.setCenter([lng, lat]);
		}
	};

	export const zoomTo = (lat: number, lng: number, zoomLevel: number) => {
		if (map) {
			map.setCenter([lng, lat]);
			map.setZoom(zoomLevel);
		}
	};

	export const getMap = () => {
		return map;
	};

	export const updateAnimatedMarker = (lat: number, lng: number) => {
		if (!map) return;

		if (!animatedMarker) {
			// Create animated marker
			const el = document.createElement("div");
			el.className = "animated-marker";
			el.innerHTML = `
				<svg width="32" height="32" viewBox="0 0 32 32">
					<circle cx="16" cy="16" r="14" fill="#FF6B6B" opacity="0.3" class="pulse-ring"/>
					<circle cx="16" cy="16" r="8" fill="#FF1744" stroke="white" stroke-width="2"/>
				</svg>
			`;

			animatedMarker = new maplibregl.Marker({ element: el, anchor: "center" })
				.setLngLat([lng, lat])
				.addTo(map);
		} else {
			// Update position
			animatedMarker.setLngLat([lng, lat]);
		}

		// Pan to marker
		map.panTo([lng, lat], { duration: 100 });
	};

	export const removeAnimatedMarker = () => {
		if (animatedMarker) {
			animatedMarker.remove();
			animatedMarker = null;
		}
	};
</script>

<div class="map-container" bind:this={mapContainer}>
	<!-- <div class="card coordinates-display">
		<p class="card-text">
			<b>Lat:</b>
			{mouseLat.toFixed(6)},
			<b>Lon:</b>
			{mouseLng.toFixed(6)}
		</p>
	</div> -->
	<slot />
	{#if map && windData}
		<WindVisualization {map} {windData} />
	{/if}
</div>

<style>
	:global(.animated-marker) {
		cursor: pointer;
	}

	:global(.animated-marker .pulse-ring) {
		animation: pulse 2s ease-out infinite;
		transform-origin: center;
	}

	@keyframes :global(pulse) {
		0% {
			r: 8;
			opacity: 0.8;
		}
		100% {
			r: 14;
			opacity: 0;
		}
	}
</style>
