import { useEffect, useRef } from 'react'
import L from 'leaflet'
import { sportColor } from '../../utils/format'

delete L.Icon.Default.prototype._getIconUrl
L.Icon.Default.mergeOptions({
  iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
  iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
  shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
})

const TILE_LAYERS = {
  dark: {
    url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/">CARTO</a>',
  },
  street: {
    url: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png',
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/">CARTO</a>',
  },
  satellite: {
    url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
    attribution: '© <a href="https://www.esri.com/">Esri</a>',
  },
}

// Tile options tuned for smoother panning/zooming: keep a larger off-screen
// buffer of tiles and don't defer loads until the map is idle.
const TILE_OPTS = { maxZoom: 19, keepBuffer: 6, updateWhenIdle: false, updateWhenZooming: false }

// Slow → fast colour ramp for speed-coloured routes (red → purple).
export const SPEED_STOPS = ['#ef4444', '#f97316', '#22c55e', '#3b82f6', '#a855f7']

// CSS gradient string for the speed legend.
export const SPEED_GRADIENT = `linear-gradient(to right, ${SPEED_STOPS.join(', ')})`

const SPEED_LEVELS = 24   // quantisation steps → smooth gradient while limiting layer count

function lerpColor(c1, c2, t) {
  const a = parseInt(c1.slice(1), 16), b = parseInt(c2.slice(1), 16)
  const r = Math.round(((a >> 16) & 255) + (((b >> 16) & 255) - ((a >> 16) & 255)) * t)
  const g = Math.round(((a >> 8) & 255) + (((b >> 8) & 255) - ((a >> 8) & 255)) * t)
  const bl = Math.round((a & 255) + ((b & 255) - (a & 255)) * t)
  return `#${((1 << 24) + (r << 16) + (g << 8) + bl).toString(16).slice(1)}`
}

function rampColor(t) {
  t = Math.max(0, Math.min(1, t))
  const seg = t * (SPEED_STOPS.length - 1)
  const i = Math.min(SPEED_STOPS.length - 2, Math.floor(seg))
  return lerpColor(SPEED_STOPS[i], SPEED_STOPS[i + 1], seg - i)
}

function decodePolyline(encoded) {
  const coords = []
  let index = 0, lat = 0, lng = 0
  while (index < encoded.length) {
    let b, shift = 0, result = 0
    do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5 } while (b >= 0x20)
    lat += (result & 1) ? ~(result >> 1) : result >> 1
    shift = 0; result = 0
    do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5 } while (b >= 0x20)
    lng += (result & 1) ? ~(result >> 1) : result >> 1
    coords.push([lat / 1e5, lng / 1e5])
  }
  return coords
}

const dot = (color) => L.divIcon({
  html: `<div style="width:12px;height:12px;background:${color};border:2px solid white;border-radius:50%"></div>`,
  iconSize: [12, 12], iconAnchor: [6, 6], className: '',
})

function drawRoute(map, { polyline, dataPoints, sportType, colorMode }, trackRef) {
  if (trackRef.current) {
    trackRef.current.remove()
    trackRef.current = null
  }

  // Prefer the data-point track when colouring by speed; fall back to the encoded polyline.
  const speedPts = (colorMode === 'speed' && dataPoints)
    ? dataPoints.filter(p => p.latitude != null && p.longitude != null)
    : []

  const group = L.layerGroup()

  if (speedPts.length >= 2 && speedPts.some(p => p.speed_ms != null)) {
    const speeds = speedPts.map(p => p.speed_ms).filter(s => s != null && s > 0)
    speeds.sort((a, b) => a - b)
    // Clamp the range to the 5th–95th percentile so a couple of GPS spikes don't wash out the ramp.
    const lo = speeds[Math.floor(speeds.length * 0.05)] ?? 0
    const hi = speeds[Math.floor(speeds.length * 0.95)] ?? lo + 1

    const levelOf = (s) => {
      const t = (hi > lo) ? (((s ?? lo) - lo) / (hi - lo)) : 0.5
      return Math.round(Math.max(0, Math.min(1, t)) * SPEED_LEVELS)
    }

    // Group consecutive points into runs of the same colour level → one polyline per run.
    let runStart = 0
    let runLevel = levelOf(speedPts[0].speed_ms)
    const flush = (end) => {
      const coords = speedPts.slice(runStart, end + 1).map(p => [p.latitude, p.longitude])
      if (coords.length >= 2) {
        L.polyline(coords, { color: rampColor(runLevel / SPEED_LEVELS), weight: 3, opacity: 0.95 }).addTo(group)
      }
    }
    for (let i = 1; i < speedPts.length; i++) {
      const level = levelOf(speedPts[i].speed_ms)
      if (level !== runLevel) {
        flush(i)            // include current point so runs join up
        runStart = i
        runLevel = level
      }
    }
    flush(speedPts.length - 1)

    const coords = speedPts.map(p => [p.latitude, p.longitude])
    L.marker(coords[0], { icon: dot('#22c55e') }).addTo(group)
    L.marker(coords[coords.length - 1], { icon: dot('#ef4444') }).addTo(group)
    group.addTo(map)
    trackRef.current = group
    map.fitBounds(L.latLngBounds(coords), { padding: [20, 20] })
    return
  }

  // Solid single-colour route from the encoded polyline.
  if (!polyline) return
  const coords = decodePolyline(polyline)
  if (!coords.length) return
  L.polyline(coords, { color: sportColor(sportType), weight: 3, opacity: 0.9 }).addTo(group)
  L.marker(coords[0], { icon: dot('#22c55e') }).addTo(group)
  L.marker(coords[coords.length - 1], { icon: dot('#ef4444') }).addTo(group)
  group.addTo(map)
  trackRef.current = group
  map.fitBounds(L.latLngBounds(coords), { padding: [20, 20] })
}

