import { useEffect, useRef, forwardRef, useImperativeHandle, useState } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { Protocol } from 'pmtiles'
import { layers, namedTheme } from 'protomaps-themes-base'
import { getTheme, getThemeSprite, getOverlayConfig } from '../themes/registry'
import { useStore } from '../store'
import { decodePolyline } from '../utils/decode'
import { fetchReverse } from '../api'
import { getConfig, hasFeature } from '../config'
import { MapPin, Navigation, ArrowUpRight, ArrowDownLeft, Plus, Star, Ruler, X } from 'lucide-react'
import RadialMenu from './RadialMenu'
import useContextMenu from '../hooks/useContextMenu'
import toast from 'react-hot-toast'


/** Check if current theme is dark based on registry */
function isCurrentThemeDark() {
  const themeId = document.documentElement.getAttribute('data-theme') || 'dark'
  return getTheme(themeId).dark
}

const ROUTE_SOURCE = 'route-source'
const BOUNDARY_SOURCE = 'boundary-source'
const BOUNDARY_LAYER = 'boundary-layer'
const ROUTE_LAYER_PREFIX = 'route-layer-'
const HILLSHADE_SOURCE = 'hillshade-dem'
const HILLSHADE_LAYER = 'hillshade-layer'
const TRAFFIC_SOURCE = 'traffic-tiles'
const TRAFFIC_LAYER = 'traffic-layer'
const PUBLIC_LANDS_SOURCE = 'public-lands-tiles'
const PUBLIC_LANDS_FILL = 'public-lands-fill'
const PUBLIC_LANDS_LINE = 'public-lands-line'
const PUBLIC_LANDS_LABEL = 'public-lands-label'
const CONTOUR_SOURCE = 'contour-tiles'
const CONTOUR_MINOR = 'contour-minor'
const CONTOUR_INTERMEDIATE = 'contour-intermediate'
const CONTOUR_INDEX = 'contour-index'
const CONTOUR_LABEL = 'contour-label'
const CONTOUR_TEST_SOURCE = 'contour-test-tiles'
const CONTOUR_TEST_MINOR = 'contour-test-minor'
const CONTOUR_TEST_INTERMEDIATE = 'contour-test-intermediate'
const CONTOUR_TEST_INDEX = 'contour-test-index'
const CONTOUR_TEST_LABEL = 'contour-test-label'
const CONTOUR_TEST_10FT_SOURCE = 'contour-test-10ft-tiles'
const CONTOUR_TEST_10FT_MINOR = 'contour-test-10ft-minor'
const CONTOUR_TEST_10FT_INTERMEDIATE = 'contour-test-10ft-intermediate'
const CONTOUR_TEST_10FT_INDEX = 'contour-test-10ft-index'
const CONTOUR_TEST_10FT_LABEL = 'contour-test-10ft-label'
const MEASURE_SOURCE = 'measure-source'
const MEASURE_LINE_LAYER = 'measure-line-layer'
const MEASURE_POINT_LAYER = 'measure-point-layer'
const USFS_SOURCE = 'usfs-trails-source'
const USFS_ROADS_LAYER = 'usfs-roads-layer'
const USFS_TRAILS_LAYER = 'usfs-trails-layer'
const USFS_ROADS_LABEL = 'usfs-roads-label'
const USFS_TRAILS_LABEL = 'usfs-trails-label'
const USFS_ROADS_HIT = 'usfs-roads-hit'
const USFS_TRAILS_HIT = 'usfs-trails-hit'
const BLM_SOURCE = 'blm-trails-source'
const BLM_ROUTES_NATURAL = 'blm-routes-natural'
const BLM_ROUTES_IMPROVED = 'blm-routes-improved'
const BLM_ROUTES_AGGREGATE = 'blm-routes-aggregate'
const BLM_ROUTES_SNOW = 'blm-routes-snow'
const BLM_ROUTES_OTHER = 'blm-routes-other'
const BLM_ROUTES_LABEL = 'blm-routes-label'
const BLM_ROUTES_HIT = 'blm-routes-hit'


// Highlight state - use data-driven expressions to target specific features
const INTERACTIVE_LABEL_LAYERS = ['pois', 'places_subplace', 'places_locality', 'places_region', 'places_country']
let originalPaintValues = {} // Store original paint values for restoration
let highlightState = {
  hoveredLayer: null,
  hoveredName: null,
  selectedLayer: null,
  selectedName: null,
}

function storeOriginalPaint(map, layerId) {
  if (originalPaintValues[layerId]) return
  if (!map.getLayer(layerId)) return
  originalPaintValues[layerId] = {
    'text-color': map.getPaintProperty(layerId, 'text-color'),
    'text-halo-color': map.getPaintProperty(layerId, 'text-halo-color'),
    'text-halo-width': map.getPaintProperty(layerId, 'text-halo-width'),
  }
}

function restoreOriginalPaint(map, layerId) {
  if (!originalPaintValues[layerId] || !map.getLayer(layerId)) return
  const orig = originalPaintValues[layerId]
  if (orig['text-color'] !== undefined) map.setPaintProperty(layerId, 'text-color', orig['text-color'])
  if (orig['text-halo-color'] !== undefined) map.setPaintProperty(layerId, 'text-halo-color', orig['text-halo-color'])
  if (orig['text-halo-width'] !== undefined) map.setPaintProperty(layerId, 'text-halo-width', orig['text-halo-width'])
}

function applyHighlightExpression(map, layerId) {
  if (!map.getLayer(layerId)) return
  storeOriginalPaint(map, layerId)

  const orig = originalPaintValues[layerId]
  const isDark = isCurrentThemeDark()
  const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#7a9a6b'

  // Hover: darken text slightly, bump halo to full opacity for focus effect
  const hoverColor = isDark ? '#ffffff' : '#000000'
  const hoverHaloColor = isDark ? 'rgba(30,30,30,1)' : 'rgba(255,255,255,1)'
  // Selected: accent color text with solid white halo at full opacity
  const selectedHaloColor = isDark ? 'rgba(30,30,30,1)' : 'rgba(255,255,255,1)'

  const isHovered = highlightState.hoveredLayer === layerId && highlightState.hoveredName
  const isSelected = highlightState.selectedLayer === layerId && highlightState.selectedName

  // Build case expressions for each paint property
  // Priority: selected > hover > original

  if (isSelected && isHovered && highlightState.selectedName !== highlightState.hoveredName) {
    // Both selected and hover active on different features
    map.setPaintProperty(layerId, 'text-color', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], accentColor,
      ['==', ['get', 'name'], highlightState.hoveredName], hoverColor,
      orig['text-color'] || (isDark ? '#e0e0e0' : '#2a2a2a')
    ])
    map.setPaintProperty(layerId, 'text-halo-color', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], selectedHaloColor,
      ['==', ['get', 'name'], highlightState.hoveredName], hoverHaloColor,
      orig['text-halo-color'] || (isDark ? 'rgba(20,20,20,0.9)' : 'rgba(255,255,255,0.9)')
    ])
    map.setPaintProperty(layerId, 'text-halo-width', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], 2.2,
      ['==', ['get', 'name'], highlightState.hoveredName], 2,
      orig['text-halo-width'] || 1.5
    ])
  } else if (isSelected) {
    // Only selected
    map.setPaintProperty(layerId, 'text-color', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], accentColor,
      orig['text-color'] || (isDark ? '#e0e0e0' : '#2a2a2a')
    ])
    map.setPaintProperty(layerId, 'text-halo-color', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], selectedHaloColor,
      orig['text-halo-color'] || (isDark ? 'rgba(20,20,20,0.9)' : 'rgba(255,255,255,0.9)')
    ])
    map.setPaintProperty(layerId, 'text-halo-width', [
      'case',
      ['==', ['get', 'name'], highlightState.selectedName], 2.2,
      orig['text-halo-width'] || 1.8
    ])
  } else if (isHovered) {
    // Only hovered
    map.setPaintProperty(layerId, 'text-color', [
      'case',
      ['==', ['get', 'name'], highlightState.hoveredName], hoverColor,
      orig['text-color'] || (isDark ? '#e0e0e0' : '#2a2a2a')
    ])
    map.setPaintProperty(layerId, 'text-halo-color', [
      'case',
      ['==', ['get', 'name'], highlightState.hoveredName], hoverHaloColor,
      orig['text-halo-color'] || (isDark ? 'rgba(20,20,20,0.9)' : 'rgba(255,255,255,0.9)')
    ])
    map.setPaintProperty(layerId, 'text-halo-width', [
      'case',
      ['==', ['get', 'name'], highlightState.hoveredName], 2,
      orig['text-halo-width'] || 1.8
    ])
  } else {
    // No highlight on this layer - restore original
    restoreOriginalPaint(map, layerId)
  }
}

function setHoverHighlight(map, feature) {
  const prevLayer = highlightState.hoveredLayer

  if (!feature) {
    highlightState.hoveredLayer = null
    highlightState.hoveredName = null
    if (prevLayer) applyHighlightExpression(map, prevLayer)
    return
  }

  const layerId = feature.layer?.id
  const name = feature.properties?.name
  if (!layerId || !name || !map.getLayer(layerId)) return

  // Don't hover the selected feature
  if (layerId === highlightState.selectedLayer && name === highlightState.selectedName) return

  highlightState.hoveredLayer = layerId
  highlightState.hoveredName = name

  // Update previous layer if different
  if (prevLayer && prevLayer !== layerId) {
    applyHighlightExpression(map, prevLayer)
  }
  // Update current layer
  applyHighlightExpression(map, layerId)
}

function setSelectedHighlight(map, feature) {
  const prevLayer = highlightState.selectedLayer

  if (!feature) {
    highlightState.selectedLayer = null
    highlightState.selectedName = null
    highlightState.hoveredLayer = null
    highlightState.hoveredName = null
    if (prevLayer) applyHighlightExpression(map, prevLayer)
    return
  }

  const layerId = feature.layer?.id
  const name = feature.properties?.name
  if (!layerId || !name || !map.getLayer(layerId)) return

  highlightState.selectedLayer = layerId
  highlightState.selectedName = name
  // Clear hover when selecting
  highlightState.hoveredLayer = null
  highlightState.hoveredName = null

  // Update previous layer if different
  if (prevLayer && prevLayer !== layerId) {
    applyHighlightExpression(map, prevLayer)
  }
  // Update current layer
  applyHighlightExpression(map, layerId)
}

function clearAllHighlights(map) {
  const layers = [highlightState.hoveredLayer, highlightState.selectedLayer].filter(Boolean)
  highlightState.hoveredLayer = null
  highlightState.hoveredName = null
  highlightState.selectedLayer = null
  highlightState.selectedName = null
  layers.forEach(layerId => restoreOriginalPaint(map, layerId))
}

/** Apply improved base label styling for readability (Google Maps style) */
function applyBaseLabelStyling(map) {
  const isDark = isCurrentThemeDark()

  INTERACTIVE_LABEL_LAYERS.forEach(layerId => {
    if (!map.getLayer(layerId)) return

    // Base styling: dark text with solid opaque white halo for knockout effect
    // This ensures labels read cleanly over any background (parks, water, terrain)
    map.setPaintProperty(layerId, 'text-color', isDark ? '#e0e0e0' : '#2a2a2a')
    map.setPaintProperty(layerId, 'text-halo-color', isDark ? 'rgba(20,20,20,0.9)' : 'rgba(255,255,255,0.9)')
    map.setPaintProperty(layerId, 'text-halo-width', 1.8)

    // Store these as the original values for highlight restoration
    originalPaintValues[layerId] = {
      'text-color': isDark ? '#e0e0e0' : '#2a2a2a',
      'text-halo-color': isDark ? 'rgba(20,20,20,0.9)' : 'rgba(255,255,255,0.9)',
      'text-halo-width': 1.8,
    }
  })
}

