import mapboxgl from 'mapbox-gl'
import type { GeoPosition } from '../../hooks/useGeolocation'

// Build the DOM element that backs the mapbox Marker. We animate the
// heading cone via a CSS rotation so the DOM stays stable across updates
// and mapbox doesn't get confused about which element to position.
function buildLocationEl(): { root: HTMLDivElement; cone: HTMLDivElement } {
  const root = document.createElement('div')
  root.style.cssText = 'width:28px;height:28px;position:relative;pointer-events:none;'
  // Accuracy pulse behind the dot
  const pulse = document.createElement('div')
  pulse.style.cssText = `
    position:absolute;inset:-14px;border-radius:50%;
    background:#3b82f6;opacity:0.25;
    animation:trek-location-pulse 2s ease-out infinite;
  `
  // Heading cone (conic gradient fan)
  const cone = document.createElement('div')
  cone.style.cssText = `
    position:absolute;left:50%;top:50%;width:60px;height:60px;
    transform:translate(-50%,-50%) rotate(0deg);
    background:conic-gradient(from -30deg, rgba(59,130,246,0) 0deg, rgba(59,130,246,0.35) 15deg, rgba(59,130,246,0) 60deg, rgba(59,130,246,0) 360deg);
    border-radius:50%;
    mask:radial-gradient(circle, transparent 12px, black 13px);
    -webkit-mask:radial-gradient(circle, transparent 12px, black 13px);
    transition:transform 0.12s ease-out;
    display:none;
  `
  // Blue dot
  const dot = document.createElement('div')
  dot.style.cssText = `
    position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);
    width:18px;height:18px;border-radius:50%;
    background:#3b82f6;border:3px solid white;
    box-shadow:0 0 0 1px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.3);
  `
  root.appendChild(pulse)
  root.appendChild(cone)
  root.appendChild(dot)
  return { root, cone }
}

// Inject the pulse keyframes once per document so the animation is
// available for every map instance.
function ensurePulseStyle() {
  if (document.getElementById('trek-location-style')) return
  const s = document.createElement('style')
  s.id = 'trek-location-style'
  s.textContent = `
    @keyframes trek-location-pulse {
      0%   { transform: scale(0.6); opacity: 0.35; }
      70%  { transform: scale(1.6); opacity: 0; }
      100% { transform: scale(1.6); opacity: 0; }
    }
  `
  document.head.appendChild(s)
}

export interface LocationMarkerHandle {
  update: (p: GeoPosition | null) => void
  destroy: () => void
}

// Creates (or reuses) a location marker + accuracy circle on the given
// mapbox map. Returns a handle the caller uses to push position updates
// and clean up. Keeps its own DOM element and GeoJSON source so it can
// coexist with the regular trip markers.
export function attachLocationMarker(map: mapboxgl.Map): LocationMarkerHandle {
  ensurePulseStyle()
  const { root, cone } = buildLocationEl()
  const marker = new mapboxgl.Marker({ element: root, anchor: 'center' })

  const ensureAccuracyLayer = () => {
    if (map.getSource('trek-location-accuracy')) return
    try {
      map.addSource('trek-location-accuracy', {
        type: 'geojson',
        data: { type: 'FeatureCollection', features: [] },
      })
      // Draw the accuracy ring as a geographic polygon: it's a real geodesic
      // circle defined in meters, so mapbox automatically scales it with
      // zoom the way Apple/Google Maps does — always the same real-world
      // size regardless of viewport.
      map.addLayer({
        id: 'trek-location-accuracy',
        type: 'fill',
        source: 'trek-location-accuracy',
        paint: {
          'fill-color': '#3b82f6',
          'fill-opacity': 0.14,
          'fill-outline-color': '#3b82f6',
        },
      })
    } catch { /* noop */ }
  }

  // Build a polygon approximating a geodesic circle around (lng, lat)
  // with the given radius in meters. 48 segments is plenty for a smooth
  // edge without paying much CPU per fix.
  const geodesicCircle = (lng: number, lat: number, radiusMeters: number): number[][] => {
    const earth = 6378137
    const d = radiusMeters / earth
    const lat1 = lat * Math.PI / 180
    const lng1 = lng * Math.PI / 180
    const coords: number[][] = []
    const segments = 48
    for (let i = 0; i <= segments; i++) {
      const bearing = (i / segments) * 2 * Math.PI
      const lat2 = Math.asin(Math.sin(lat1) * Math.cos(d) + Math.cos(lat1) * Math.sin(d) * Math.cos(bearing))
      const lng2 = lng1 + Math.atan2(
        Math.sin(bearing) * Math.sin(d) * Math.cos(lat1),
        Math.cos(d) - Math.sin(lat1) * Math.sin(lat2),
      )
      coords.push([lng2 * 180 / Math.PI, lat2 * 180 / Math.PI])
    }
    return coords
  }

  const setAccuracy = (p: GeoPosition) => {
    const src = map.getSource('trek-location-accuracy') as mapboxgl.GeoJSONSource | undefined
    if (!src) return
    if (!p.accuracy || p.accuracy < 1) {
      src.setData({ type: 'FeatureCollection', features: [] })
      return
    }
    const ring = geodesicCircle(p.lng, p.lat, p.accuracy)
    src.setData({
      type: 'FeatureCollection',
      features: [{
        type: 'Feature',
        properties: {},
        geometry: { type: 'Polygon', coordinates: [ring] },
      }],
    })
  }

  let lastPosRef: GeoPosition | null = null

  if (map.loaded()) ensureAccuracyLayer()
  else map.once('load', ensureAccuracyLayer)

  const handle: LocationMarkerHandle = {
    update: (p) => {
      lastPosRef = p
      if (!p) {
        marker.remove()
        const src = map.getSource('trek-location-accuracy') as mapboxgl.GeoJSONSource | undefined
        src?.setData({ type: 'FeatureCollection', features: [] })
        return
      }
      marker.setLngLat([p.lng, p.lat])
      if (!marker.getElement().parentElement) marker.addTo(map)
      if (p.heading !== null && !Number.isNaN(p.heading)) {
        cone.style.display = 'block'
        cone.style.transform = `translate(-50%,-50%) rotate(${p.heading}deg)`
      } else {
        cone.style.display = 'none'
      }
      setAccuracy(p)
    },
    destroy: () => {
      try { marker.remove() } catch { /* noop */ }
      try {
        if (map.getLayer('trek-location-accuracy')) map.removeLayer('trek-location-accuracy')
        if (map.getSource('trek-location-accuracy')) map.removeSource('trek-location-accuracy')
      } catch { /* noop */ }
    },
  }

  return handle
}