export default function ActivityMap({ polyline, dataPoints, hoveredDistance, sportType, mapType = 'street', colorMode = 'speed', onMapClick }) {
  const mapRef = useRef(null)
  const mapInstanceRef = useRef(null)
  const markerRef = useRef(null)
  const trackRef = useRef(null)
  const tileLayerRef = useRef(null)
  const drawArgsRef = useRef({ polyline, dataPoints, sportType, colorMode })
  const clickRef = useRef(onMapClick)

  drawArgsRef.current = { polyline, dataPoints, sportType, colorMode }
  useEffect(() => { clickRef.current = onMapClick }, [onMapClick])

  useEffect(() => {
    if (!mapRef.current || mapInstanceRef.current) return

    mapInstanceRef.current = L.map(mapRef.current, {
      zoomControl: true,
      attributionControl: true,
      preferCanvas: true,
    })

    const tile = TILE_LAYERS.street
    tileLayerRef.current = L.tileLayer(tile.url, { attribution: tile.attribution, ...TILE_OPTS })
      .addTo(mapInstanceRef.current)

    mapInstanceRef.current.on('click', (e) => {
      if (clickRef.current) clickRef.current({ lat: e.latlng.lat, lng: e.latlng.lng })
    })

    return () => {
      mapInstanceRef.current?.remove()
      mapInstanceRef.current = null
    }
  }, [])

  useEffect(() => {
    if (!mapInstanceRef.current) return
    const tile = TILE_LAYERS[mapType] || TILE_LAYERS.street
    if (tileLayerRef.current) tileLayerRef.current.remove()
    tileLayerRef.current = L.tileLayer(tile.url, { attribution: tile.attribution, ...TILE_OPTS })
      .addTo(mapInstanceRef.current)
  }, [mapType])

  useEffect(() => {
    if (!mapInstanceRef.current) return
    drawRoute(mapInstanceRef.current, drawArgsRef.current, trackRef)
  }, [polyline, sportType, colorMode, dataPoints])

  useEffect(() => {
    if (!mapInstanceRef.current || !dataPoints || hoveredDistance == null) return
    const point = dataPoints.find(p => p.distance_m >= hoveredDistance)
    if (!point?.latitude || !point?.longitude) return
    if (markerRef.current) {
      markerRef.current.setLatLng([point.latitude, point.longitude])
    } else {
      const icon = L.divIcon({
        html: '<div style="width:14px;height:14px;background:#fff;border:3px solid #3b82f6;border-radius:50%;box-shadow:0 0 6px rgba(59,130,246,0.8)"></div>',
        iconSize: [14, 14], iconAnchor: [7, 7], className: '',
      })
      markerRef.current = L.marker([point.latitude, point.longitude], { icon })
        .addTo(mapInstanceRef.current)
    }
  }, [hoveredDistance, dataPoints])

  return <div ref={mapRef} style={{ height: '100%', width: '100%', background: '#1a1a2e' }} />
}
