<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import maplibregl from 'maplibre-gl';
  import 'maplibre-gl/dist/maplibre-gl.css';
  import type { Timeseries } from '../lib/types';

  export let trackUrl: string;
  export let timeseries: Timeseries | null = null;
  export let bbox: [number, number, number, number] | null = null;
  /** ~20 [lat, lon] preview coords from the activity summary — used to position
   *  the map immediately on mount so there's no flash of world view before the
   *  detail JSON loads and bbox arrives. */
  export let initialCoords: [number, number][] | null = null;
  export let accentColor: string = '#00c8ff';
  export let hoveredIdx: number | null = null;
  /** Colors the track by a metric value using a blue→green→yellow→red gradient. */
  export let colorMode: 'default' | 'speed' | 'hr' | 'power' | 'elevation' | 'cadence' = 'default';

  let mapEl: HTMLDivElement;
  let map: any;
  const MarkerClass = maplibregl.Marker;
  let hoverMarker: any;
  let markersAdded = false;
  let mapLoaded = false;

  const TILE_STYLE = 'https://tiles.openfreemap.org/styles/positron';

  const defaultGradient = [
    'interpolate', ['linear'], ['line-progress'],
    0,   accentColor,
    0.5, '#ff6b35',
    1,   accentColor,
  ];

  // Shared blue → green → yellow → red scale, t ∈ [0, 1]
  function _linearColor(t: number): string {
    const stops: [number, [number, number, number]][] = [
      [0,    [59,  130, 246]],  // blue-500  (low)
      [0.33, [74,  222, 128]],  // green-400
      [0.66, [250, 204, 21 ]],  // yellow-400
      [1,    [239, 68,  68 ]],  // red-500   (high)
    ];
    for (let i = 0; i < stops.length - 1; i++) {
      const [t0, c0] = stops[i], [t1, c1] = stops[i + 1];
      if (t >= t0 && t <= t1) {
        const f = (t - t0) / (t1 - t0);
        return `rgb(${Math.round(c0[0] + (c1[0] - c0[0]) * f)},${Math.round(c0[1] + (c1[1] - c0[1]) * f)},${Math.round(c0[2] + (c1[2] - c0[2]) * f)})`;
      }
    }
    return '#60a5fa';
  }

  // Cumulative-distance progress array from speed integration.
  // Fast sections occupy proportionally more of [0,1] than slow ones,
  // matching their visual length on the track.
  function _computeProgress(ts: Timeseries): number[] | null {
    const { t, speed_kmh } = ts;
    if (!t?.length) return null;
    const n = t.length;
    if (speed_kmh?.length) {
      const cum: number[] = [0];
      let last = 0;
      for (let i = 1; i < n; i++) {
        const s = speed_kmh[i] != null ? speed_kmh[i]! : last;
        if (speed_kmh[i] != null) last = s;
        cum.push(cum[i - 1] + (s / 3.6) * (t[i] - t[i - 1]));
      }
      const total = cum[n - 1];
      if (total > 0) return cum.map(d => Math.min(d / total, 1));
    }
    // Fallback: time-based progress
    const t0 = t[0], span = t[n - 1] - t[0];
    return span > 0 ? t.map(ti => (ti - t0) / span) : null;
  }

  // Generic gradient builder: progress array + any nullable metric array → MapLibre expression.
  function _buildGradient(ts: Timeseries, values: (number | null)[]): any[] | null {
    const progress = _computeProgress(ts);
    if (!progress) return null;
    const n = Math.min(progress.length, values.length);

    let minV = Infinity, maxV = -Infinity;
    for (const v of values) { if (v != null) { if (v < minV) minV = v; if (v > maxV) maxV = v; } }
    if (!isFinite(minV)) return null;
    const range = maxV - minV;

    const expr: any[] = ['interpolate', ['linear'], ['line-progress']];
    let prev = -1, lastFill = minV;
    for (let i = 0; i < n; i++) {
      const p = Math.min(progress[i], 1);
      if (p <= prev + 0.001) continue;
      prev = p;
      const v = values[i] != null ? values[i]! : lastFill;
      if (values[i] != null) lastFill = v;
      expr.push(p, _linearColor(range > 0 ? (v - minV) / range : 0.5));
    }
    if (prev < 1) expr.push(1, expr[expr.length - 1]);
    return expr.length >= 6 ? expr : null;
  }

  function _applyGradient() {
    if (!map?.getLayer('track-line')) return;
    let gradient = defaultGradient;
    if (timeseries && colorMode !== 'default') {
      const field: (number | null)[] | null =
        colorMode === 'speed'     ? timeseries.speed_kmh :
        colorMode === 'hr'        ? timeseries.hr_bpm :
        colorMode === 'power'     ? timeseries.power_w :
        colorMode === 'elevation' ? timeseries.elevation_m :
        colorMode === 'cadence'   ? timeseries.cadence_rpm : null;
      if (field) gradient = _buildGradient(timeseries, field) ?? defaultGradient;
    }
    map.setPaintProperty('track-line', 'line-gradient', gradient);
  }

  // Re-apply whenever colorMode or timeseries changes (once map is loaded).
  $: if (mapLoaded) { colorMode; timeseries; _applyGradient(); }

  onMount(() => {
    // Derive initial center and zoom from preview_coords so the map starts at
    // the right location without waiting for the async detail JSON / bbox load.
    let initCenter: [number, number] = [0, 0];
    let initZoom = 1;
    if (initialCoords && initialCoords.length > 0) {
      const lats = initialCoords.map(c => c[0]);
      const lons = initialCoords.map(c => c[1]);
      initCenter = [
        (Math.min(...lons) + Math.max(...lons)) / 2,
        (Math.min(...lats) + Math.max(...lats)) / 2,
      ];
      initZoom = 10; // rough default; fitBounds will correct this when bbox arrives
    }

    map = new maplibregl.Map({
      container: mapEl,
      style: TILE_STYLE,
      center: initCenter,
      zoom: initZoom,
      attributionControl: false,
    });

    map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');

    // Hover dot marker — must set lngLat before addTo in MapLibre v5
    const el = document.createElement('div');
    el.style.cssText = `
      width:12px;height:12px;border-radius:50%;
      background:white;border:2px solid ${accentColor};
      box-shadow:0 0 6px ${accentColor};display:none;pointer-events:none;
    `;
    hoverMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
      .setLngLat([0, 0])
      .addTo(map);

    map.on('load', () => {
      mapLoaded = true;
      map.addSource('track', {
        type: 'geojson',
        data: trackUrl,
        lineMetrics: true,
      });

      map.addLayer({
        id: 'track-shadow',
        type: 'line',
        source: 'track',
        paint: { 'line-color': 'rgba(0,0,0,0.3)', 'line-width': 5, 'line-blur': 2 },
      });

      map.addLayer({
        id: 'track-line',
        type: 'line',
        source: 'track',
        layout: { 'line-cap': 'round', 'line-join': 'round' },
        paint: {
          'line-width': 3,
          'line-gradient': [
            'interpolate', ['linear'], ['line-progress'],
            0,   accentColor,
            0.5, '#ff6b35',
            1,   accentColor,
          ],
        },
      });
    });
  });

  // Fit to bbox when detail JSON loads (bbox is null at map init).
  // Always resize first so MapLibre knows the real container dimensions,
  // and defer with rAF so the browser has finished laying out the container.
  $: if (map && bbox) {
    const fit = () => requestAnimationFrame(() => {
      map.resize();
      map.fitBounds(
        [[bbox![0], bbox![1]], [bbox![2], bbox![3]]],
        { padding: 40, animate: false },
      );
    });
    map.loaded() ? fit() : map.once('load', fit);
  }

  // Add start/end markers when timeseries arrives
  $: if (map && MarkerClass && timeseries && !markersAdded) {
    markersAdded = true;
    const add = () => {
      // Filter lat/lon together so indices stay aligned
      const pts = (timeseries!.lat ?? [])
        .map((lat, i) => ({ lat, lon: (timeseries!.lon ?? [])[i] }))
        .filter(p => p.lat != null && p.lon != null) as { lat: number; lon: number }[];
      if (!pts.length) return;
      new MarkerClass({ element: makeDot('#4ade80'), anchor: 'center' })
        .setLngLat([pts[0].lon, pts[0].lat]).addTo(map);
      new MarkerClass({ element: makeDot('#f87171'), anchor: 'center' })
        .setLngLat([pts[pts.length - 1].lon, pts[pts.length - 1].lat]).addTo(map);
    };
    map.loaded() ? add() : map.once('load', add);
  }

  // Hover dot linked to chart crosshair
  $: if (hoverMarker && timeseries && hoveredIdx != null) {
    const lat = timeseries.lat?.[hoveredIdx];
    const lon = timeseries.lon?.[hoveredIdx];
    if (lat != null && lon != null) {
      hoverMarker.getElement().style.display = 'block';
      hoverMarker.setLngLat([lon, lat]);
    }
  } else if (hoverMarker) {
    hoverMarker.getElement().style.display = 'none';
  }

  function makeDot(color: string): HTMLDivElement {
    const el = document.createElement('div');
    el.style.cssText = `
      width:10px;height:10px;border-radius:50%;
      background:${color};border:2px solid white;
      box-shadow:0 0 4px rgba(0,0,0,0.5);
    `;
    return el;
  }

  onDestroy(() => {
    resizeObserver?.disconnect();
    map?.remove();
  });

  let resizeObserver: ResizeObserver;
  $: if (mapEl && map) {
    resizeObserver?.disconnect();
    resizeObserver = new ResizeObserver(() => map?.resize());
    resizeObserver.observe(mapEl);
  }
</script>

<div bind:this={mapEl} class="w-full h-full"></div>
