<script>
    import { onDestroy, onMount } from "svelte";
    import L from "leaflet";
    import { theme } from "./stores.js";
    import {
        defaultDispatch,
        samplePathResponse,
        fallbackBounds,
    } from "./sampleData.js";
    import Sidebar from "./components/Sidebar.svelte";
    import PlaybackControls from "./components/PlaybackControls.svelte";
    import {
        buildTimeline,
        computeBounds,
        computeWallClock,
        colorFor,
    } from "./utils.js";

    let palette = [];
    let lightTiles, darkTiles;

    let STEP_SECONDS = 1;

    function updatePalette() {
        if (typeof document === "undefined") return;
        const style = getComputedStyle(document.documentElement);
        const newPalette = Array.from({ length: 6 }, (_, i) =>
            // Reads CSS custom property --p-1 through --p-6 (palette colors defined in CSS)
            style.getPropertyValue(`--p-${i + 1}`).trim(),
        );

        // Only update if colors are valid and different to avoid unnecessary redraws
        if (
            newPalette.some((c) => c) &&
            JSON.stringify(newPalette) !== JSON.stringify(palette)
        ) {
            palette = newPalette;
        }
    }

    // const fallbackBounds = {
    //     minLng: -3.19,
    //     maxLng: -3.17,
    //     minLat: 55.94,
    //     maxLat: 55.99,
    // };

    let mapContainer;
    let map;
    let pathLayer;
    let markerLayer;
    let restrictedLayer;
    let servicePointLayer;
    let currentBounds = fallbackBounds;

    let apiBase = "http://localhost:8080/api/v1";
    let blackBoxBase = "http://localhost:3000";
    let dispatchBody = JSON.stringify(defaultDispatch, null, 2);
    let plannedPath = samplePathResponse;
    let restrictedAreas = [];
    let servicePoints = [];
    let stepSeconds = STEP_SECONDS;

    let status = "System Ready. Waiting for dispatch payload.";
    let snapshotStatus = "";
    let snapshot = [];
    let positionsNow = [];
    let snapshotController;
    let lastSnapshotTime = "";
    let tick = 0;
    let isPlaying = false;
    let loading = false;
    let playTimer;
    let startTime = "";
    let desiredTime = "";
    let sidebarOpen = true;

    function deriveStartTime(payload) {
        if (!Array.isArray(payload)) return null;
        let earliest = Number.POSITIVE_INFINITY;
        for (const rec of payload) {
            if (!rec?.date || !rec?.time) continue;
            const ts = Date.parse(`${rec.date}T${rec.time}`);
            if (!Number.isNaN(ts)) {
                earliest = Math.min(earliest, ts);
            }
        }
        if (!Number.isFinite(earliest)) return null;
        return new Date(earliest).toISOString().slice(0, 16);
    }

    // Set to current time
    startTime = new Date().toISOString().slice(0, 16);
    desiredTime = startTime;

    function jumpToTime(timeValue) {
        if (!timeValue) return;
        const target = Date.parse(timeValue);
        if (Number.isNaN(target)) return;
        // Reset mission base to the chosen time so the progress bar reflects that date.
        startTime = timeValue;
        desiredTime = timeValue;
        tick = 0;
        fetchSnapshotForTime(new Date(target).toISOString());
    }

    // Reactive Statements
    $: timeline = buildTimeline(plannedPath);
    $: totalSteps = Math.max(
        timeline.totalSteps,
        plannedPath?.totalMoves || 1,
        1,
    );
    $: tick = Math.min(tick, totalSteps - 1);
    $: playbackSeconds = tick * stepSeconds;
    $: wallClock = computeWallClock(startTime, playbackSeconds);
    $: fetchSnapshotForTime(wallClock, true);
    $: positionsNow = snapshotToPositions(snapshot);
    $: currentBounds =
        positionsNow.length > 0
            ? computeBounds(
                  positionsNow
                      .map((d) => d.current)
                      .filter(
                          (p) =>
                              p &&
                              typeof p.lat === "number" &&
                              typeof p.lng === "number",
                      ),
              )
            : timeline.bounds || fallbackBounds;

    // Update Map
    $: if (map && palette.length > 0) {
        refreshMap(positionsNow);
    }

    // Update theme
    $: {
        if (typeof document !== "undefined") {
            if ($theme === "dark") {
                document.documentElement.classList.add("dark");
                if (map && lightTiles && darkTiles) {
                    if (map.hasLayer(lightTiles)) map.removeLayer(lightTiles);
                    if (!map.hasLayer(darkTiles)) map.addLayer(darkTiles);
                }
            } else {
                document.documentElement.classList.remove("dark");
                if (map && lightTiles && darkTiles) {
                    if (map.hasLayer(darkTiles)) map.removeLayer(darkTiles);
                    if (!map.hasLayer(lightTiles)) map.addLayer(lightTiles);
                }
            }
            // We need to wait for the browser to apply the new CSS variables
            setTimeout(updatePalette, 50);
        }
    }

    onMount(() => {
        updatePalette(); // Set initial palette

        map = L.map(mapContainer, {
            preferCanvas: true,
            zoomControl: false, // We'll move zoom control or rely on scroll
            attributionControl: false, // Custom placement if needed, or keep minimal
        });

        // ADD ZOOM CONTROL to Top Right
        L.control.zoom({ position: "topright" }).addTo(map);

        const tileOptions = {
            attribution:
                '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/attributions">CARTO</a>',
            subdomains: "abcd",
            maxZoom: 20,
        };

        lightTiles = L.tileLayer(
            "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
            tileOptions,
        );
        darkTiles = L.tileLayer(
            "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
            tileOptions,
        );

        if ($theme === "dark") {
            darkTiles.addTo(map);
        } else {
            lightTiles.addTo(map);
        }

        pathLayer = L.layerGroup().addTo(map);
        markerLayer = L.layerGroup().addTo(map);
        restrictedLayer = L.layerGroup().addTo(map);
        servicePointLayer = L.layerGroup().addTo(map);

        fitMapToBounds();
        refreshMap();
        loadRestrictedAreas();
        // loadServicePoints();
        return () => map?.remove();
    });

    onDestroy(() => {
        stopPlaying();
    });

    function loadSample() {
        plannedPath = samplePathResponse;
        status = "Loaded sample simulation data.";
        tick = 0;
        stopPlaying();
        fitMapToBounds();
        const sampleStart = deriveStartTime(defaultDispatch);
        if (sampleStart) {
            startTime = sampleStart;
            desiredTime = sampleStart;
        }
    }

    function togglePlay() {
        if (isPlaying) {
            stopPlaying();
        } else if (totalSteps > 1) {
            isPlaying = true;
            clearInterval(playTimer);
            playTimer = setInterval(() => {
                tick = (tick + 1) % totalSteps;
                if (tick === totalSteps - 1) stopPlaying(); // Auto stop at end
            }, 650);
        }
    }

    function stopPlaying() {
        isPlaying = false;
        if (playTimer) clearInterval(playTimer);
    }

    async function fetchSnapshot() {
        await fetchSnapshotForTime(wallClock, false);
    }

    async function loadRestrictedAreas() {
        try {
            const res = await fetch(`${apiBase}/restrictedAreas`);
            if (!res.ok)
                throw new Error(
                    res.statusText || "failed to load restricted areas",
                );
            const data = await res.json();
            restrictedAreas = Array.isArray(data) ? data : [];
            drawRestrictedAreas();
        } catch (err) {
            console.error("[RestrictedAreas] fetch failed:", err);
        }
    }

    async function loadServicePoints() {
        try {
            const res = await fetch(`${apiBase}/servicePoints`);
            if (!res.ok)
                throw new Error(
                    res.statusText || "failed to load service points",
                );
            const data = await res.json();
            servicePoints = Array.isArray(data) ? data : [];
            drawServicePoints();
        } catch (err) {
            console.error("[ServicePoints] fetch failed:", err);
        }
    }

    async function fetchSnapshotForTime(
        requestedTime = wallClock,
        silent = false,
    ) {
        if (!requestedTime) return;

        if (silent && requestedTime === lastSnapshotTime) return;

        snapshotStatus = silent
            ? `Syncing snapshot for ${requestedTime}`
            : `Querying Black Box for ${requestedTime}...`;
        lastSnapshotTime = requestedTime;

        if (snapshotController) snapshotController.abort();
        const controller = new AbortController();
        snapshotController = controller;

        try {
            const res = await fetch(
                `${blackBoxBase}/snapshot?time=${encodeURIComponent(requestedTime)}`,
                { signal: controller.signal },
            );
            if (!res.ok) throw new Error(res.status);
            const data = await res.json();
            if (Array.isArray(data) && data.length > 0) {
                snapshot = data;
                snapshotStatus = `Retrieved ${snapshot.length} events for ${requestedTime}`;
            } else {
                // Graceful fallback: project planned position if no events exist at the requested time.
                const fallback = (timeline?.drones || [])
                    .map((d, idx) => {
                        const coords = d.path || [];
                        if (!coords.length) return null;
                        const pos = coords[Math.min(tick, coords.length - 1)];
                        return {
                            droneId: d.id ?? idx + 1,
                            latitude: pos.lat,
                            longitude: pos.lng,
                            timestamp: requestedTime,
                        };
                    })
                    .filter(Boolean);
                snapshot = fallback;
                snapshotStatus = `No telemetry at ${requestedTime}; showing planned positions.`;
            }
        } catch (err) {
            if (err?.name === "AbortError") return;
            snapshotStatus = `Connection Error: ${err.message}`;
            snapshot = [];
            lastSnapshotTime = "";
        }
    }

    function fitMapToBounds() {
        if (!map || !currentBounds) return;
        const b = currentBounds;
        map.fitBounds(
            [
                [b.minLat, b.minLng],
                [b.maxLat, b.maxLng],
            ],
            {
                padding: [50, 50],
                animate: true,
            },
        );
    }

    function refreshMap(currentPositions = positionsNow) {
        if (!map || !pathLayer || !markerLayer) return;
        pathLayer.clearLayers();
        markerLayer.clearLayers();

        currentPositions.forEach((drone, idx) => {
            const color = colorFor(idx, palette);

            // // Draw Full Trajectory (Dimmed)
            // if (drone.path.length > 1) {
            //     L.polyline(
            //         drone.path.map((p) => [p.lat, p.lng]),
            //         {
            //             color: color,
            //             weight: 2,
            //             opacity: 0.2,
            //             dashArray: "4, 8", // Dashed line for future path
            //         },
            //     ).addTo(pathLayer);
            // }
            //
            // // Draw Visited Trajectory (Bright)
            // if (drone.visited.length > 1) {
            //     L.polyline(
            //         drone.visited.map((p) => [p.lat, p.lng]),
            //         {
            //             color: color,
            //             weight: 3,
            //             opacity: 0.9,
            //             className: "neon-path", // Can add glow via CSS if desired
            //         },
            //     ).addTo(pathLayer);
            // }

            // Draw Drone Marker
            if (drone.current) {
                const marker = L.circleMarker(
                    [drone.current.lat, drone.current.lng],
                    {
                        radius: 6,
                        color: "#fff",
                        weight: 2,
                        fillColor: color,
                        fillOpacity: 1,
                    },
                );

                const isMoving =
                    drone.path.length > 1 &&
                    drone.visited.length < drone.path.length;

                const popupContent = `
                    <div class="min-w-[120px]">
                        <h3 class="font-bold text-sm mb-1" style="color: ${color}">Drone ${drone.id}</h3>
                        <div class="text-[10px] space-y-0.5 font-mono opacity-80">
                            <div class="flex justify-between"><span>Lat:</span> <span>${drone.current.lat.toFixed(4)}</span></div>
                            <div class="flex justify-between"><span>Lng:</span> <span>${drone.current.lng.toFixed(4)}</span></div>
                        </div>
                    </div>
                `;

                // Tooltip style
                marker.bindTooltip(`ID: ${drone.id}`, {
                    permanent: false,
                    direction: "top",
                    className: "drone-tooltip", // Custom class
                });

                marker.bindPopup(popupContent, {
                    className: "drone-popup",
                    closeButton: false,
                });

                marker.addTo(markerLayer);
            }
        });
    }

    function drawRestrictedAreas() {
        if (!restrictedLayer) return;
        restrictedLayer.clearLayers();
        restrictedAreas.forEach((area) => {
            const coords = (area?.vertices || [])
                .map((v) => [v.lat, v.lng])
                .filter((p) => Array.isArray(p) && p.length === 2);
            if (coords.length < 3) return;
            L.polygon(coords, {
                color: "#ef4444", // red
                weight: 2,
                fill: false,
                dashArray: "6,6",
            })
                .bindTooltip(area?.name || "Restricted Area", { sticky: true })
                .addTo(restrictedLayer);
        });
    }

    function drawServicePoints() {
        if (!servicePointLayer) return;
        servicePointLayer.clearLayers();
        servicePoints.forEach((sp) => {
            const loc = sp?.location;
            if (
                !loc ||
                typeof loc.lat !== "number" ||
                typeof loc.lng !== "number"
            )
                return;
            const marker = L.marker([loc.lat, loc.lng], {
                icon: L.divIcon({
                    className: "service-point-icon",
                    html: `<div class="triangle-marker"></div>`,
                    iconSize: [16, 16],
                    iconAnchor: [8, 8],
                }),
            }).bindTooltip(sp?.name || `Service Point ${sp?.id ?? ""}`, {
                sticky: true,
            });
            marker.addTo(servicePointLayer);
        });
    }

    function toggleTheme() {
        theme.update((t) => (t === "dark" ? "light" : "dark"));
    }

    function snapshotToPositions(data = snapshot) {
        const timelinePaths = new Map(
            (timeline?.drones || []).map((d) => [String(d.id), d.path || []]),
        );

        const findClosestIndex = (coords, point) => {
            if (!coords?.length || !point) return -1;
            let bestIdx = -1;
            let bestDist = Number.POSITIVE_INFINITY;
            coords.forEach((c, idx) => {
                const dist =
                    Math.abs(c.lat - point.lat) + Math.abs(c.lng - point.lng);
                if (dist < bestDist) {
                    bestDist = dist;
                    bestIdx = idx;
                }
            });
            return bestIdx;
        };

        return data
            .map((event, idx) => {
                const lat =
                    typeof event.latitude === "number"
                        ? event.latitude
                        : typeof event.lat === "number"
                          ? event.lat
                          : null;
                const lng =
                    typeof event.longitude === "number"
                        ? event.longitude
                        : typeof event.lng === "number"
                          ? event.lng
                          : null;
                if (lat === null || lng === null) return null;
                const id = event.droneId ?? event.drone_id ?? idx + 1;
                const point = { lat, lng };
                const coords = timelinePaths.get(String(id)) || [];
                const matchedIdx = findClosestIndex(coords, point);
                const visited =
                    matchedIdx >= 0 ? coords.slice(0, matchedIdx + 1) : [point];
                const fullPath = coords.length ? coords : [point];
                return {
                    id,
                    path: fullPath,
                    visited,
                    current: visited[visited.length - 1] || point,
                };
            })
            .filter(Boolean);
    }