/** Build a full MapLibre style object for the given theme */
function buildStyle(themeName) {
  const config = getConfig()
  const tileUrl = config?.tileset?.url || '/tiles/na.pmtiles'
  const attribution = config?.tileset?.attribution || 'Protomaps \u00a9 OSM'

  // Use namedTheme directly for built-in themes, custom colors for others
  const theme = getTheme(themeName)
  const colors = theme.colors || namedTheme(themeName)

  return {
    version: 8,
    glyphs: 'https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf',
    sprite: getThemeSprite(themeName),
    sources: {
      protomaps: {
        type: 'vector',
        url: `pmtiles://${tileUrl}`,
        attribution,
      },
    },
    layers: layers('protomaps', colors, { lang: 'en' }),
  }
}

/** SVG for ATAK-style chevron pointing up (will be rotated via CSS) */
/** Calculate haversine distance between two points in meters */
function haversineDistance(lat1, lon1, lat2, lon2) {
  const R = 6371000 // Earth radius in meters
  const dLat = (lat2 - lat1) * Math.PI / 180
  const dLon = (lon2 - lon1) * Math.PI / 180
  const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2)
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  return R * c
}

/** Format distance for display (feet/miles, imperial) */
function formatDistance(meters) {
  const feet = meters * 3.28084
  if (feet < 1000) return Math.round(feet) + " ft"
  const miles = feet / 5280
  return miles < 10 ? miles.toFixed(2) + " mi" : miles.toFixed(1) + " mi"
}

const CHEVRON_SVG = `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
  <path d="M8 1 L14 13 L8 10 L2 13 Z" fill="var(--accent)" stroke="var(--bg-raised)" stroke-width="1.5" stroke-linejoin="round"/>
</svg>`

/** Add hillshade raster-dem source + layer to the map */
function addHillshade(map, themeId) {
  if (!map || map.getSource(HILLSHADE_SOURCE)) return
  const config = getConfig()
  const hs = config?.tileset_hillshade
  if (!hs?.url) return

  const c = getOverlayConfig(themeId, 'hillshade')

  map.addSource(HILLSHADE_SOURCE, {
    type: 'raster-dem',
    url: `pmtiles://${hs.url}`,
    encoding: hs.encoding || 'terrarium',
    tileSize: 256,
    maxzoom: hs.max_zoom || 12,
  })

  // Insert below the first symbol/label layer for proper z-ordering
  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === 'symbol') {
      beforeId = layer.id
      break
    }
  }

  map.addLayer({
    id: HILLSHADE_LAYER,
    type: 'hillshade',
    source: HILLSHADE_SOURCE,
    paint: {
      'hillshade-exaggeration': c.exaggeration,
      'hillshade-illumination-direction': c.illuminationDirection,
      'hillshade-shadow-color': c.shadowColor,
      'hillshade-highlight-color': c.highlightColor,
    },
  }, beforeId)
}

/** Remove hillshade layer + source */
function removeHillshade(map) {
  if (!map) return
  if (map.getLayer(HILLSHADE_LAYER)) map.removeLayer(HILLSHADE_LAYER)
  if (map.getSource(HILLSHADE_SOURCE)) map.removeSource(HILLSHADE_SOURCE)
}

/** Add traffic raster tile source + layer */
function addTraffic(map, themeId) {
  if (!map || map.getSource(TRAFFIC_SOURCE)) return
  const config = getConfig()
  const tr = config?.traffic
  if (!tr?.proxy_url) return

  const c = getOverlayConfig(themeId, 'traffic')
  const tileUrl = tr.proxy_url.replace('{z}', '{z}').replace('{x}', '{x}').replace('{y}', '{y}')

  map.addSource(TRAFFIC_SOURCE, {
    type: 'raster',
    tiles: [tileUrl],
    tileSize: 256,
    maxzoom: 18,
  })

  map.addLayer({
    id: TRAFFIC_LAYER,
    type: 'raster',
    source: TRAFFIC_SOURCE,
    paint: {
      'raster-opacity': c.opacity,
    },
  })
}

/** Remove traffic layer + source */
function removeTraffic(map) {
  if (!map) return
  if (map.getLayer(TRAFFIC_LAYER)) map.removeLayer(TRAFFIC_LAYER)
  if (map.getSource(TRAFFIC_SOURCE)) map.removeSource(TRAFFIC_SOURCE)
}

/** Add public lands vector tile overlay (PAD-US) */
function addPublicLands(map, themeId) {
  if (!map || map.getSource(PUBLIC_LANDS_SOURCE)) return

  const c = getOverlayConfig(themeId, 'publicLands')

  map.addSource(PUBLIC_LANDS_SOURCE, {
    type: 'vector',
    url: 'pmtiles:///tiles/public-lands.pmtiles',
  })

  // Insert below symbol layers for proper z-ordering
  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === 'symbol') {
      beforeId = layer.id
      break
    }
  }

  // Fill layer — data-driven color by agency + designation
  map.addLayer({
    id: PUBLIC_LANDS_FILL,
    type: 'fill',
    source: PUBLIC_LANDS_SOURCE,
    'source-layer': 'public_lands',
    paint: {
      'fill-color': [
        'case',
        ['==', ['get', 'designation'], 'WA'], c.fillWA,
        ['==', ['get', 'designation'], 'WSA'], c.fillWA,
        ['==', ['get', 'agency'], 'NPS'], c.fillNPS,
        ['==', ['get', 'agency'], 'USFS'], c.fillUSFS,
        ['==', ['get', 'agency'], 'BLM'], c.fillBLM,
        ['==', ['get', 'agency'], 'FWS'], c.fillFWS,
        ['any',
          ['==', ['get', 'manager_type'], 'STAT'],
          ['==', ['get', 'agency'], 'SPR'],
          ['==', ['get', 'agency'], 'SDC'],
          ['==', ['get', 'agency'], 'SLB']
        ], c.fillSTAT,
        ['any',
          ['==', ['get', 'manager_type'], 'LOC'],
          ['==', ['get', 'manager_type'], 'DIST']
        ], c.fillLOC,
        c.fillDefault
      ],
      'fill-opacity': [
        'case',
        ['==', ['get', 'designation'], 'WA'], c.fillOpacityWA * c.opacityMod,
        ['==', ['get', 'designation'], 'WSA'], c.fillOpacityWA * c.opacityMod,
        ['==', ['get', 'agency'], 'NPS'], c.fillOpacityNPS * c.opacityMod,
        ['==', ['get', 'agency'], 'USFS'], c.fillOpacityUSFS * c.opacityMod,
        ['==', ['get', 'agency'], 'BLM'], c.fillOpacityBLM * c.opacityMod,
        ['any',
          ['==', ['get', 'manager_type'], 'STAT'],
          ['==', ['get', 'agency'], 'SPR']
        ], c.fillOpacitySTAT * c.opacityMod,
        ['any',
          ['==', ['get', 'manager_type'], 'LOC'],
          ['==', ['get', 'manager_type'], 'DIST']
        ], c.fillOpacityLOC * c.opacityMod,
        c.fillOpacityDefault * c.opacityMod
      ],
    },
  }, beforeId)

  // Outline layer
  map.addLayer({
    id: PUBLIC_LANDS_LINE,
    type: 'line',
    source: PUBLIC_LANDS_SOURCE,
    'source-layer': 'public_lands',
    paint: {
      'line-color': [
        'case',
        ['==', ['get', 'designation'], 'WA'], c.outlineWA,
        ['==', ['get', 'designation'], 'WSA'], c.outlineWA,
        ['==', ['get', 'agency'], 'NPS'], c.outlineNPS,
        ['==', ['get', 'agency'], 'USFS'], c.outlineUSFS,
        ['==', ['get', 'agency'], 'BLM'], c.outlineBLM,
        ['==', ['get', 'agency'], 'FWS'], c.outlineFWS,
        ['any',
          ['==', ['get', 'manager_type'], 'STAT'],
          ['==', ['get', 'agency'], 'SPR']
        ], c.outlineSTAT,
        ['any',
          ['==', ['get', 'manager_type'], 'LOC'],
          ['==', ['get', 'manager_type'], 'DIST']
        ], c.outlineLOC,
        c.outlineDefault
      ],
      'line-opacity': [
        'case',
        ['==', ['get', 'agency'], 'NPS'], c.outlineOpacityNPS,
        ['==', ['get', 'agency'], 'USFS'], c.outlineOpacityUSFS,
        ['==', ['get', 'agency'], 'BLM'], c.outlineOpacityDefault,
        c.outlineOpacityDefault
      ],
      'line-width': [
        'interpolate', ['linear'], ['zoom'],
        4, c.outlineWidth.z4,
        8, c.outlineWidth.z8,
        12, c.outlineWidth.z12
      ],
    },
  }, beforeId)

  // Label layer — unit names at zoom 10+
  map.addLayer({
    id: PUBLIC_LANDS_LABEL,
    type: 'symbol',
    source: PUBLIC_LANDS_SOURCE,
    'source-layer': 'public_lands',
    minzoom: 10,
    layout: {
      'text-field': ['get', 'name'],
      'text-size': ['interpolate', ['linear'], ['zoom'], 10, c.labelSize.z10, 14, c.labelSize.z14],
      'text-font': c.labelFont,
      'symbol-placement': 'point',
      'text-anchor': 'center',
      'text-max-width': 8,
      'text-allow-overlap': false,
      'text-ignore-placement': false,
    },
    paint: {
      'text-color': c.labelColor,
      'text-halo-color': c.labelHaloColor,
      'text-halo-width': c.labelHaloWidth,
      'text-opacity': c.labelOpacity,
    },
  })
}

/** Remove public lands layers + source */
function removePublicLands(map) {
  if (!map) return
  if (map.getLayer(PUBLIC_LANDS_LABEL)) map.removeLayer(PUBLIC_LANDS_LABEL)
  if (map.getLayer(PUBLIC_LANDS_LINE)) map.removeLayer(PUBLIC_LANDS_LINE)
  if (map.getLayer(PUBLIC_LANDS_FILL)) map.removeLayer(PUBLIC_LANDS_FILL)
  if (map.getSource(PUBLIC_LANDS_SOURCE)) map.removeSource(PUBLIC_LANDS_SOURCE)
}

/** Add topographic contour vector tile overlay */
function addContours(map, themeId) {
  if (!map || map.getSource(CONTOUR_SOURCE)) return

  const c = getOverlayConfig(themeId, 'contours')

  map.addSource(CONTOUR_SOURCE, {
    type: 'vector',
    url: 'pmtiles:///tiles/contours-na.pmtiles',
  })

  // Insert below first symbol layer (above hillshade, below labels)
  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === 'symbol') {
      beforeId = layer.id
      break
    }
  }

  // Minor contours (40ft) — visible z11+
  map.addLayer({
    id: CONTOUR_MINOR,
    type: 'line',
    source: CONTOUR_SOURCE,
    'source-layer': 'contours',
    minzoom: 11,
    filter: ['==', ['get', 'tier'], 'minor'],
    paint: {
      'line-color': c.minorColor,
      'line-opacity': c.minorOpacity * c.opacityMod,
      'line-width': ['interpolate', ['linear'], ['zoom'], 11, c.minorWidth.z11, 14, c.minorWidth.z14],
    },
  }, beforeId)

  // Intermediate contours (200ft) — visible z8+
  map.addLayer({
    id: CONTOUR_INTERMEDIATE,
    type: 'line',
    source: CONTOUR_SOURCE,
    'source-layer': 'contours',
    minzoom: 8,
    filter: ['==', ['get', 'tier'], 'intermediate'],
    paint: {
      'line-color': c.intermediateColor,
      'line-opacity': c.intermediateOpacity * c.opacityMod,
      'line-width': ['interpolate', ['linear'], ['zoom'], 8, c.intermediateWidth.z8, 14, c.intermediateWidth.z14],
    },
  }, beforeId)

  // Index contours (1000ft) — visible z4+
  map.addLayer({
    id: CONTOUR_INDEX,
    type: 'line',
    source: CONTOUR_SOURCE,
    'source-layer': 'contours',
    minzoom: 4,
    filter: ['==', ['get', 'tier'], 'index'],
    paint: {
      'line-color': c.indexColor,
      'line-opacity': c.indexOpacity * c.opacityMod,
      'line-width': ['interpolate', ['linear'], ['zoom'], 4, c.indexWidth.z4, 14, c.indexWidth.z14],
    },
  }, beforeId)

  // Elevation labels on index contours (z12+)
  map.addLayer({
    id: CONTOUR_LABEL,
    type: 'symbol',
    source: CONTOUR_SOURCE,
    'source-layer': 'contours',
    minzoom: 12,
    filter: ['==', ['get', 'tier'], 'index'],
    layout: {
      'text-field': ['concat', ['to-string', ['get', 'elevation_ft']], "'"],
      'text-size': c.labelSize,
      'text-font': c.labelFont,
      'symbol-placement': 'line',
      'text-anchor': 'center',
      'symbol-spacing': 400,
      'text-max-angle': 30,
      'text-allow-overlap': false,
    },
    paint: {
      'text-color': c.labelColor,
      'text-halo-color': c.labelHaloColor,
      'text-halo-width': c.labelHaloWidth,
      'text-opacity': c.labelOpacity,
    },
  })
}

