"use client";

import { useEffect, useRef } from "react";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { TripPoint } from "@/lib/api";

/**
 * The path one trip took, drawn from its recorded breadcrumbs.
 *
 * <p>The line is the trip; the two discs are where it began and ended. Start and end are marked
 * distinctly because a loop that returns to its origin is otherwise unreadable, and "where did
 * they leave it" is the question a support call actually opens with.
 */
export function MapboxTripMap({
  token,
  track,
  start,
  end,
  className = "",
}: {
  token: string;
  track: TripPoint[];
  start: { lat: number; lng: number } | null;
  end: { lat: number; lng: number } | null;
  className?: string;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const mapRef = useRef<mapboxgl.Map | null>(null);
  const readyRef = useRef(false);

  useEffect(() => {
    if (!ref.current || mapRef.current) return;
    mapboxgl.accessToken = token;
    const map = new mapboxgl.Map({
      container: ref.current,
      style: "mapbox://styles/mapbox/dark-v11",
      center: [end?.lng ?? start?.lng ?? 46.6753, end?.lat ?? start?.lat ?? 24.7136],
      zoom: 13,
      attributionControl: false,
    });
    map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), "top-right");
    map.addControl(new mapboxgl.FullscreenControl(), "top-right");
    mapRef.current = map;

    map.on("load", () => {
      readyRef.current = true;
      map.addSource("path", { type: "geojson", data: emptyFC() });
      map.addSource("ends", { type: "geojson", data: emptyFC() });

      // A wider dark casing under the line, so the route stays readable over pale streets as
      // well as dark ones.
      map.addLayer({
        id: "path-casing",
        type: "line",
        source: "path",
        layout: { "line-cap": "round", "line-join": "round" },
        paint: { "line-color": "#000000", "line-width": 7, "line-opacity": 0.45 },
      });
      map.addLayer({
        id: "path-line",
        type: "line",
        source: "path",
        layout: { "line-cap": "round", "line-join": "round" },
        paint: { "line-color": "#F5C24B", "line-width": 3.5 },
      });

      // Every recorded fix, small. They show where the vehicle actually reported rather than
      // implying we know the curve between two distant points.
      map.addLayer({
        id: "path-points",
        type: "circle",
        source: "path",
        filter: ["==", ["geometry-type"], "Point"],
        paint: {
          "circle-radius": 3,
          "circle-color": "#F5C24B",
          "circle-stroke-width": 1,
          "circle-stroke-color": "#000000",
          "circle-opacity": 0.85,
        },
      });

      map.addLayer({
        id: "ends",
        type: "circle",
        source: "ends",
        paint: {
          "circle-radius": 8,
          "circle-color": ["get", "color"],
          "circle-stroke-width": 2.5,
          "circle-stroke-color": "#ffffff",
        },
      });

      sync();
    });

    return () => {
      map.remove();
      mapRef.current = null;
      readyRef.current = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [token]);

  const sync = () => {
    const map = mapRef.current;
    if (!map || !readyRef.current) return;

    const line = track.map((p) => [p.lng, p.lat] as [number, number]);
    const features: GeoJSON.Feature[] = track.map((p) => ({
      type: "Feature",
      properties: {},
      geometry: { type: "Point", coordinates: [p.lng, p.lat] },
    }));
    // A single fix is a dot, not a line: LineString needs two positions and mapbox drops the
    // whole source if given a malformed one.
    if (line.length >= 2) {
      features.push({
        type: "Feature",
        properties: {},
        geometry: { type: "LineString", coordinates: line },
      });
    }
    (map.getSource("path") as mapboxgl.GeoJSONSource | undefined)?.setData({
      type: "FeatureCollection",
      features,
    });

    const ends: GeoJSON.Feature[] = [];
    if (start) {
      ends.push({
        type: "Feature",
        properties: { color: "#22C55E" },
        geometry: { type: "Point", coordinates: [start.lng, start.lat] },
      });
    }
    if (end) {
      ends.push({
        type: "Feature",
        properties: { color: "#EF4444" },
        geometry: { type: "Point", coordinates: [end.lng, end.lat] },
      });
    }
    (map.getSource("ends") as mapboxgl.GeoJSONSource | undefined)?.setData({
      type: "FeatureCollection",
      features: ends,
    });

    const all = [...line, ...ends.map((f) => (f.geometry as GeoJSON.Point).coordinates as [number, number])];
    if (all.length > 0) {
      const bounds = new mapboxgl.LngLatBounds();
      all.forEach((c) => bounds.extend(c));
      // maxZoom, because a trip that never moved would otherwise zoom to street level on one dot.
      map.fitBounds(bounds, { padding: 60, maxZoom: 16, duration: 0 });
    }
  };
  useEffect(sync, [track, start, end]);

  return <div ref={ref} className={className} />;
}

function emptyFC(): GeoJSON.FeatureCollection {
  return { type: "FeatureCollection", features: [] };
}