</script>

<div
    id="map-background"
    class="absolute inset-0 z-0 bg-gray-800"
    bind:this={mapContainer}
></div>

<div
    class="relative z-10 pointer-events-none h-screen w-screen grid transition-[grid-template-columns] duration-500 ease-in-out grid-rows-[1fr_auto]"
    style="grid-template-columns: {sidebarOpen ? '380px' : '0px'} 1fr;"
>
    <!-- Sidebar Container -->
    <div class="row-span-full relative h-full">
        <!-- Collapsed State Toggle Button (Visible when sidebar is closed) -->
        <button
            on:click={() => (sidebarOpen = true)}
            class="pointer-events-auto absolute top-4 left-4 z-50 p-2.5 bg-white/80 dark:bg-slate-900/80 backdrop-blur-xl border border-slate-200 dark:border-slate-800 shadow-lg rounded-xl text-slate-500 hover:text-sky-500 transition-all duration-300 {sidebarOpen
                ? 'opacity-0 pointer-events-none -translate-x-full'
                : 'opacity-100 translate-x-0'}"
            aria-label="Open Sidebar"
        >
            <svg
                xmlns="http://www.w3.org/2000/svg"
                width="20"
                height="20"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                ><rect x="3" y="3" width="18" height="18" rx="2" ry="2"
                ></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg
            >
        </button>

        <Sidebar
            bind:open={sidebarOpen}
            bind:apiBase
            bind:blackBoxBase
            bind:startTime
            bind:stepSeconds
            bind:dispatchBody
            {status}
            {loading}
            on:close={() => (sidebarOpen = false)}
            on:loadSample={loadSample}
        />
    </div>

    <PlaybackControls
        {wallClock}
        {playbackSeconds}
        activeDrones={plannedPath?.dronePaths?.length}
        {isPlaying}
        bind:tick
        {totalSteps}
        {snapshotStatus}
        {snapshot}
        bind:desiredTime
        on:togglePlay={togglePlay}
        on:seek={stopPlaying}
        on:fetchSnapshot={fetchSnapshot}
        on:setTime={(e) => jumpToTime(e.detail)}
    />
</div>