/** Remove contour layers + source */
function removeContours(map) {
  if (!map) return
  if (map.getLayer(CONTOUR_LABEL)) map.removeLayer(CONTOUR_LABEL)
  if (map.getLayer(CONTOUR_INDEX)) map.removeLayer(CONTOUR_INDEX)
  if (map.getLayer(CONTOUR_INTERMEDIATE)) map.removeLayer(CONTOUR_INTERMEDIATE)
  if (map.getLayer(CONTOUR_MINOR)) map.removeLayer(CONTOUR_MINOR)
  if (map.getSource(CONTOUR_SOURCE)) map.removeSource(CONTOUR_SOURCE)
}

/** Add TEST topographic contour overlay (blue color scheme) */
function addContoursTest(map, themeId) {
  if (!map || map.getSource(CONTOUR_TEST_SOURCE)) return

  const c = getOverlayConfig(themeId, 'contoursTest')

  map.addSource(CONTOUR_TEST_SOURCE, {
    type: "vector",
    url: "pmtiles:///tiles/contours-test.pmtiles",
  })

  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === "symbol") {
      beforeId = layer.id
      break
    }
  }

  // Minor contours (40ft) — blue scheme
  map.addLayer({
    id: CONTOUR_TEST_MINOR,
    type: "line",
    source: CONTOUR_TEST_SOURCE,
    "source-layer": "contours",
    minzoom: 11,
    filter: ["==", ["get", "tier"], "minor"],
    paint: {
      "line-color": c.minorColor,
      "line-opacity": c.minorOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 11, c.minorWidth.z11, 14, c.minorWidth.z14],
    },
  }, beforeId)

  // Intermediate contours (200ft)
  map.addLayer({
    id: CONTOUR_TEST_INTERMEDIATE,
    type: "line",
    source: CONTOUR_TEST_SOURCE,
    "source-layer": "contours",
    minzoom: 8,
    filter: ["==", ["get", "tier"], "intermediate"],
    paint: {
      "line-color": c.intermediateColor,
      "line-opacity": c.intermediateOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 8, c.intermediateWidth.z8, 14, c.intermediateWidth.z14],
    },
  }, beforeId)

  // Index contours (1000ft)
  map.addLayer({
    id: CONTOUR_TEST_INDEX,
    type: "line",
    source: CONTOUR_TEST_SOURCE,
    "source-layer": "contours",
    minzoom: 4,
    filter: ["==", ["get", "tier"], "index"],
    paint: {
      "line-color": c.indexColor,
      "line-opacity": c.indexOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 4, c.indexWidth.z4, 14, c.indexWidth.z14],
    },
  }, beforeId)

  // Labels
  map.addLayer({
    id: CONTOUR_TEST_LABEL,
    type: "symbol",
    source: CONTOUR_TEST_SOURCE,
    "source-layer": "contours",
    minzoom: 12,
    filter: ["==", ["get", "tier"], "index"],
    layout: {
      "text-field": ["concat", ["to-string", ["get", "elevation_ft"]], ""],
      "text-size": c.labelSize,
      "text-font": c.labelFont,
      "symbol-placement": "line",
      "text-anchor": "center",
      "symbol-spacing": 400,
      "text-max-angle": 30,
      "text-allow-overlap": false,
    },
    paint: {
      "text-color": c.labelColor,
      "text-halo-color": c.labelHaloColor,
      "text-halo-width": c.labelHaloWidth,
      "text-opacity": c.labelOpacity,
    },
  })
}

/** Remove TEST contour layers + source */
function removeContoursTest(map) {
  if (!map) return
  if (map.getLayer(CONTOUR_TEST_LABEL)) map.removeLayer(CONTOUR_TEST_LABEL)
  if (map.getLayer(CONTOUR_TEST_INDEX)) map.removeLayer(CONTOUR_TEST_INDEX)
  if (map.getLayer(CONTOUR_TEST_INTERMEDIATE)) map.removeLayer(CONTOUR_TEST_INTERMEDIATE)
  if (map.getLayer(CONTOUR_TEST_MINOR)) map.removeLayer(CONTOUR_TEST_MINOR)
  if (map.getSource(CONTOUR_TEST_SOURCE)) map.removeSource(CONTOUR_TEST_SOURCE)
}

/** Add TEST 10ft topographic contour overlay (green color scheme) */
function addContoursTest10ft(map, themeId) {
  if (!map || map.getSource(CONTOUR_TEST_10FT_SOURCE)) return

  const c = getOverlayConfig(themeId, 'contoursTest10ft')

  map.addSource(CONTOUR_TEST_10FT_SOURCE, {
    type: "vector",
    url: "pmtiles:///tiles/contours-test-10ft.pmtiles",
  })

  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === "symbol") {
      beforeId = layer.id
      break
    }
  }

  // Minor contours (10ft) — green scheme
  map.addLayer({
    id: CONTOUR_TEST_10FT_MINOR,
    type: "line",
    source: CONTOUR_TEST_10FT_SOURCE,
    "source-layer": "contours",
    minzoom: 11,
    filter: ["==", ["get", "tier"], "minor"],
    paint: {
      "line-color": c.minorColor,
      "line-opacity": c.minorOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 11, c.minorWidth.z11, 14, c.minorWidth.z14],
    },
  }, beforeId)

  // Intermediate contours (50ft) — green scheme
  map.addLayer({
    id: CONTOUR_TEST_10FT_INTERMEDIATE,
    type: "line",
    source: CONTOUR_TEST_10FT_SOURCE,
    "source-layer": "contours",
    minzoom: 8,
    filter: ["==", ["get", "tier"], "intermediate"],
    paint: {
      "line-color": c.intermediateColor,
      "line-opacity": c.intermediateOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 8, c.intermediateWidth.z8, 14, c.intermediateWidth.z14],
    },
  }, beforeId)

  // Index contours (250ft) — darker green
  map.addLayer({
    id: CONTOUR_TEST_10FT_INDEX,
    type: "line",
    source: CONTOUR_TEST_10FT_SOURCE,
    "source-layer": "contours",
    minzoom: 4,
    filter: ["==", ["get", "tier"], "index"],
    paint: {
      "line-color": c.indexColor,
      "line-opacity": c.indexOpacity * c.opacityMod,
      "line-width": ["interpolate", ["linear"], ["zoom"], 4, c.indexWidth.z4, 14, c.indexWidth.z14],
    },
  }, beforeId)

  // Elevation labels on index contours (z12+)
  map.addLayer({
    id: CONTOUR_TEST_10FT_LABEL,
    type: "symbol",
    source: CONTOUR_TEST_10FT_SOURCE,
    "source-layer": "contours",
    minzoom: 12,
    filter: ["==", ["get", "tier"], "index"],
    layout: {
      "text-field": ["concat", ["to-string", ["get", "elevation_ft"]], "'"],
      "text-size": c.labelSize,
      "text-font": c.labelFont,
      "symbol-placement": "line",
      "text-anchor": "center",
      "symbol-spacing": 400,
      "text-max-angle": 30,
      "text-allow-overlap": false,
    },
    paint: {
      "text-color": c.labelColor,
      "text-halo-color": c.labelHaloColor,
      "text-halo-width": c.labelHaloWidth,
      "text-opacity": c.labelOpacity,
    },
  })
}

/** Remove test 10ft contour layers + source */
function removeContoursTest10ft(map) {
  if (!map) return
  if (map.getLayer(CONTOUR_TEST_10FT_LABEL)) map.removeLayer(CONTOUR_TEST_10FT_LABEL)
  if (map.getLayer(CONTOUR_TEST_10FT_INDEX)) map.removeLayer(CONTOUR_TEST_10FT_INDEX)
  if (map.getLayer(CONTOUR_TEST_10FT_INTERMEDIATE)) map.removeLayer(CONTOUR_TEST_10FT_INTERMEDIATE)
  if (map.getLayer(CONTOUR_TEST_10FT_MINOR)) map.removeLayer(CONTOUR_TEST_10FT_MINOR)
  if (map.getSource(CONTOUR_TEST_10FT_SOURCE)) map.removeSource(CONTOUR_TEST_10FT_SOURCE)
}
/** Add USFS trails and roads vector tile overlay */
function addUsfsTrails(map, themeId) {
  if (!map || map.getSource(USFS_SOURCE)) return

  const c = getOverlayConfig(themeId, 'usfsTrails')

  map.addSource(USFS_SOURCE, {
    type: "vector",
    url: "pmtiles:///tiles/usfs-trails-roads.pmtiles",
  })

  // Insert below first symbol layer
  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === "symbol") {
      beforeId = layer.id
      break
    }
  }

  // Invisible hit-area layers for easier clicking
  map.addLayer({
    id: USFS_ROADS_HIT,
    type: "line",
    source: USFS_SOURCE,
    "source-layer": "roads",
    minzoom: 10,
    paint: {
      "line-color": "#000000",
      "line-opacity": 0,
      "line-width": c.hitWidth,
    },
  }, beforeId)

  map.addLayer({
    id: USFS_TRAILS_HIT,
    type: "line",
    source: USFS_SOURCE,
    "source-layer": "trails",
    minzoom: 10,
    paint: {
      "line-color": "#000000",
      "line-opacity": 0,
      "line-width": c.hitWidth,
    },
  }, beforeId)

  // Roads layer - solid amber/tan line
  map.addLayer({
    id: USFS_ROADS_LAYER,
    type: "line",
    source: USFS_SOURCE,
    "source-layer": "roads",
    minzoom: 10,
    paint: {
      "line-color": c.roadsColor,
      "line-opacity": c.roadsOpacity,
      "line-width": ["interpolate", ["linear"], ["zoom"], 10, c.roadsWidth.z10, 14, c.roadsWidth.z14, 16, c.roadsWidth.z16],
    },
  }, beforeId)

  // Trails layer - color by allowed use
  map.addLayer({
    id: USFS_TRAILS_LAYER,
    type: "line",
    source: USFS_SOURCE,
    "source-layer": "trails",
    minzoom: 10,
    paint: {
      "line-color": [
        "case",
        // Motorcycle/ATV trails - orange
        ["any",
          ["==", ["slice", ["get", "MOTORCYCLE"], 0, 1], "0"],
          ["==", ["slice", ["get", "ATV_MANAGE"], 0, 1], "0"]
        ], c.trailsMotorized,
        // Bike trails - amber
        ["==", ["slice", ["get", "BICYCLE_MA"], 0, 1], "0"],
        c.trailsBicycle,
        // Hiker/Horse only - green
        ["any",
          ["==", ["slice", ["get", "HIKER_PEDE"], 0, 1], "0"],
          ["==", ["slice", ["get", "HORSE_MANA"], 0, 1], "0"]
        ], c.trailsHiker,
        // Default - tan
        c.trailsDefault
      ],
      "line-opacity": c.trailsOpacity,
      "line-width": ["interpolate", ["linear"], ["zoom"], 10, c.trailsWidth.z10, 14, c.trailsWidth.z14, 16, c.trailsWidth.z16],
      "line-dasharray": c.trailsDash,
    },
  }, beforeId)

  // Road labels (zoom 12+)
  map.addLayer({
    id: USFS_ROADS_LABEL,
    type: "symbol",
    source: USFS_SOURCE,
    "source-layer": "roads",
    minzoom: 12,
    filter: ["has", "NAME"],
    layout: {
      "text-field": ["get", "NAME"],
      "text-size": c.roadsLabelSize,
      "text-font": c.labelFont,
      "symbol-placement": "line",
      "text-anchor": "center",
      "symbol-spacing": 300,
      "text-max-angle": 25,
      "text-allow-overlap": false,
    },
    paint: {
      "text-color": c.roadsLabelColor,
      "text-halo-color": c.roadsLabelHaloColor,
      "text-halo-width": c.roadsLabelHaloWidth,
      "text-opacity": c.roadsLabelOpacity,
    },
  })

  // Trail labels (zoom 12+)
  map.addLayer({
    id: USFS_TRAILS_LABEL,
    type: "symbol",
    source: USFS_SOURCE,
    "source-layer": "trails",
    minzoom: 12,
    filter: ["has", "TRAIL_NAME"],
    layout: {
      "text-field": ["get", "TRAIL_NAME"],
      "text-size": c.trailsLabelSize,
      "text-font": c.labelFont,
      "symbol-placement": "line",
      "text-anchor": "center",
      "symbol-spacing": 300,
      "text-max-angle": 25,
      "text-allow-overlap": false,
    },
    paint: {
      "text-color": c.trailsLabelColor,
      "text-halo-color": c.trailsLabelHaloColor,
      "text-halo-width": c.trailsLabelHaloWidth,
      "text-opacity": c.trailsLabelOpacity,
    },
  })

  // Cursor pointer on hover
  ;[USFS_TRAILS_HIT, USFS_ROADS_HIT].forEach(layerId => {
    map.on("mouseenter", layerId, () => {
      map.getCanvas().style.cursor = "pointer"
    })
    map.on("mouseleave", layerId, () => {
      map.getCanvas().style.cursor = ""
    })
  })
}
function removeUsfsTrails(map) {
  if (!map) return
  if (map.getLayer(USFS_TRAILS_LABEL)) map.removeLayer(USFS_TRAILS_LABEL)
  if (map.getLayer(USFS_ROADS_LABEL)) map.removeLayer(USFS_ROADS_LABEL)
  if (map.getLayer(USFS_TRAILS_LAYER)) map.removeLayer(USFS_TRAILS_LAYER)
  if (map.getLayer(USFS_ROADS_LAYER)) map.removeLayer(USFS_ROADS_LAYER)
  if (map.getLayer(USFS_TRAILS_HIT)) map.removeLayer(USFS_TRAILS_HIT)
  if (map.getLayer(USFS_ROADS_HIT)) map.removeLayer(USFS_ROADS_HIT)
  if (map.getSource(USFS_SOURCE)) map.removeSource(USFS_SOURCE)
}
/** Add BLM trails/roads vector tile overlay with surface-type styling */
function addBlmTrails(map, themeId) {
  if (!map || map.getSource(BLM_SOURCE)) return

  const c = getOverlayConfig(themeId, 'blmTrails')

  map.addSource(BLM_SOURCE, {
    type: "vector",
    url: "pmtiles:///tiles/blm-trails-roads.pmtiles",
  })

  // Insert below first symbol layer
  let beforeId = undefined
  for (const layer of map.getStyle().layers) {
    if (layer.type === "symbol") {
      beforeId = layer.id
      break
    }
  }

  // Color expression based on route use class
  const colorExpr = [
    "case",
    ["any",
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4WD HIGH CLEARANCE / SPECIALIZED"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4WD High Clearance/Specialized"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4wd High Clearance / Specialized"]
    ], c.color4wdHigh,
    ["any",
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4WD LOW"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4WD Low"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "4wd Low"]
    ], c.color4wdLow,
    ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "ATV"],
    c.colorAtv,
    ["any",
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "MOTORIZED SINGLE TRACK"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "Motorized Single Track"]
    ], c.colorMotoSingle,
    ["any",
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "2WD LOW"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "2WD Low"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "2wd Low"]
    ], c.color2wdLow,
    ["any",
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "NON-MECHANIZED"],
      ["==", ["get", "OBSRVE_ROUTE_USE_CLASS"], "Non-Mechanized"]
    ], c.colorNonMech,
    c.colorDefault
  ]

  const lineWidth = ["interpolate", ["linear"], ["zoom"], 10, c.lineWidth.z10, 14, c.lineWidth.z14, 16, c.lineWidth.z16]

  // Filter out paved, arterial, collector, local, and highways
  const excludeUrban = [
    "all",
    // Exclude paved
    ["!=", ["get", "OBSRVE_SRFCE_TYPE"], "SOLID SURFACE"],
    ["!=", ["get", "OBSRVE_SRFCE_TYPE"], "Solid Surface"],
    // Exclude arterial roads
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "ARTERIAL"],
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "Arterial"],
    // Exclude collector roads
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "COLLECTOR"],
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "Collector"],
    // Exclude local roads
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "LOCAL"],
    ["!=", ["get", "OBSRVE_FUNC_CLASS"], "Local"],
    // Exclude designated highways
    ["!", ["has", "HWY_CLASS"]]
  ]

  // Invisible hit-area layer for clicking
  map.addLayer({
    id: BLM_ROUTES_HIT,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: excludeUrban,
    paint: {
      "line-color": "#000000",
      "line-opacity": 0,
      "line-width": c.hitWidth,
    },
  }, beforeId)

  // NATURAL surface - solid line
  map.addLayer({
    id: BLM_ROUTES_NATURAL,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: ["all", excludeUrban,
      ["any",
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "NATURAL"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Natural"]
      ]
    ],
    paint: {
      "line-color": colorExpr,
      "line-opacity": c.lineOpacity,
      "line-width": lineWidth,
    },
  }, beforeId)

  // NATURAL IMPROVED surface - dashed
  map.addLayer({
    id: BLM_ROUTES_IMPROVED,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: ["all", excludeUrban,
      ["any",
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "NATURAL IMPROVED"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Natural Improved"]
      ]
    ],
    paint: {
      "line-color": colorExpr,
      "line-opacity": c.lineOpacity,
      "line-width": lineWidth,
      "line-dasharray": c.dashImproved,
    },
  }, beforeId)

  // AGGREGATE surface - dotted
  map.addLayer({
    id: BLM_ROUTES_AGGREGATE,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: ["all", excludeUrban,
      ["any",
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "AGGREGATE"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Aggregate"]
      ]
    ],
    paint: {
      "line-color": colorExpr,
      "line-opacity": c.lineOpacity,
      "line-width": lineWidth,
      "line-dasharray": c.dashAggregate,
    },
  }, beforeId)

  // SNOW surface - dash-dot, blue
  map.addLayer({
    id: BLM_ROUTES_SNOW,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: ["all", excludeUrban,
      ["any",
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "SNOW"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Snow"]
      ]
    ],
    paint: {
      "line-color": c.colorSnow,
      "line-opacity": c.lineOpacity,
      "line-width": lineWidth,
      "line-dasharray": c.dashSnow,
    },
  }, beforeId)

  // OTHER/UNKNOWN surface - dash-dot-dot
  map.addLayer({
    id: BLM_ROUTES_OTHER,
    type: "line",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 10,
    filter: ["all", excludeUrban,
      ["!", ["any",
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "NATURAL"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Natural"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "NATURAL IMPROVED"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Natural Improved"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "AGGREGATE"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Aggregate"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "SNOW"],
        ["==", ["get", "OBSRVE_SRFCE_TYPE"], "Snow"]
      ]]
    ],
    paint: {
      "line-color": colorExpr,
      "line-opacity": c.lineOpacityOther,
      "line-width": lineWidth,
      "line-dasharray": c.dashOther,
    },
  }, beforeId)

  // Route labels (zoom 12+)
  map.addLayer({
    id: BLM_ROUTES_LABEL,
    type: "symbol",
    source: BLM_SOURCE,
    "source-layer": "blm_routes",
    minzoom: 12,
    filter: ["all", excludeUrban, ["has", "ROUTE_PRMRY_NM"]],
    layout: {
      "text-field": ["get", "ROUTE_PRMRY_NM"],
      "text-size": c.labelSize,
      "text-font": c.labelFont,
      "symbol-placement": "line",
      "text-anchor": "center",
      "symbol-spacing": 300,
      "text-max-angle": 25,
      "text-allow-overlap": false,
    },
    paint: {
      "text-color": c.labelColor,
      "text-halo-color": c.labelHaloColor,
      "text-halo-width": c.labelHaloWidth,
      "text-opacity": c.labelOpacity,
    },
  })

  // Cursor pointer on hover
  map.on("mouseenter", BLM_ROUTES_HIT, () => {
    map.getCanvas().style.cursor = "pointer"
  })
  map.on("mouseleave", BLM_ROUTES_HIT, () => {
    map.getCanvas().style.cursor = ""
  })
}

/** Remove BLM trails/roads layers and source */
function removeBlmTrails(map) {
  if (!map) return
  if (map.getLayer(BLM_ROUTES_LABEL)) map.removeLayer(BLM_ROUTES_LABEL)
  if (map.getLayer(BLM_ROUTES_OTHER)) map.removeLayer(BLM_ROUTES_OTHER)
  if (map.getLayer(BLM_ROUTES_SNOW)) map.removeLayer(BLM_ROUTES_SNOW)
  if (map.getLayer(BLM_ROUTES_AGGREGATE)) map.removeLayer(BLM_ROUTES_AGGREGATE)
  if (map.getLayer(BLM_ROUTES_IMPROVED)) map.removeLayer(BLM_ROUTES_IMPROVED)
  if (map.getLayer(BLM_ROUTES_NATURAL)) map.removeLayer(BLM_ROUTES_NATURAL)
  if (map.getLayer(BLM_ROUTES_HIT)) map.removeLayer(BLM_ROUTES_HIT)
  if (map.getSource(BLM_SOURCE)) map.removeSource(BLM_SOURCE)
}


/** Add boundary polygon layers with computed accent color (MapLibre rejects CSS vars in paint) */
const BOUNDARY_FILL_LAYER = 'boundary-fill-layer'

function addBoundaryLayer(map) {
  if (!map || map.getLayer(BOUNDARY_LAYER)) return
  if (!map.getSource(BOUNDARY_SOURCE)) {
    map.addSource(BOUNDARY_SOURCE, {
      type: "geojson",
      data: { type: "FeatureCollection", features: [] },
    })
  }
  const accentColor = getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() || "#7a9a6b"

  // Find first symbol layer to insert boundary layers below labels
  const layers = map.getStyle().layers
  let firstSymbolId = null
  for (const layer of layers) {
    if (layer.type === 'symbol') {
      firstSymbolId = layer.id
      break
    }
  }

  // Add subtle fill layer (barely visible tint)
  map.addLayer({
    id: BOUNDARY_FILL_LAYER,
    type: "fill",
    source: BOUNDARY_SOURCE,
    paint: {
      "fill-color": accentColor,
      "fill-opacity": 0.05,
    },
  }, firstSymbolId)

  // Add dashed outline layer
  map.addLayer({
    id: BOUNDARY_LAYER,
    type: "line",
    source: BOUNDARY_SOURCE,
    paint: {
      "line-color": accentColor,
      "line-width": 2,
      "line-opacity": 0.7,
      "line-dasharray": [3, 2],
    },
  }, firstSymbolId)
}

const MapView = forwardRef(function MapView(_, ref) {
  const mapRef = useRef(null)
  const mapInstance = useRef(null)
  const markersRef = useRef([])
  const popupRef = useRef(null)
  const gpsMarkerRef = useRef(null)
  const previewMarkerRef = useRef(null)
  const watchIdRef = useRef(null)
  const currentThemeRef = useRef('dark')
  // Track which overlay layers are currently active (for theme swap re-add)
  const activeLayersRef = useRef({ hillshade: false, traffic: false, contours: false, contoursTest: false, contoursTest10ft: false, usfsTrails: false, blmTrails: false })
  // Flag to suppress map-click when a stop pin was clicked
  const pinClickedRef = useRef(false)
  const highlightedFeatureRef = useRef(null) // { source, sourceLayer, id } for setFeatureState
  const hoveredFeatureRef = useRef(null) // for hover highlight
  const updateBoundaryRef = useRef(null) // boundary update function
  // Refs for measurement state (accessible in click handlers)
  const measuringRef = useRef({ active: false, points: [] })
  const measureLabelsRef = useRef([]) // HTML label elements

  const stops = useStore((s) => s.stops)
  const route = useStore((s) => s.route)
  const theme = useStore((s) => s.theme)
  const selectedPlace = useStore((s) => s.selectedPlace)
  const clickMarker = useStore((s) => s.clickMarker)
  const setClickMarker = useStore((s) => s.setClickMarker)
  const clearClickMarker = useStore((s) => s.clearClickMarker)
  const gpsOrigin = useStore((s) => s.gpsOrigin)
  const geoPermission = useStore((s) => s.geoPermission)
  const setSheetState = useStore((s) => s.setSheetState)
  const setMapCenter = useStore((s) => s.setMapCenter)
  const pickingLocationFor = useStore((s) => s.pickingLocationFor)
  const setEditingContact = useStore((s) => s.setEditingContact)
  const clearPickingLocationFor = useStore((s) => s.clearPickingLocationFor)

  // Zoom level indicator state
  const [zoomLevel, setZoomLevel] = useState(10)

  // Radial menu state
  const [radialMenu, setRadialMenu] = useState({
    open: false,
    x: 0,
    y: 0,
    lat: 0,
    lon: 0,
    centerLabel: null,
  })
  // Measurement mode state (for UI rendering)
  const [measuring, setMeasuring] = useState({ active: false, points: [], totalMeters: 0 })

  // Sync state to ref for click handler access
  const updateMeasuringState = (newState) => {
    measuringRef.current = newState
    setMeasuring(newState)
  }

  // Update measurement layer with current points
  const updateMeasureLayer = (points) => {
    const map = mapInstance.current
    if (!map || !map.getSource(MEASURE_SOURCE)) return

    const features = []
    // Add points
    points.forEach((p, i) => {
      features.push({
        type: "Feature",
        geometry: { type: "Point", coordinates: [p.lon, p.lat] },
        properties: { index: i },
      })
    })
    // Add line if more than one point
    if (points.length > 1) {
      features.push({
        type: "Feature",
        geometry: {
          type: "LineString",
          coordinates: points.map((p) => [p.lon, p.lat]),
        },
        properties: {},
      })
    }
    map.getSource(MEASURE_SOURCE).setData({
      type: "FeatureCollection",
      features,
    })
  }

  // Update segment labels (HTML overlays)
  const updateMeasureLabels = (points) => {
    const map = mapInstance.current
    if (!map) return

    // Remove old labels
    measureLabelsRef.current.forEach(el => el.remove())
    measureLabelsRef.current = []

    if (points.length < 2) return

    const container = mapRef.current
    if (!container) return

    // Create label for each segment
    for (let i = 1; i < points.length; i++) {
      const p1 = points[i - 1]
      const p2 = points[i]
      const midLat = (p1.lat + p2.lat) / 2
      const midLon = (p1.lon + p2.lon) / 2
      const dist = haversineDistance(p1.lat, p1.lon, p2.lat, p2.lon)

      const label = document.createElement('div')
      label.className = 'measure-label'
      label.textContent = formatDistance(dist)
      label.style.cssText = `
        position: absolute;
        background: rgba(0, 0, 0, 0.75);
        color: white;
        padding: 2px 6px;
        border-radius: 10px;
        font-size: 11px;
        font-weight: 500;
        pointer-events: none;
        white-space: nowrap;
        z-index: 100;
        transform: translate(-50%, -50%);
      `

      const pos = map.project([midLon, midLat])
      label.style.left = pos.x + 'px'
      label.style.top = pos.y + 'px'

      container.appendChild(label)
      measureLabelsRef.current.push(label)
    }
  }

  // Reposition labels on map move/zoom
  const repositionLabels = () => {
    const map = mapInstance.current
    const points = measuringRef.current.points
    if (!map || points.length < 2) return

    measureLabelsRef.current.forEach((label, i) => {
      if (i >= points.length - 1) return
      const p1 = points[i]
      const p2 = points[i + 1]
      const midLat = (p1.lat + p2.lat) / 2
      const midLon = (p1.lon + p2.lon) / 2
      const pos = map.project([midLon, midLat])
      label.style.left = pos.x + 'px'
      label.style.top = pos.y + 'px'
    })
  }

  // Clear measurement mode completely
  const clearMeasuring = () => {
    const map = mapInstance.current
    updateMeasuringState({ active: false, points: [], totalMeters: 0 })

    // Remove labels
    measureLabelsRef.current.forEach(el => el.remove())
    measureLabelsRef.current = []

    if (map) {
      map.getCanvas().style.cursor = ""
      map.doubleClickZoom.enable()
      if (map.getLayer(MEASURE_LINE_LAYER)) map.removeLayer(MEASURE_LINE_LAYER)
      if (map.getLayer(MEASURE_POINT_LAYER)) map.removeLayer(MEASURE_POINT_LAYER)
      if (map.getSource(MEASURE_SOURCE)) map.removeSource(MEASURE_SOURCE)
    }
  }

  // End measurement (keep line visible, exit active mode)
  const endMeasuring = () => {
    const map = mapInstance.current
    if (map) {
      map.getCanvas().style.cursor = ""
      map.doubleClickZoom.enable()
    }
    updateMeasuringState({ ...measuringRef.current, active: false })
  }

  // Start new measurement
  const startMeasuring = (lat, lon) => {
    const map = mapInstance.current
    if (!map) return

    // Clear any existing measurement first
    measureLabelsRef.current.forEach(el => el.remove())
    measureLabelsRef.current = []
    if (map.getLayer(MEASURE_LINE_LAYER)) map.removeLayer(MEASURE_LINE_LAYER)
    if (map.getLayer(MEASURE_POINT_LAYER)) map.removeLayer(MEASURE_POINT_LAYER)
    if (map.getSource(MEASURE_SOURCE)) map.removeSource(MEASURE_SOURCE)

    // Set up new measurement
    updateMeasuringState({ active: true, points: [{ lat, lon }], totalMeters: 0 })
    map.getCanvas().style.cursor = "crosshair"
    map.doubleClickZoom.disable()

    // Add source and layers
    map.addSource(MEASURE_SOURCE, {
      type: "geojson",
      data: { type: "FeatureCollection", features: [] },
    })
    const accentColor = getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() || "#7a9a6b"
    map.addLayer({
      id: MEASURE_LINE_LAYER,
      type: "line",
      source: MEASURE_SOURCE,
      paint: {
        "line-color": accentColor,
        "line-width": 2,
        "line-dasharray": [8, 4],
      },
    })
    map.addLayer({
      id: MEASURE_POINT_LAYER,
      type: "circle",
      source: MEASURE_SOURCE,
      filter: ["==", "$type", "Point"],
      paint: {
        "circle-radius": 5,
        "circle-color": accentColor,
        "circle-stroke-width": 2,
        "circle-stroke-color": "#1a1a1a",
      },
    })
    updateMeasureLayer([{ lat, lon }])
  }

  // Add a point to the measurement
  const addMeasurePoint = (lat, lon) => {
    const current = measuringRef.current
    if (!current.active) return

    const newPoints = [...current.points, { lat, lon }]

    // Calculate total distance
    let totalMeters = 0
    for (let i = 1; i < newPoints.length; i++) {
      totalMeters += haversineDistance(
        newPoints[i - 1].lat, newPoints[i - 1].lon,
        newPoints[i].lat, newPoints[i].lon
      )
    }

    updateMeasuringState({ active: true, points: newPoints, totalMeters })
    updateMeasureLayer(newPoints)
    updateMeasureLabels(newPoints)
  }

  const radialWedges = [
    {
      id: "directions-to",
      label: "To here",
      icon: ArrowDownLeft,
      onSelect: () => {
        setRadialMenu((m) => ({ ...m, open: false }))
        const place = {
          lat: radialMenu.lat,
          lon: radialMenu.lon,
          name: radialMenu.centerLabel || radialMenu.lat.toFixed(5) + ", " + radialMenu.lon.toFixed(5),
          source: "radial_menu",
          matchCode: null,
        }
        useStore.getState().startDirections(place)
      },
    },
    {
      id: "directions-from",
      label: "From here",
      icon: ArrowUpRight,
      onSelect: () => {
        setRadialMenu((m) => ({ ...m, open: false }))
        const { clearStops, addStop } = useStore.getState()
        clearStops()
        const place = {
          lat: radialMenu.lat,
          lon: radialMenu.lon,
          name: radialMenu.centerLabel || radialMenu.lat.toFixed(5) + ", " + radialMenu.lon.toFixed(5),
          source: "radial_menu",
          matchCode: null,
        }
        addStop(place)
        useStore.setState({ gpsOrigin: false })
      },
    },
    {
      id: "add-stop",
      label: "Add stop",
      icon: Plus,
      onSelect: () => {
        setRadialMenu((m) => ({ ...m, open: false }))
        const { stops, addStop, clearStops } = useStore.getState()
        const place = {
          lat: radialMenu.lat,
          lon: radialMenu.lon,
          name: radialMenu.centerLabel || radialMenu.lat.toFixed(5) + ", " + radialMenu.lon.toFixed(5),
          source: "radial_menu",
          matchCode: null,
        }
        if (stops.length === 0) {
          addStop(place)
          useStore.setState({ gpsOrigin: false })
        } else {
          const success = addStop(place)
          if (!success) {
            toast("Maximum 10 stops reached")
          }
        }
      },
    },
    {
      id: "save-place",
      label: "Save",
      icon: Star,
      requiresAuth: true,
      onSelect: () => {
        setRadialMenu((m) => ({ ...m, open: false }))
        const { auth, setEditingContact } = useStore.getState()
        if (auth.authenticated) {
          setEditingContact({
            label: "",
            lat: radialMenu.lat,
            lon: radialMenu.lon,
          })
        } else {
          toast("Log in to save places")
        }
      },
    },
    {
      id: "measure",
      label: "Measure",
      icon: Ruler,
      onSelect: () => {
        setRadialMenu((m) => ({ ...m, open: false }))
        startMeasuring(radialMenu.lat, radialMenu.lon)
      },
    },
  ]
  // Context menu trigger handler
  const handleContextMenuTrigger = ({ x, y }) => {
    const map = mapInstance.current
    if (!map || !mapRef.current) return

    // Suppress context menu during measurement mode
    if (measuringRef.current.active) return

    // Convert screen coords to lat/lon
    const rect = mapRef.current.getBoundingClientRect()
    const lngLat = map.unproject([x - rect.left, y - rect.top])

    setRadialMenu({
      open: true,
      x,
      y,
      lat: lngLat.lat,
      lon: lngLat.lng,
      centerLabel: null,
    })

    // Async reverse geocode for center label
    fetchReverse(lngLat.lat, lngLat.lng).then((place) => {
      if (place) {
        setRadialMenu((m) => {
          if (m.open && Math.abs(m.lat - lngLat.lat) < 0.00001) {
            return { ...m, centerLabel: place.name }
          }
          return m
        })
      }
    })
  }

  // Context menu hook
  const contextMenuHandlers = useContextMenu(handleContextMenuTrigger)

    useImperativeHandle(ref, () => ({
    flyTo(lat, lon, zoom = 14) {
      mapInstance.current?.flyTo({ center: [lon, lat], zoom })
    },
    getMap() {
      return mapInstance.current
    },
    addHillshadeLayer() {
      const map = mapInstance.current
      if (!map) return
      addHillshade(map, currentThemeRef.current)
      activeLayersRef.current.hillshade = true
    },
    removeHillshadeLayer() {
      const map = mapInstance.current
      if (!map) return
      removeHillshade(map)
      activeLayersRef.current.hillshade = false
    },
    addTrafficLayer() {
      const map = mapInstance.current
      if (!map) return
      addTraffic(map, currentThemeRef.current)
      activeLayersRef.current.traffic = true
    },
    removeTrafficLayer() {
      const map = mapInstance.current
      if (!map) return
      removeTraffic(map)
      activeLayersRef.current.traffic = false
    },
    addPublicLandsLayer() {
      const map = mapInstance.current
      if (!map) return
      addPublicLands(map, currentThemeRef.current)
      activeLayersRef.current.publicLands = true
    },
    removePublicLandsLayer() {
      const map = mapInstance.current
      if (!map) return
      removePublicLands(map)
      activeLayersRef.current.publicLands = false
    },
    addContoursLayer() {
      const map = mapInstance.current
      if (!map) return
      addContours(map, currentThemeRef.current)
      activeLayersRef.current.contours = true
    },
    removeContoursLayer() {
      const map = mapInstance.current
      if (!map) return
      removeContours(map)
      activeLayersRef.current.contours = false
    },
    addContoursTestLayer() {
      const map = mapInstance.current
      if (!map) return
      addContoursTest(map, currentThemeRef.current)
      activeLayersRef.current.contoursTest = true
    },
    removeContoursTestLayer() {
      const map = mapInstance.current
      if (!map) return
      removeContoursTest(map)
      activeLayersRef.current.contoursTest = false
    },
    addContoursTest10ftLayer() {
      const map = mapInstance.current
      if (!map) return
      addContoursTest10ft(map, currentThemeRef.current)
      activeLayersRef.current.contoursTest10ft = true
    },
    removeContoursTest10ftLayer() {
      const map = mapInstance.current
      if (!map) return
      removeContoursTest10ft(map)
      activeLayersRef.current.contoursTest10ft = false
    },
    addUsfsTrailsLayer() {
      const map = mapInstance.current
      if (!map) return
      addUsfsTrails(map, currentThemeRef.current)
      activeLayersRef.current.usfsTrails = true
    },
    removeUsfsTrailsLayer() {
      const map = mapInstance.current
      if (!map) return
      removeUsfsTrails(map)
      activeLayersRef.current.usfsTrails = false
    },
    addBlmTrailsLayer() {
      const map = mapInstance.current
      if (!map) return
      addBlmTrails(map, currentThemeRef.current)
      activeLayersRef.current.blmTrails = true
    },
    removeBlmTrailsLayer() {
      const map = mapInstance.current
      if (!map) return
      removeBlmTrails(map)
      activeLayersRef.current.blmTrails = false
    },

  }))

  // Initialize map
  useEffect(() => {
    const protocol = new Protocol()
    maplibregl.addProtocol('pmtiles', protocol.tile)

    const config = getConfig()
    const DEFAULT_CENTER = config?.defaults?.center
      ? [config.defaults.center[1], config.defaults.center[0]]  // config is [lat,lon], MapLibre wants [lon,lat]
      : [-114.6066, 42.5736]
    const DEFAULT_ZOOM = config?.defaults?.zoom || 10

    const initialTheme = document.documentElement.getAttribute('data-theme') || 'dark'
    currentThemeRef.current = initialTheme

    const map = new maplibregl.Map({
      container: mapRef.current,
      style: buildStyle(initialTheme),
      center: DEFAULT_CENTER,
      zoom: DEFAULT_ZOOM,
    })

    map.addControl(new maplibregl.NavigationControl(), 'top-right')

    // Scale bar control
    map.addControl(new maplibregl.ScaleControl({
      maxWidth: 120,
      unit: 'imperial',
    }), 'bottom-right')

    // Map click — two-click selection model
    map.on('click', (e) => {
      // If a stop pin was just clicked, skip
      if (pinClickedRef.current) {
        pinClickedRef.current = false
        return
      }

      // CRITICAL: Check measuring mode FIRST using ref (not stale closure)
      if (measuringRef.current.active) {
        const { lng, lat } = e.lngLat
        addMeasurePoint(lat, lng)
        return
      }

      // Handle location pick mode for contacts
      const pickState = useStore.getState().pickingLocationFor
      if (pickState) {
        const { lng, lat } = e.lngLat
        map.getCanvas().style.cursor = ''
        // Reverse geocode for address
        fetchReverse(lat, lng).then((place) => {
          const addr = place?.address || place?.name || ''
          // Rebuild form data with new location
          useStore.getState().setEditingContact({
            ...pickState,
            lat,
            lon: lng,
            address: addr || pickState.address || '',
          })
          useStore.getState().clearPickingLocationFor()
        }).catch(() => {
          // Even if reverse geocode fails, set the location
          useStore.getState().setEditingContact({
            ...pickState,
            lat,
            lon: lng,
          })
          useStore.getState().clearPickingLocationFor()
        })
        return
      }



      const store = useStore.getState()
      const marker = store.clickMarker

      if (marker) {
        // State B: marker present — check if click is inside the circle
        const markerScreen = map.project([marker.lon, marker.lat])
        const dx = e.point.x - markerScreen.x
        const dy = e.point.y - markerScreen.y
        const dist = Math.sqrt(dx * dx + dy * dy)

        if (dist <= marker.circleRadiusPx) {
          // Inside circle → open radial at marker location
          const rect = mapRef.current?.getBoundingClientRect()
          const screenX = rect ? markerScreen.x + rect.left : markerScreen.x
          const screenY = rect ? markerScreen.y + rect.top : markerScreen.y

          setRadialMenu({
            open: true,
            x: screenX,
            y: screenY,
            lat: marker.lat,
            lon: marker.lon,
            centerLabel: store.selectedPlace?.name || null,
          })

          // Fetch reverse geocode for center label if not already loaded
          if (!store.selectedPlace?.name || store.selectedPlace.name === 'Dropped pin') {
            fetchReverse(marker.lat, marker.lon).then((place) => {
              if (place) {
                setRadialMenu((m) => {
                  if (m.open && Math.abs(m.lat - marker.lat) < 0.00001) {
                    return { ...m, centerLabel: place.name }
                  }
                  return m
                })
              }
            })
          }
        } else {
          // Outside circle → deselect, no new selection
          store.clearClickMarker()
          store.clearSelectedPlace()
          // Clear boundary when deselecting
          if (updateBoundaryRef.current) updateBoundaryRef.current(null)
          setSelectedHighlight(map, null)
        }
      } else {
        // State A: nothing selected → select
        if (window.innerWidth < 768) setSheetState('collapsed')

        const { lng, lat } = e.lngLat
        const MARKER_RADIUS_PX = 14 // half of 28px preview marker

        // Check for USFS trails/roads click (show info popup)
        const usfsLayers = [USFS_TRAILS_HIT, USFS_ROADS_HIT].filter(id => map.getLayer(id))
        const usfsFeatures = usfsLayers.length > 0
          ? map.queryRenderedFeatures(e.point, { layers: usfsLayers })
          : []
        const usfsFeature = usfsFeatures.find(f => f.properties)
        if (usfsFeature && hasFeature('has_usfs_trails')) {
          const props = usfsFeature.properties
          const isTrail = usfsFeature.layer?.id === USFS_TRAILS_HIT
          const name = isTrail ? (props.TRAIL_NAME || 'Unnamed Trail') : (props.NAME || 'Unnamed Road')
          const typeLabel = isTrail ? 'USFS Trail' : 'USFS Road'

          // Build popup content
          let html = '<div style="font-size:12px;max-width:240px;line-height:1.4">'
          html += '<strong style="font-size:13px">' + name + '</strong>'
          html += '<div style="color:var(--text-secondary);font-size:11px;margin-bottom:4px">' + typeLabel + '</div>'

          if (isTrail) {
            // Trail-specific info
            if (props.TRAIL_TYPE) html += '<div><b>Type:</b> ' + props.TRAIL_TYPE + '</div>'
            if (props.TRAIL_SURF) html += '<div><b>Surface:</b> ' + props.TRAIL_SURF + '</div>'
            if (props.GIS_MILES) html += '<div><b>Length:</b> ' + parseFloat(props.GIS_MILES).toFixed(1) + ' mi</div>'
            // Allowed uses
            const uses = []
            if (props.HIKER_PEDE === 'Y') uses.push('Hiking')
            if (props.BICYCLE_MA === 'Y') uses.push('Biking')
            if (props.MOTORCYCLE === 'Y') uses.push('Motorcycle')
            if (props.ATV_MANAGE === 'Y') uses.push('ATV')
            if (props.HORSE_MANA === 'Y') uses.push('Horse')
            if (uses.length > 0) html += '<div><b>Allowed:</b> ' + uses.join(', ') + '</div>'
          } else {
            // Road-specific info
            if (props.OPER_MAINT) html += '<div><b>Maintenance:</b> ' + props.OPER_MAINT + '</div>'
            if (props.SURFACE_TY) html += '<div><b>Surface:</b> ' + props.SURFACE_TY + '</div>'
            if (props.ROUTE_STAT) html += '<div><b>Status:</b> ' + props.ROUTE_STAT + '</div>'
          }
          html += '</div>'

          // Remove existing popup
          if (popupRef.current) popupRef.current.remove()

          const popup = new maplibregl.Popup({ offset: 10, closeButton: true })
            .setLngLat([lng, lat])
            .setHTML(html)
            .addTo(map)
          popupRef.current = popup
          return
        }

        // Check for BLM routes click (show info popup)
        const blmLayers = [BLM_ROUTES_HIT].filter(id => map.getLayer(id))
        const blmFeatures = blmLayers.length > 0
          ? map.queryRenderedFeatures(e.point, { layers: blmLayers })
          : []
        const blmFeature = blmFeatures.find(f => f.properties)
        if (blmFeature && hasFeature("has_blm_trails")) {
          const props = blmFeature.properties
          const name = props.ROUTE_PRMRY_NM || "Unnamed Route"

          // Build popup content
          let html = "<div style=\"font-size:12px;max-width:240px;line-height:1.4\">"
          html += "<strong style=\"font-size:13px\">" + name + "</strong>"
          html += "<div style=\"color:var(--text-secondary);font-size:11px;margin-bottom:4px\">BLM Route</div>"

          // Route info - handle potential field name variations across states
          if (props.PLAN_ASSET_CLASS) html += "<div><b>Asset Class:</b> " + props.PLAN_ASSET_CLASS + "</div>"
          if (props.PLAN_MODE_TRNSPRT) html += "<div><b>Transport:</b> " + props.PLAN_MODE_TRNSPRT + "</div>"
          if (props.OBSRVE_SRFCE_TYPE) html += "<div><b>Surface:</b> " + props.OBSRVE_SRFCE_TYPE + "</div>"
          if (props.GIS_MILES) html += "<div><b>Length:</b> " + parseFloat(props.GIS_MILES).toFixed(1) + " mi</div>"
          html += "</div>"

          // Remove existing popup
          if (popupRef.current) popupRef.current.remove()

          const popup = new maplibregl.Popup({ offset: 10, closeButton: true })
            .setLngLat([lng, lat])
            .setHTML(html)
            .addTo(map)
          popupRef.current = popup
          return
        }



        // Query rendered features at click point (label/POI priority)
        const labelLayers = ['pois', 'places_subplace', 'places_locality', 'places_region', 'places_country']
        const features = map.queryRenderedFeatures(e.point, { layers: labelLayers })

        // Find first feature with a name (respects layer order = priority)
        const labelFeature = features.find(f => f.properties?.name)

        // Clear previous feature highlight and boundary
          if (highlightedFeatureRef.current) {
            const { source, sourceLayer, id } = highlightedFeatureRef.current
            try {
              map.setFeatureState({ source, sourceLayer, id }, { selected: false })
            } catch (e) { /* ignore if layer removed */ }
            highlightedFeatureRef.current = null
          }
          setSelectedHighlight(map, null)
          // Note: do not clear boundary here - new data replaces old when API returns

          if (labelFeature) {
          // Clicked a labeled feature — snap to geometry and highlight
          const props = labelFeature.properties
          const geom = labelFeature.geometry

          // Get feature coordinates (Point geometry)
          let featureLat = lat
          let featureLon = lng
          if (geom && geom.type === 'Point' && geom.coordinates) {
            featureLon = geom.coordinates[0]
            featureLat = geom.coordinates[1]
          }

          // Apply feature state highlight
          const featureId = labelFeature.id ?? props.mvt_id
          const sourceLayer = labelFeature.sourceLayer
          const source = labelFeature.source
          if (featureId != null && source) {
            try {
              map.setFeatureState({ source, sourceLayer, id: featureId }, { selected: true })
              highlightedFeatureRef.current = { source, sourceLayer, id: featureId }
            } catch (e) { console.warn('setFeatureState error:', e) }
          }

          // Filter-based highlight (works with PMTiles)
          setSelectedHighlight(map, labelFeature)
          setHoverHighlight(map, null)

          // For feature clicks, don't show pin marker
          store.clearClickMarker()

          store.setSelectedPlace({
            lat: featureLat,
            lon: featureLon,
            name: props.name || 'Unknown',
            address: null,
            type: props.kind_detail || props.kind || null,
            source: 'basemap_label',
            matchCode: null,
            mode: 'feature',
            featureId: featureId,
            featureLayer: labelFeature.layer?.id || null,
            wikidata: props.wikidata || null,
            raw: {
              wikidata: props.wikidata || null,
              population: props.population || null,
              kind: props.kind || null,
              kind_detail: props.kind_detail || null,
              elevation: props.elevation || null,
            },
          })
        } else {
          // No labeled feature — show reticle at click point
          // Clear any existing boundary when clicking empty map
          if (updateBoundaryRef.current) updateBoundaryRef.current(null)
          store.setClickMarker({
            lat,
            lon: lng,
            circleRadiusPx: MARKER_RADIUS_PX,
          })

          store.setSelectedPlace({
            lat,
            lon: lng,
            name: 'Dropped pin',
            address: null,
            type: null,
            source: 'map_click',
            matchCode: null,
            mode: 'reticle',
            raw: {},
          })

          // Reverse geocode in background
          fetchReverse(lat, lng).then((place) => {
            if (!place) return
            const current = useStore.getState().selectedPlace
            if (current && Math.abs(current.lat - lat) < 0.00001 && Math.abs(current.lon - lng) < 0.00001) {
              useStore.getState().setSelectedPlace({
                ...place,
                lat,
                lon: lng,
              })
            }
          })
        }
      }
    })

    // Double-click ends measurement mode (and prevents zoom)
    map.on('dblclick', (e) => {
      if (measuringRef.current.active) {
        e.preventDefault()
        // Add final point and end
        const { lng, lat } = e.lngLat
        addMeasurePoint(lat, lng)
        endMeasuring()
      }
    })

    // Reposition measure labels on map move
    map.on('move', repositionLabels)

    // Initialize mapCenter immediately when map loads (Fix 1: search viewport)
    map.once('load', () => {
      const center = map.getCenter()
      const zoom = map.getZoom()
      setMapCenter({ lat: center.lat, lon: center.lng, zoom })
    })

    map.on('load', () => {
      // Guard against double-mount in React strict mode
      if (!map.getSource(ROUTE_SOURCE)) {
        map.addSource(ROUTE_SOURCE, {
          type: 'geojson',
          data: { type: 'FeatureCollection', features: [] },
        })
      }

      // Boundary polygon layer for selected places
      if (!map.getLayer(BOUNDARY_LAYER)) {
        addBoundaryLayer(map)
      }

      // Apply improved base label styling for readability
      applyBaseLabelStyling(map)

      // Restore overlay layers from localStorage prefs
      try {
        const raw = localStorage.getItem('navi-layer-prefs')
        if (raw) {
          const prefs = JSON.parse(raw)
          if (prefs.hillshade && hasFeature('has_hillshade')) {
            addHillshade(map, currentThemeRef.current)
            activeLayersRef.current.hillshade = true
          }
          if (prefs.traffic && hasFeature('has_traffic_overlay')) {
            addTraffic(map, currentThemeRef.current)
            activeLayersRef.current.traffic = true
          }
          if (prefs.publicLands && hasFeature('has_public_lands_layer')) {
            addPublicLands(map, currentThemeRef.current)
            activeLayersRef.current.publicLands = true
          }
          if (prefs.contours && hasFeature('has_contours')) {
            addContours(map, currentThemeRef.current)
            activeLayersRef.current.contours = true
          }
        } else if (hasFeature('has_hillshade')) {
          // Default: hillshade ON if available
          addHillshade(map, currentThemeRef.current)
          activeLayersRef.current.hillshade = true
        }
      } catch {}


      // Register updateBoundary function - called directly when boundary data arrives
      const updateBoundaryFn = (boundaryGeometry) => {
        const source = map.getSource(BOUNDARY_SOURCE)
        if (!source) return

        if (!boundaryGeometry) {
          source.setData({ type: 'FeatureCollection', features: [] })
          return
        }

        if (boundaryGeometry.type === 'Polygon' || boundaryGeometry.type === 'MultiPolygon') {
          source.setData({
            type: 'Feature',
            geometry: boundaryGeometry,
            properties: {},
          })

          // Zoom to fit boundary
          try {
            const coords = boundaryGeometry.type === 'Polygon'
              ? boundaryGeometry.coordinates[0]
              : boundaryGeometry.coordinates.flat(1)

            if (coords.length > 0) {
              let minLng = Infinity, maxLng = -Infinity, minLat = Infinity, maxLat = -Infinity
              for (const [lng, lat] of coords) {
                if (lng < minLng) minLng = lng
                if (lng > maxLng) maxLng = lng
                if (lat < minLat) minLat = lat
                if (lat > maxLat) maxLat = lat
              }
              map.fitBounds([[minLng, minLat], [maxLng, maxLat]], {
                padding: 50,
                duration: 700,
                maxZoom: 16,
              })
            }
          } catch (e) {
            console.warn('fitBounds error:', e)
          }
        }
      }
      updateBoundaryRef.current = updateBoundaryFn
      useStore.getState().setUpdateBoundary(updateBoundaryFn)

      // POI/label hover affordance — cursor pointer + highlight
      const interactiveLayers = ['pois', 'places_locality', 'places_region', 'places_country', 'places_subplace']

      interactiveLayers.forEach(layerId => {
        map.on('mouseenter', layerId, (e) => {
          if (!measuringRef.current.active) {
            map.getCanvas().style.cursor = 'pointer'
            const feature = e.features?.[0]
            if (feature?.properties?.name) {
              setHoverHighlight(map, feature)
              hoveredFeatureRef.current = feature
            }
          }
        })

        map.on('mouseleave', layerId, () => {
          if (!measuringRef.current.active) {
            map.getCanvas().style.cursor = ''
            setHoverHighlight(map, null)
            hoveredFeatureRef.current = null
          }
        })
      })
    })

    mapInstance.current = map

    // ResizeObserver to handle layout settling, panel changes, window resize
    const ro = new ResizeObserver(() => {
      map.resize()
    })
    ro.observe(mapRef.current)

    return () => {
      ro.disconnect()
      if (watchIdRef.current != null) navigator.geolocation.clearWatch(watchIdRef.current)
      if (gpsMarkerRef.current) gpsMarkerRef.current.remove()
      // Clean up measure labels
      measureLabelsRef.current.forEach(el => el.remove())
      measureLabelsRef.current = []
      maplibregl.removeProtocol('pmtiles')
      map.remove()
    }
  }, [setSheetState])

  /** Create or update the GPS chevron/dot marker */
  function createOrUpdateGpsMarker(map, lat, lon, heading) {
    if (!gpsMarkerRef.current) {
      const el = document.createElement('div')
      if (heading != null && !isNaN(heading)) {
        el.className = 'navi-chevron'
        el.innerHTML = CHEVRON_SVG
        el.style.transform = `rotate(${heading}deg)`
      } else {
        el.className = 'navi-gps-dot'
      }
      gpsMarkerRef.current = new maplibregl.Marker({ element: el })
        .setLngLat([lon, lat])
        .addTo(map)
    } else {
      gpsMarkerRef.current.setLngLat([lon, lat])
      const el = gpsMarkerRef.current.getElement()
      if (heading != null && !isNaN(heading)) {
        if (!el.classList.contains('navi-chevron')) {
          el.className = 'navi-chevron'
          el.innerHTML = CHEVRON_SVG
        }
        el.style.transform = `rotate(${heading}deg)`
      } else {
        if (!el.classList.contains('navi-gps-dot')) {
          el.className = 'navi-gps-dot'
          el.innerHTML = ''
        }
      }
    }
  }

  // React to permission changes from LocateButton (when user grants after initial denial)
  useEffect(() => {
    const map = mapInstance.current
    if (!map || geoPermission !== 'granted') return

    // If marker already exists, watchPosition is already running — nothing to do
    if (gpsMarkerRef.current) return

    // Permission was just granted (likely from LocateButton) — create marker + start tracking
    const loc = useStore.getState().userLocation
    if (loc) {
      createOrUpdateGpsMarker(map, loc.lat, loc.lon, null)
    }

    if (!watchIdRef.current) {
      watchIdRef.current = navigator.geolocation.watchPosition(
        (pos) => {
          const { latitude, longitude, heading } = pos.coords
          useStore.getState().setUserLocation({ lat: latitude, lon: longitude })
          createOrUpdateGpsMarker(map, latitude, longitude, heading)
        },
        () => {},
        { enableHighAccuracy: true, maximumAge: 5000 }
      )
    }
  }, [geoPermission])

    // Swap map theme when store.theme changes
  useEffect(() => {
    const map = mapInstance.current
    if (!map || currentThemeRef.current === theme) return

    currentThemeRef.current = theme
    const center = map.getCenter()
    const zoom = map.getZoom()
    const bearing = map.getBearing()
    const pitch = map.getPitch()

    map.setStyle(buildStyle(theme), { diff: false })

    // Re-add sources/layers after style swap
    map.once('style.load', () => {
      // Guard against source already existing
      if (!map.getSource(ROUTE_SOURCE)) {
        map.addSource(ROUTE_SOURCE, {
          type: 'geojson',
          data: { type: 'FeatureCollection', features: [] },
        })
      }

      // Boundary polygon layer
      if (!map.getLayer(BOUNDARY_LAYER)) {
        addBoundaryLayer(map)
      }

      // Apply improved base label styling for readability
      applyBaseLabelStyling(map)

      // Re-add active overlay layers
      if (activeLayersRef.current.hillshade) addHillshade(map, currentThemeRef.current)
      if (activeLayersRef.current.traffic) addTraffic(map, currentThemeRef.current)
      if (activeLayersRef.current.publicLands) addPublicLands(map, currentThemeRef.current)
      if (activeLayersRef.current.contours) addContours(map, currentThemeRef.current)
      if (activeLayersRef.current.contoursTest) addContoursTest(map, currentThemeRef.current)
      if (activeLayersRef.current.contoursTest10ft) addContoursTest10ft(map, currentThemeRef.current)
      if (activeLayersRef.current.usfsTrails) addUsfsTrails(map, currentThemeRef.current)
      if (activeLayersRef.current.blmTrails) addBlmTrails(map, currentThemeRef.current)

      // Clear highlights on theme change (paint values will be re-stored on next interaction)
      clearAllHighlights(map)
      originalPaintValues = {}

      // Restore view
      map.jumpTo({ center, zoom, bearing, pitch })
      // Re-render route if exists
      const currentRoute = useStore.getState().route
      if (currentRoute) updateRoute(map, currentRoute)
    })
  }, [theme])

  // Preview pin for selected place
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return

    // Remove old preview marker
    if (previewMarkerRef.current) {
      previewMarkerRef.current.remove()
      previewMarkerRef.current = null
    }

    if (!selectedPlace) return

    // Only fly to place if it came from search (not map-click which already centered)
    if (selectedPlace.source !== 'map_click' && selectedPlace.source !== 'basemap_label') {
      map.flyTo({ center: [selectedPlace.lon, selectedPlace.lat], zoom: 14, duration: 800 })
    }

    // Different visual feedback based on mode
    const isFeatureMode = selectedPlace.mode === 'feature'

    // Create marker element
    const el = document.createElement('div')
    if (isFeatureMode) {
      // Feature mode: subtle ring indicator
      el.className = 'navi-feature-highlight'
    } else {
      // Reticle mode: pin with center dot
      el.className = 'navi-pin-preview'
      const dot = document.createElement('div')
      dot.className = 'navi-pin-center-dot'
      el.appendChild(dot)
    }

    previewMarkerRef.current = new maplibregl.Marker({ element: el })
      .setLngLat([selectedPlace.lon, selectedPlace.lat])
      .addTo(map)

    return () => {
      if (previewMarkerRef.current) {
        previewMarkerRef.current.remove()
        previewMarkerRef.current = null
      }
    }
  }, [selectedPlace])

  // Escape key to close/deselect place card
  useEffect(() => {
    if (!selectedPlace) return

    const handleKeyDown = (e) => {
      if (e.key === 'Escape') {
        const map = mapInstance.current
        const store = useStore.getState()

        // Clear selected place and click marker
        store.clearSelectedPlace()
        store.clearClickMarker()

        // Clear boundary
        if (updateBoundaryRef.current) updateBoundaryRef.current(null)

        // Clear highlight
        if (map) setSelectedHighlight(map, null)
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [selectedPlace])

  // Update route polyline when route changes
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return
    if (!map.isStyleLoaded()) {
      const handler = () => updateRoute(map, route)
      map.once('idle', handler)
      return () => map.off('idle', handler)
    }
    updateRoute(map, route)
  }, [route])

  function updateRoute(map, routeData) {
    if (!map) return

    // Remove old route layers
    const style = map.getStyle()
    if (style) {
      for (const layer of style.layers) {
        if (layer.id.startsWith(ROUTE_LAYER_PREFIX)) {
          map.removeLayer(layer.id)
        }
      }
    }

    if (!routeData || !routeData.legs) {
      if (map.getSource(ROUTE_SOURCE)) {
        map.getSource(ROUTE_SOURCE).setData({ type: 'FeatureCollection', features: [] })
      }
      return
    }

    const features = []
    for (let i = 0; i < routeData.legs.length; i++) {
      const leg = routeData.legs[i]
      if (!leg.shape) continue
      const coords = decodePolyline(leg.shape, 6)
      features.push({
        type: 'Feature',
        properties: { legIndex: i },
        geometry: { type: 'LineString', coordinates: coords },
      })
    }

    const source = map.getSource(ROUTE_SOURCE)
    if (source) {
      source.setData({ type: 'FeatureCollection', features })
    } else {
      map.addSource(ROUTE_SOURCE, {
        type: 'geojson',
        data: { type: 'FeatureCollection', features },
      })
    }

    // Use CSS variable for route color (read computed value)
    const routeColor = getComputedStyle(document.documentElement).getPropertyValue('--route-line').trim()

    for (let i = 0; i < features.length; i++) {
      const layerId = `${ROUTE_LAYER_PREFIX}${i}`
      if (!map.getLayer(layerId)) {
        map.addLayer({
          id: layerId,
          type: 'line',
          source: ROUTE_SOURCE,
          filter: ['==', ['get', 'legIndex'], i],
          layout: { 'line-join': 'round', 'line-cap': 'round' },
          paint: {
            'line-color': routeColor || '#7a9a6b',
            'line-width': 5,
            'line-opacity': 0.85,
          },
        })
      }
    }

    // Fit bounds to route
    if (features.length > 0) {
      const allCoords = features.flatMap((f) => f.geometry.coordinates)
      const bounds = allCoords.reduce(
        (b, c) => b.extend(c),
        new maplibregl.LngLatBounds(allCoords[0], allCoords[0])
      )
      // Single-panel: no floating detail
      const leftPad = 420  // 360px panel + margin
      map.fitBounds(bounds, { padding: { top: 60, bottom: 60, left: leftPad, right: 60 } })
    }
  }

  // Update stop markers when stops change
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return

    // Remove old markers
    for (const m of markersRef.current) m.remove()
    markersRef.current = []
    if (popupRef.current) {
      popupRef.current.remove()
      popupRef.current = null
    }

    const hasGpsOrigin = gpsOrigin && geoPermission === 'granted'
    const indexOffset = hasGpsOrigin ? 1 : 0

    stops.forEach((stop, i) => {
      const displayIndex = i + indexOffset
      const effectiveTotal = stops.length + indexOffset

      let pinClass = 'navi-pin navi-pin--intermediate'
      if (displayIndex === 0) pinClass = 'navi-pin navi-pin--origin'
      else if (displayIndex === effectiveTotal - 1 && effectiveTotal > 1) pinClass = 'navi-pin navi-pin--destination'

      const label = String.fromCharCode(65 + Math.min(displayIndex, 25))

      const el = document.createElement('div')
      el.className = pinClass
      el.textContent = label

      el.addEventListener('click', (e) => {
        e.stopPropagation()
        // Flag so the map-level click handler doesn't fire
        pinClickedRef.current = true
        if (popupRef.current) popupRef.current.remove()
        const popup = new maplibregl.Popup({ offset: 20, closeButton: true })
          .setLngLat([stop.lon, stop.lat])
          .setHTML(
            `<div style="font-size:12px;max-width:200px">
              <strong>${stop.name}</strong>
              <br/><button id="remove-stop-${stop.id}" style="margin-top:4px;padding:2px 8px;background:var(--status-danger);border:none;border-radius:4px;color:white;cursor:pointer;font-size:11px">Remove</button>
            </div>`
          )
          .addTo(map)

        popup.getElement().querySelector(`#remove-stop-${stop.id}`)?.addEventListener('click', () => {
          useStore.getState().removeStop(stop.id)
          popup.remove()
        })
        popupRef.current = popup
      })

      const marker = new maplibregl.Marker({ element: el })
        .setLngLat([stop.lon, stop.lat])
        .addTo(map)

      markersRef.current.push(marker)
    })

    // If stops but no route yet, fit to stops
    if (stops.length > 0 && !route) {
      if (stops.length === 1) {
        map.flyTo({ center: [stops[0].lon, stops[0].lat], zoom: 13 })
      } else {
        const bounds = stops.reduce(
          (b, s) => b.extend([s.lon, s.lat]),
          new maplibregl.LngLatBounds([stops[0].lon, stops[0].lat], [stops[0].lon, stops[0].lat])
        )
        map.fitBounds(bounds, { padding: { top: 60, bottom: 60, left: 420, right: 60 } })
      }
    }
  }, [stops, route, gpsOrigin, geoPermission])


  // ESC key handler for measurement mode
  useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.key === "Escape" && measuringRef.current.active) {
        endMeasuring()
      }
    }
    window.addEventListener("keydown", handleKeyDown)
    return () => window.removeEventListener("keydown", handleKeyDown)
  }, [])

  // Handle location pick mode for contacts
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return
    if (pickingLocationFor) {
      map.getCanvas().style.cursor = 'crosshair'
    }
    return () => {
      if (map && !measuringRef.current.active) {
        map.getCanvas().style.cursor = ''
      }
    }
  }, [pickingLocationFor])

  // ESC key handler for location pick mode
  useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.key === 'Escape' && pickingLocationFor) {
        // Cancel pick mode, reopen modal with original form data
        const map = mapInstance.current
        if (map) map.getCanvas().style.cursor = ''
        setEditingContact(pickingLocationFor)
        clearPickingLocationFor()
      }
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [pickingLocationFor, setEditingContact, clearPickingLocationFor])


  // Track zoom level for indicator
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return

    const updateZoom = () => setZoomLevel(map.getZoom())

    // Set initial zoom
    if (map.loaded()) {
      updateZoom()
    } else {
      map.once("load", updateZoom)
    }

    // Subscribe to zoom changes
    map.on("zoom", updateZoom)

    return () => {
      map.off("zoom", updateZoom)
    }
  }, [])


  // Track map center for search viewport bias
  useEffect(() => {
    const map = mapInstance.current
    if (!map) return

    const updateCenter = () => {
      const center = map.getCenter()
      const zoom = map.getZoom()
      setMapCenter({ lat: center.lat, lon: center.lng, zoom })
    }

    // Set initial center
    if (map.loaded()) {
      updateCenter()
    } else {
      map.once("load", updateCenter)
    }

    // Update on move end (not every frame)
    map.on("moveend", updateCenter)

    return () => {
      map.off("moveend", updateCenter)
    }
  }, [setMapCenter])

  return (
    <div className="relative w-full h-full">
      <div ref={mapRef} className="w-full h-full" {...contextMenuHandlers} />
      {/* Zoom level indicator - bottom-left corner */}
      <div
        className="absolute bottom-4 left-4 z-50 px-2 py-1 rounded-full text-xs font-mono pointer-events-none"
        style={{
          backgroundColor: "rgba(0, 0, 0, 0.6)",
          color: "white",
          fontSize: "12px",
          padding: "4px 8px",
          borderRadius: "12px",
        }}
      >
        Z {zoomLevel.toFixed(1)}
      </div>

      {/* Measurement info bar */}
      {(measuring.active || measuring.points.length > 1) && (
        <div
          className="absolute top-4 left-1/2 transform -translate-x-1/2 z-50 flex items-center gap-3 px-4 py-2 rounded-lg"
          style={{
            backgroundColor: "rgba(0, 0, 0, 0.8)",
            color: "white",
            fontSize: "13px",
            boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
          }}
        >
          <Ruler size={16} style={{ opacity: 0.8 }} />
          <span>
            <strong>{formatDistance(measuring.totalMeters)}</strong>
            <span style={{ opacity: 0.7, marginLeft: "6px" }}>
              ({measuring.points.length} {measuring.points.length === 1 ? "point" : "points"})
            </span>
          </span>
          {measuring.active && (
            <span style={{ opacity: 0.6, fontSize: "11px" }}>
              Click to add points
            </span>
          )}
          <button
            onClick={endMeasuring}
            className="px-2 py-1 rounded text-xs font-medium"
            style={{
              background: "var(--accent)",
              color: "white",
              border: "none",
              cursor: "pointer",
            }}
          >
            Done
          </button>
          <button
            onClick={clearMeasuring}
            className="p-1 rounded"
            style={{
              background: "transparent",
              color: "white",
              border: "none",
              cursor: "pointer",
              opacity: 0.7,
            }}
            title="Clear measurement"
          >
            <X size={16} />
          </button>
        </div>
      )}

      {/* Radial context menu */}
      <RadialMenu
        open={radialMenu.open}
        x={radialMenu.x}
        y={radialMenu.y}
        lat={radialMenu.lat}
        lon={radialMenu.lon}
        wedges={radialWedges}
        centerLabel={radialMenu.centerLabel}
        onDismiss={() => setRadialMenu((m) => ({ ...m, open: false }))}
      />
    </div>
  )
})

export default MapView
