'use client'

import { useEffect, useRef, useState, useCallback } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { useDrop } from 'react-dnd'
import Moveable from 'react-moveable'
import { getSymbolById, getSymbolDataUri } from '@/lib/fw-symbols'
import type { Project, DrawFeature, DrawMode } from '@/types'
import { MeasurePanel } from './measure-panel'
import { FALLBACK_HOSE_TYPES, type HoseTypeData, type MeasurementSummary } from '@/lib/hose-calc'

// Haversine distance between two [lng, lat] points in meters
function haversineDistance(a: number[], b: number[]): number {
  const R = 6371000
  const toRad = Math.PI / 180
  const dLat = (b[1] - a[1]) * toRad
  const dLng = (b[0] - a[0]) * toRad
  const x = Math.sin(dLat / 2) ** 2 + Math.cos(a[1] * toRad) * Math.cos(b[1] * toRad) * Math.sin(dLng / 2) ** 2
  return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x))
}

function formatDistance(meters: number): string {
  if (meters < 1000) return `${Math.round(meters)} m`
  return `${(meters / 1000).toFixed(2)} km`
}

// Approximate polygon area in m² using the Shoelace formula on spherical coordinates
function polygonArea(ring: number[][]): number {
  const toRad = Math.PI / 180
  const R = 6371000
  let area = 0
  const n = ring.length - 1 // exclude closing duplicate
  for (let i = 0; i < n; i++) {
    const j = (i + 1) % n
    area += (ring[j][0] - ring[i][0]) * toRad * (2 + Math.sin(ring[i][1] * toRad) + Math.sin(ring[j][1] * toRad))
  }
  return Math.abs(area * R * R / 2)
}

// Point-to-segment distance in screen pixels (for click detection on lines)
function pointToSegmentDist(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number {
  const dx = x2 - x1, dy = y2 - y1
  const lenSq = dx * dx + dy * dy
  if (lenSq === 0) return Math.hypot(px - x1, py - y1)
  const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq))
  return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy))
}

// Generate a geodesic circle (corrects for lat/lng distortion)
function generateCircleCoords(center: [number, number], edge: [number, number], steps: number = 64): number[][] {
  const [cLng, cLat] = center
  const [eLng, eLat] = edge
  const toRad = Math.PI / 180
  // Haversine distance in meters
  const R = 6371000
  const dLat = (eLat - cLat) * toRad
  const dLng = (eLng - cLng) * toRad
  const a = Math.sin(dLat / 2) ** 2 + Math.cos(cLat * toRad) * Math.cos(eLat * toRad) * Math.sin(dLng / 2) ** 2
  const radiusM = R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  // Generate circle points using destination formula
  const coords: number[][] = []
  for (let i = 0; i <= steps; i++) {
    const bearing = (i / steps) * 2 * Math.PI
    const latR = cLat * toRad
    const lngR = cLng * toRad
    const d = radiusM / R
    const lat2 = Math.asin(Math.sin(latR) * Math.cos(d) + Math.cos(latR) * Math.sin(d) * Math.cos(bearing))
    const lng2 = lngR + Math.atan2(Math.sin(bearing) * Math.sin(d) * Math.cos(latR), Math.cos(d) - Math.sin(latR) * Math.sin(lat2))
    coords.push([lng2 / toRad, lat2 / toRad])
  }
  return coords
}

interface MapViewProps {
  project: Project | null
  features: DrawFeature[]
  drawMode: DrawMode
  selectedColor: string
  selectedWidth: number
  onFeaturesChange: (features: DrawFeature[]) => void
  onSymbolDrop: (iconId: string, coordinates: [number, number], imageUrl?: string) => void
  onTextPlace: (coordinates: [number, number]) => void
  canEdit: boolean
  mapRef?: React.MutableRefObject<maplibregl.Map | null>
  onDrawModeChange?: (mode: DrawMode) => void
  undoDrawPointRef?: React.MutableRefObject<(() => boolean) | null>
  /** Tap-to-place: wenn gesetzt, platziert der nächste Karten-Tipp dieses Symbol (Mobil-freundlich). */
  pendingSymbol?: { iconId: string; imageUrl?: string } | null
  onSymbolPlaced?: () => void
}

export function MapView({
  project,
  features,
  drawMode,
  selectedColor,
  selectedWidth,
  onFeaturesChange,
  onSymbolDrop,
  onTextPlace,
  canEdit,
  mapRef: externalMapRef,
  onDrawModeChange,
  undoDrawPointRef,
  pendingSymbol,
  onSymbolPlaced,
}: MapViewProps) {
  const mapContainer = useRef<HTMLDivElement | null>(null)
  const map = useRef<maplibregl.Map | null>(null)
  const markersRef = useRef<maplibregl.Marker[]>([])
  const markerCleanupsRef = useRef<(() => void)[]>([])
  const measureMarkersRef = useRef<maplibregl.Marker[]>([])
  const measureCoordsRef = useRef<number[][]>([])
  const [isMapLoaded, setIsMapLoaded] = useState(false)
  const [activeBaseLayer, setActiveBaseLayer] = useState<'osm' | 'satellite' | 'swisstopo'>('osm')
  const [layerDropdownOpen, setLayerDropdownOpen] = useState(false)
  const [measurePointCount, setMeasurePointCount] = useState(0)
  const [measureFinished, setMeasureFinished] = useState(false)
  // Messergebnis für das React-Panel (ersetzt das frühere innerHTML-DOM-Overlay)
  const [measureResult, setMeasureResult] = useState<{ measurement: MeasurementSummary; hoseTypes: HoseTypeData[] } | null>(null)
  const [drawingPointCount, setDrawingPointCount] = useState(0)
  const tooltipRef = useRef<HTMLDivElement | null>(null)
  const tooltipPosRef = useRef({ x: 0, y: 0 })

  // Vertex editing state (for lines/polygons in select mode)
  const selectedLineIdRef = useRef<string | null>(null)
  const vertexMarkersRef = useRef<maplibregl.Marker[]>([])

  // Inline edit state (replaces native prompt() which breaks fullscreen)
  const [inlineEdit, setInlineEdit] = useState<{
    featureId: string
    type: 'label' | 'text'
    value: string
  } | null>(null)
  const inlineEditInputRef = useRef<HTMLInputElement>(null)

  // Symbol selection state for Moveable (rotation/scale handles)
  const [isSymbolSelected, setIsSymbolSelected] = useState(false)
  const [symbolSelectKey, setSymbolSelectKey] = useState(0) // Force Moveable re-mount on symbol switch
  const moveableRef = useRef<Moveable>(null)
  const selectedSymbolRef = useRef<{
    id: string
    wrapperEl: HTMLElement
    innerEl: HTMLElement
    rotation: number
    scale: number
    featureType: 'symbol' | 'text'
    baseFontSize?: number
    resizeStartWidth?: number
    resizeStartScale?: number
  } | null>(null)
  const drawingRef = useRef<{
    isDrawing: boolean
    currentCoords: number[][]
    startPoint: [number, number] | null
  }>({
    isDrawing: false,
    currentCoords: [],
    startPoint: null,
  })

  // --- REFS for stable closures (prevents stale state in map event handlers) ---
  const drawModeRef = useRef(drawMode)
  const selectedColorRef = useRef(selectedColor)
  const selectedWidthRef = useRef(selectedWidth)
  const featuresRef = useRef(features)
  const canEditRef = useRef(canEdit)
  const onFeaturesChangeRef = useRef(onFeaturesChange)
  const onSymbolDropRef = useRef(onSymbolDrop)
  const onTextPlaceRef = useRef(onTextPlace)
  const onDrawModeChangeRef = useRef(onDrawModeChange)
  const pendingSymbolRef = useRef(pendingSymbol)
  const onSymbolPlacedRef = useRef(onSymbolPlaced)

  // Lock/unlock map panning based on draw mode
  // Apple-style: touchZoomRotate (two-finger pinch/zoom) ALWAYS enabled
  // dragPan only disabled for freehand (single finger = draw, two finger = zoom)
  // For click-based tools (line, polygon, arrow, etc.) dragPan stays enabled — taps add points, drags pan
  useEffect(() => {
    drawModeRef.current = drawMode
    if (map.current) {
      // Only freehand needs dragPan disabled (single finger draws)
      if (drawMode === 'freehand' && canEdit) {
        map.current.dragPan.disable()
      } else {
        map.current.dragPan.enable()
      }
      // Two-finger zoom/rotate ALWAYS works
      map.current.touchZoomRotate.enable()
    }
  }, [drawMode, canEdit])
  useEffect(() => { selectedColorRef.current = selectedColor }, [selectedColor])
  useEffect(() => { selectedWidthRef.current = selectedWidth }, [selectedWidth])
  useEffect(() => { featuresRef.current = features }, [features])
  useEffect(() => { canEditRef.current = canEdit }, [canEdit])
  useEffect(() => { onFeaturesChangeRef.current = onFeaturesChange }, [onFeaturesChange])
  useEffect(() => { onSymbolDropRef.current = onSymbolDrop }, [onSymbolDrop])
  useEffect(() => { onTextPlaceRef.current = onTextPlace }, [onTextPlace])
  useEffect(() => { onDrawModeChangeRef.current = onDrawModeChange }, [onDrawModeChange])
  useEffect(() => { pendingSymbolRef.current = pendingSymbol }, [pendingSymbol])
  useEffect(() => { onSymbolPlacedRef.current = onSymbolPlaced }, [onSymbolPlaced])

  // Expose undo-draw-point function to parent via ref
  useEffect(() => {
    if (undoDrawPointRef) {
      undoDrawPointRef.current = () => {
        if (drawingRef.current.isDrawing && drawingRef.current.currentCoords.length > 1) {
          drawingRef.current.currentCoords.pop()
          return true // handled
        }
        if (drawingRef.current.isDrawing && drawingRef.current.currentCoords.length <= 1) {
          // Cancel drawing entirely
          drawingRef.current.isDrawing = false
          drawingRef.current.currentCoords = []
          drawingRef.current.startPoint = null
          return true
        }
        return false // not handled, let parent undo features
      }
    }
  }, [undoDrawPointRef])

  // --- Measurement display helper ---
  const updateMeasureDisplayRef = useRef<() => void>(() => {})
  const updateMeasureDisplay = useCallback(() => {
    if (!map.current) return
    const coords = measureCoordsRef.current

    // Remove old labels (but keep point markers at indices 0,2,4,... and labels at odd indices)
    // Actually, let's clear ALL measure markers and rebuild
    measureMarkersRef.current.forEach(m => m.remove())
    measureMarkersRef.current = []

    if (coords.length === 0) {
      // Clear the measure line source
      const src = map.current.getSource('measure-line') as maplibregl.GeoJSONSource
      if (src) src.setData({ type: 'FeatureCollection', features: [] })
      return
    }

    // Update line
    const src = map.current.getSource('measure-line') as maplibregl.GeoJSONSource
    if (src) {
      src.setData({
        type: 'FeatureCollection',
        features: coords.length >= 2 ? [{
          type: 'Feature',
          geometry: { type: 'LineString', coordinates: coords },
          properties: {},
        }] : [],
      })
    }

    // Create draggable point markers + distance labels
    let totalDist = 0
    coords.forEach((pt, i) => {
      // Draggable point marker
      const dot = document.createElement('div')
      dot.style.cssText = 'width:18px;height:18px;background:#fbbf24;border:3px solid #000;border-radius:50%;cursor:grab;'
      const pointMarker = new maplibregl.Marker({ element: dot, draggable: true, anchor: 'center' })
        .setLngLat(pt as [number, number])
        .addTo(map.current!)
      
      // On drag: update coords and redraw
      const idx = i
      pointMarker.on('dragend', () => {
        const pos = pointMarker.getLngLat()
        measureCoordsRef.current[idx] = [pos.lng, pos.lat]
        updateMeasureDisplay()
      })
      measureMarkersRef.current.push(pointMarker)

      // Segment distance label at midpoint
      if (i > 0) {
        const prev = coords[i - 1]
        const segDist = haversineDistance(prev, pt)
        totalDist += segDist
        const midLng = (prev[0] + pt[0]) / 2
        const midLat = (prev[1] + pt[1]) / 2

        const segLabel = document.createElement('div')
        segLabel.style.cssText = 'background:rgba(0,0,0,0.75);color:#fbbf24;padding:3px 7px;border-radius:5px;font-size:12px;font-weight:600;white-space:nowrap;pointer-events:none;'
        segLabel.textContent = formatDistance(segDist)
        const segMarker = new maplibregl.Marker({ element: segLabel, anchor: 'center' })
          .setLngLat([midLng, midLat])
          .addTo(map.current!)
        measureMarkersRef.current.push(segMarker)
      }
    })

    // Total distance label at last point
    if (coords.length >= 2) {
      const last = coords[coords.length - 1]
      const totalLabel = document.createElement('div')
      totalLabel.style.cssText = 'background:#fbbf24;color:#000;padding:5px 10px;border-radius:6px;font-size:14px;font-weight:700;white-space:nowrap;pointer-events:none;box-shadow:0 2px 8px rgba(0,0,0,0.3);'
      totalLabel.textContent = `Gesamt: ${formatDistance(totalDist)}`
      const totalMarker = new maplibregl.Marker({ element: totalLabel, anchor: 'top' })
        .setLngLat(last as [number, number])
        .addTo(map.current!)
      measureMarkersRef.current.push(totalMarker)
    }
  }, [])
  updateMeasureDisplayRef.current = updateMeasureDisplay

  // --- Vertex editing: show/clear draggable vertex markers for selected line/polygon ---
  const showVertexMarkersRef = useRef<(featureId: string | null) => void>(() => {})
  const showVertexMarkers = useCallback((featureId: string | null) => {
    // Clear existing vertex markers
    vertexMarkersRef.current.forEach(m => m.remove())
    vertexMarkersRef.current = []

    if (!featureId || !map.current) {
      selectedLineIdRef.current = null
      return
    }

    selectedLineIdRef.current = featureId
    const feature = featuresRef.current.find(f => f.id === featureId)
    if (!feature) return

    let coords: number[][]
    const isPolygon = feature.geometry.type === 'Polygon'
    if (feature.geometry.type === 'LineString') {
      coords = feature.geometry.coordinates as number[][]
    } else if (isPolygon) {
      const ring = (feature.geometry.coordinates as number[][][])[0]
      coords = ring.slice(0, -1) // exclude closing point
    } else return

    const m = map.current

    // Create draggable vertex markers
    coords.forEach((pt, i) => {
      const dot = document.createElement('div')
      dot.style.cssText = 'width:14px;height:14px;background:#3b82f6;border:2px solid #fff;border-radius:50%;cursor:grab;box-shadow:0 1px 4px rgba(0,0,0,0.4);'

      const marker = new maplibregl.Marker({ element: dot, draggable: true, anchor: 'center' })
        .setLngLat(pt as [number, number])
        .addTo(m)

      const idx = i
      marker.on('dragend', () => {
        const pos = marker.getLngLat()
        const feat = featuresRef.current.find(f => f.id === featureId)
        if (!feat) return

        if (feat.geometry.type === 'LineString') {
          const newCoords = [...(feat.geometry.coordinates as number[][])]; newCoords[idx] = [pos.lng, pos.lat]
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: newCoords } } : f)
          onFeaturesChangeRef.current(updated)
        } else if (feat.geometry.type === 'Polygon') {
          const ring = [...(feat.geometry.coordinates as number[][][])[0]]
          ring[idx] = [pos.lng, pos.lat]
          if (idx === 0) ring[ring.length - 1] = [pos.lng, pos.lat] // close ring
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: [ring] } } : f)
          onFeaturesChangeRef.current(updated)
        }
      })

      // Right-click to delete vertex (min 2 for lines, 3 for polygons)
      const handleDelete = (e: Event) => {
        e.preventDefault(); e.stopPropagation()
        const feat = featuresRef.current.find(f => f.id === featureId)
        if (!feat) return

        if (feat.geometry.type === 'LineString') {
          const c = feat.geometry.coordinates as number[][]
          if (c.length <= 2) return
          const newCoords = c.filter((_, ci) => ci !== idx)
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: newCoords } } : f)
          onFeaturesChangeRef.current(updated)
        } else if (feat.geometry.type === 'Polygon') {
          const ring = (feat.geometry.coordinates as number[][][])[0]
          const unique = ring.slice(0, -1)
          if (unique.length <= 3) return
          const newRing = unique.filter((_, ci) => ci !== idx)
          newRing.push(newRing[0]) // close ring
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: [newRing] } } : f)
          onFeaturesChangeRef.current(updated)
        }
      }
      dot.addEventListener('contextmenu', handleDelete)

      // Long-press for touch devices (500ms = delete)
      let longPressTimer: ReturnType<typeof setTimeout> | null = null
      dot.addEventListener('touchstart', (e) => {
        longPressTimer = setTimeout(() => {
          handleDelete(e)
          longPressTimer = null
        }, 500)
      }, { passive: true })
      dot.addEventListener('touchend', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null } })
      dot.addEventListener('touchmove', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null } }, { passive: true })

      vertexMarkersRef.current.push(marker)
    })

    // Create "+" markers between vertices to add new points
    for (let i = 0; i < coords.length - (isPolygon ? 0 : 1); i++) {
      const nextIdx = (i + 1) % coords.length
      const midLng = (coords[i][0] + coords[nextIdx][0]) / 2
      const midLat = (coords[i][1] + coords[nextIdx][1]) / 2

      const plus = document.createElement('div')
      plus.style.cssText = 'width:18px;height:18px;background:rgba(59,130,246,0.5);border:1.5px dashed #3b82f6;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:#fff;line-height:1;'
      plus.textContent = '+'

      const insertAfter = i
      const addVertex = (e: Event) => {
        e.stopPropagation()
        const feat = featuresRef.current.find(f => f.id === featureId)
        if (!feat) return

        if (feat.geometry.type === 'LineString') {
          const newCoords = [...(feat.geometry.coordinates as number[][])]
          newCoords.splice(insertAfter + 1, 0, [midLng, midLat])
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: newCoords } } : f)
          onFeaturesChangeRef.current(updated)
        } else if (feat.geometry.type === 'Polygon') {
          const ring = [...(feat.geometry.coordinates as number[][][])[0]]
          ring.splice(insertAfter + 1, 0, [midLng, midLat])
          const updated = featuresRef.current.map(f => f.id === featureId ? { ...f, geometry: { ...f.geometry, coordinates: [ring] } } : f)
          onFeaturesChangeRef.current(updated)
        }
      }
      plus.addEventListener('click', addVertex)
      plus.addEventListener('touchend', (e) => { e.preventDefault(); addVertex(e) })

      const marker = new maplibregl.Marker({ element: plus, anchor: 'center' })
        .setLngLat([midLng, midLat])
        .addTo(m)
      vertexMarkersRef.current.push(marker)
    }
  }, [])
  showVertexMarkersRef.current = showVertexMarkers

  // --- Fetch elevation data and show pressure calculation ---
  const fetchElevationsAndUpdate = useCallback(async () => {
    const coords = measureCoordsRef.current
    if (coords.length < 2 || !map.current) return

    // Interpolate extra points along the line (every ~30m) for better elevation accuracy
    const sampledCoords: number[][] = [coords[0]]
    const SAMPLE_INTERVAL = 30 // meters
    for (let i = 1; i < coords.length; i++) {
      const segDist = haversineDistance(coords[i - 1], coords[i])
      const numSamples = Math.max(1, Math.floor(segDist / SAMPLE_INTERVAL))
      for (let s = 1; s <= numSamples; s++) {
        const t = s / numSamples
        sampledCoords.push([
          coords[i - 1][0] + t * (coords[i][0] - coords[i - 1][0]),
          coords[i - 1][1] + t * (coords[i][1] - coords[i - 1][1]),
        ])
      }
    }

    // Fetch elevations for all sampled points
    const lats = sampledCoords.map(c => c[1].toFixed(6)).join(',')
    const lngs = sampledCoords.map(c => c[0].toFixed(6)).join(',')

    let sampledElevations: number[] = []
    let elevationSource: 'open-meteo' | 'open-elevation' | 'none' = 'none'

    // Try Open-Meteo first
    try {
      const res = await fetch(`https://api.open-meteo.com/v1/elevation?latitude=${lats}&longitude=${lngs}`)
      if (res.ok) {
        const data = await res.json()
        sampledElevations = data.elevation || []
        if (sampledElevations.length === sampledCoords.length) elevationSource = 'open-meteo'
      }
    } catch (e) {
      console.warn('[Elevation] Open-Meteo nicht erreichbar')
    }

    // Fallback: Open-Elevation API (better resolution in some areas)
    if (sampledElevations.length !== sampledCoords.length) {
      try {
        const locations = sampledCoords.map(c => ({ latitude: c[1], longitude: c[0] }))
        const res = await fetch('https://api.open-elevation.com/api/v1/lookup', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ locations }),
        })
        if (res.ok) {
          const data = await res.json()
          sampledElevations = (data.results || []).map((r: any) => r.elevation)
          if (sampledElevations.length === sampledCoords.length) elevationSource = 'open-elevation'
        }
      } catch (e) {
        console.warn('[Elevation] Open-Elevation auch nicht erreichbar')
      }
    }

    // Final fallback: flat terrain (ehrlich als 'none' ausgewiesen)
    if (sampledElevations.length !== sampledCoords.length) {
      sampledElevations = sampledCoords.map(() => 0)
      elevationSource = 'none'
    }

    // Extract elevations for the original measurement points
    // First point = index 0, then for each segment we took numSamples sub-points
    const elevations: number[] = [sampledElevations[0]]
    let sIdx = 0
    for (let i = 1; i < coords.length; i++) {
      const segDist = haversineDistance(coords[i - 1], coords[i])
      const numSamples = Math.max(1, Math.floor(segDist / SAMPLE_INTERVAL))
      sIdx += numSamples
      elevations.push(sampledElevations[sIdx] ?? 0)
    }

    // Use sampled elevations for more accurate height profile
    // Calculate max elevation gain along full sampled path
    let maxSampledElev = sampledElevations[0]
    let minSampledElev = sampledElevations[0]
    let totalClimb = 0
    let totalDescent = 0
    for (let i = 1; i < sampledElevations.length; i++) {
      const diff = sampledElevations[i] - sampledElevations[i - 1]
      if (diff > 0) totalClimb += diff
      else totalDescent += Math.abs(diff)
      maxSampledElev = Math.max(maxSampledElev, sampledElevations[i])
      minSampledElev = Math.min(minSampledElev, sampledElevations[i])
    }

    // === Leitungsrechner: Schlauchtypen laden (volle Stammdaten) ===
    let hoseTypes: HoseTypeData[] = []
    try {
      const htRes = await fetch('/api/hose-types')
      if (htRes.ok) {
        const htData = await htRes.json()
        if (htData.hoseTypes && htData.hoseTypes.length > 0) {
          hoseTypes = htData.hoseTypes.map((ht: any) => ({
            id: ht.id,
            name: ht.name,
            diameterMm: ht.diameterMm,
            flowRateLpm: ht.flowRateLpm,
            frictionCoeff: ht.frictionCoeff,
            lengthPerPieceM: ht.lengthPerPieceM ?? 20,
            isDefault: ht.isDefault === true,
          }))
        }
      }
    } catch { /* ignore */ }
    if (hoseTypes.length === 0) hoseTypes = FALLBACK_HOSE_TYPES

    let totalDist = 0
    for (let i = 1; i < coords.length; i++) {
      totalDist += haversineDistance(coords[i - 1], coords[i])
    }

    const elStart = elevations[0]
    const elEnd = elevations[elevations.length - 1]

    // Ergebnis in React-State — das MeasurePanel übernimmt Darstellung + Interaktion
    setMeasureResult({
      measurement: {
        totalDistanceM: totalDist,
        elevationStartM: elStart,
        elevationEndM: elEnd,
        netDiffM: elEnd - elStart,
        climbM: totalClimb,
        descentM: totalDescent,
        minElevationM: minSampledElev,
        maxElevationM: maxSampledElev,
        samplePointCount: sampledCoords.length,
        elevationSource,
      },
      hoseTypes,
    })

    // Add elevation labels at each point
    coords.forEach((pt, i) => {
      if (elevations[i] === 0 && elevations.every(e => e === 0)) return // skip if no real data
      const elLabel = document.createElement('div')
      elLabel.style.cssText = 'background:rgba(59,130,246,0.85);color:white;padding:2px 6px;border-radius:4px;font-size:11px;font-weight:600;white-space:nowrap;pointer-events:none;'
      elLabel.textContent = `${Math.round(elevations[i])}m`
      if (map.current) {
        const m = new maplibregl.Marker({ element: elLabel, anchor: 'top-left' })
          .setLngLat(pt as [number, number])
          .addTo(map.current)
        measureMarkersRef.current.push(m)
      }
    })
  }, [])

  const fetchElevationsAndUpdateRef = useRef(fetchElevationsAndUpdate)
  fetchElevationsAndUpdateRef.current = fetchElevationsAndUpdate

  // Initialize map
  useEffect(() => {
    if (!mapContainer.current || map.current) return

    const initialCenter = project?.mapCenter || { lng: 8.2275, lat: 47.3497 }
    const initialZoom = project?.mapZoom || 15

    map.current = new maplibregl.Map({
      container: mapContainer.current,
      style: {
        version: 8,
        sources: {
          osm: {
            type: 'raster',
            tiles: [
              'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
              'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
              'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png',
            ],
            tileSize: 256,
            maxzoom: 19,
            attribution: '© OpenStreetMap contributors',
          },
          satellite: {
            type: 'raster',
            tiles: [
              'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
            ],
            tileSize: 256,
            attribution: '© Esri, Maxar, Earthstar Geographics',
            maxzoom: 19,
          },
          'swisstopo': {
            type: 'raster',
            tiles: [
              'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/3857/{z}/{x}/{y}.jpeg',
            ],
            tileSize: 256,
            attribution: '© swisstopo',
            maxzoom: 17,
          },
        },
        layers: [
          {
            id: 'osm',
            type: 'raster',
            source: 'osm',
          },
          {
            id: 'satellite',
            type: 'raster',
            source: 'satellite',
            layout: { visibility: 'none' },
          },
          {
            id: 'swisstopo',
            type: 'raster',
            source: 'swisstopo',
            layout: { visibility: 'none' },
          },
        ],
      },
      center: [initialCenter.lng, initialCenter.lat],
      zoom: initialZoom,
      preserveDrawingBuffer: true,
    })

    // Expose map instance to parent for export
    if (externalMapRef) externalMapRef.current = map.current

    // --- WebGL context loss recovery ---
    // When the browser reclaims GPU memory (background tab, memory pressure),
    // the WebGL context is lost and tiles go black. This recovers automatically.
    const canvas = map.current.getCanvas()
    canvas.addEventListener('webglcontextlost', (e) => {
      console.warn('[Map] WebGL context lost — will restore when possible')
      e.preventDefault() // allows context to be restored
    })
    canvas.addEventListener('webglcontextrestored', () => {
      console.info('[Map] WebGL context restored — reloading map style')
      const m = map.current
      if (m) {
        // Force full tile reload by re-setting the style
        const style = m.getStyle()
        if (style) {
          m.setStyle(style)
        }
      }
    })

    // --- Page visibility recovery ---
    // When user switches back to this tab after a while, tiles may be stale/black.
    // Force a resize + tile re-request on visibility change.
    const handleVisibilityChange = () => {
      if (document.visibilityState === 'visible' && map.current) {
        // Small delay to let browser finish tab switch
        setTimeout(() => {
          if (!map.current) return
          map.current.resize()
          // Nudge the map to force tile re-requests
          const center = map.current.getCenter()
          map.current.setCenter(center)
        }, 100)
      }
    }
    document.addEventListener('visibilitychange', handleVisibilityChange)

    // Store cleanup reference
    const cleanupVisibility = () => document.removeEventListener('visibilitychange', handleVisibilityChange)

    map.current.addControl(new maplibregl.NavigationControl(), 'bottom-right')
    map.current.addControl(new maplibregl.ScaleControl(), 'bottom-left')

    // Geolocation: center map on user's position
    const geolocate = new maplibregl.GeolocateControl({
      positionOptions: { enableHighAccuracy: true },
      trackUserLocation: true,
      showAccuracyCircle: false,
    })
    map.current.addControl(geolocate, 'bottom-right')

    // Auto-trigger geolocation when map loads to center on user's real position
    map.current.on('load', () => { geolocate.trigger() })

    map.current.on('load', () => {
      const m = map.current
      if (!m) return
      setIsMapLoaded(true)

      // Guard: skip if sources already exist (React strict mode double-mount)
      if (m.getSource('draw-features')) return

      // Drawing features source
      m.addSource('draw-features', {
        type: 'geojson',
        data: { type: 'FeatureCollection', features: [] },
      })

      // Measurement line source + layer
      m.addSource('measure-line', {
        type: 'geojson',
        data: { type: 'FeatureCollection', features: [] },
      })
      m.addLayer({
        id: 'measure-line-layer',
        type: 'line',
        source: 'measure-line',
        paint: {
          'line-color': '#fbbf24',
          'line-width': 3,
          'line-dasharray': [4, 3],
        },
      })

      // Preview source (for live drawing feedback)
      m.addSource('draw-preview', {
        type: 'geojson',
        data: { type: 'FeatureCollection', features: [] },
      })

      // Create hatched pattern for danger zones
      const hatchCanvas = document.createElement('canvas')
      hatchCanvas.width = 16
      hatchCanvas.height = 16
      const hatchCtx = hatchCanvas.getContext('2d')!
      hatchCtx.clearRect(0, 0, 16, 16)
      hatchCtx.fillStyle = 'rgba(220, 38, 38, 0.15)'
      hatchCtx.fillRect(0, 0, 16, 16)
      hatchCtx.strokeStyle = 'rgba(220, 38, 38, 0.6)'
      hatchCtx.lineWidth = 2
      hatchCtx.beginPath()
      hatchCtx.moveTo(0, 16)
      hatchCtx.lineTo(16, 0)
      hatchCtx.moveTo(-4, 4)
      hatchCtx.lineTo(4, -4)
      hatchCtx.moveTo(12, 20)
      hatchCtx.lineTo(20, 12)
      hatchCtx.stroke()
      const hatchImg = new Image()
      hatchImg.src = hatchCanvas.toDataURL()
      hatchImg.onload = () => {
        if (!m.hasImage('hatch-pattern')) {
          m.addImage('hatch-pattern', hatchImg)
        }
      }

      // Normal polygon fill layer (non-dangerzone)
      m.addLayer({
        id: 'draw-polygons-fill',
        type: 'fill',
        source: 'draw-features',
        filter: ['all', ['==', ['geometry-type'], 'Polygon'], ['!=', ['get', 'isDangerZone'], 1]],
        paint: {
          'fill-color': ['get', 'color'],
          'fill-opacity': 0.25,
        },
      })

      // Dangerzone hatched fill layer
      m.addLayer({
        id: 'draw-dangerzone-fill',
        type: 'fill',
        source: 'draw-features',
        filter: ['all', ['==', ['geometry-type'], 'Polygon'], ['==', ['get', 'isDangerZone'], 1]],
        paint: {
          'fill-color': 'rgba(220, 38, 38, 0.2)',
          'fill-opacity': 1,
        },
      })

      // Polygon outline
      m.addLayer({
        id: 'draw-polygons-outline',
        type: 'line',
        source: 'draw-features',
        filter: ['==', ['geometry-type'], 'Polygon'],
        paint: {
          'line-color': ['get', 'color'],
          'line-width': ['coalesce', ['get', 'width'], 3],
        },
      })

      // Line layer
      m.addLayer({
        id: 'draw-lines',
        type: 'line',
        source: 'draw-features',
        filter: ['==', ['geometry-type'], 'LineString'],
        layout: {
          'line-cap': 'round',
          'line-join': 'round',
        },
        paint: {
          'line-color': ['get', 'color'],
          'line-width': ['coalesce', ['get', 'width'], 3],
        },
      })

      // Point layer
      m.addLayer({
        id: 'draw-points',
        type: 'circle',
        source: 'draw-features',
        filter: ['==', ['geometry-type'], 'Point'],
        paint: {
          'circle-radius': 6,
          'circle-color': ['get', 'color'],
          'circle-stroke-color': '#ffffff',
          'circle-stroke-width': 2,
        },
      })

      // Preview line
      m.addLayer({
        id: 'draw-preview-line',
        type: 'line',
        source: 'draw-preview',
        filter: ['==', ['geometry-type'], 'LineString'],
        layout: {
          'line-cap': 'round',
        },
        paint: {
          'line-color': ['get', 'color'],
          'line-width': 2,
          'line-dasharray': [3, 3],
        },
      })

      // Preview polygon fill
      m.addLayer({
        id: 'draw-preview-fill',
        type: 'fill',
        source: 'draw-preview',
        filter: ['==', ['geometry-type'], 'Polygon'],
        paint: {
          'fill-color': ['get', 'color'],
          'fill-opacity': 0.15,
        },
      })

      // Preview polygon outline
      m.addLayer({
        id: 'draw-preview-outline',
        type: 'line',
        source: 'draw-preview',
        filter: ['==', ['geometry-type'], 'Polygon'],
        paint: {
          'line-color': ['get', 'color'],
          'line-width': 2,
          'line-dasharray': [3, 3],
        },
      })

      // --- Helper: update preview source ---
      function setPreview(data: any) {
        const mapRef = map.current
        if (!mapRef) return
        const src = mapRef.getSource('draw-preview') as maplibregl.GeoJSONSource
        if (src) src.setData(data)
      }
      function clearPreviewData() {
        setPreview({ type: 'FeatureCollection', features: [] })
      }

      // --- ALL drawing event handlers registered directly in map load ---
      m.on('click', (e: maplibregl.MapMouseEvent) => {
        if (!canEditRef.current) return
        const mode = drawModeRef.current
        const color = selectedColorRef.current
        const width = selectedWidthRef.current
        const coords: [number, number] = [e.lngLat.lng, e.lngLat.lat]

        // Tap-to-place: ein zuvor angetipptes Symbol wird hier platziert (Mobil-freundlich,
        // Alternative zum Drag&Drop). Hat Vorrang vor allen Zeichen-Modi.
        if (pendingSymbolRef.current) {
          const p = pendingSymbolRef.current
          onSymbolDropRef.current(p.iconId, coords, p.imageUrl)
          onSymbolPlacedRef.current?.()
          return
        }

        if (mode === 'select') {
          // Detect click near a line/polygon for vertex editing
          const pixel = e.point
          const tolerance = 15
          const currentFeatures = featuresRef.current
          let closestId: string | null = null
          let closestDist = Infinity

          for (const f of currentFeatures) {
            if (f.geometry.type === 'LineString') {
              const lineCoords = f.geometry.coordinates as number[][]
              for (let j = 0; j < lineCoords.length - 1; j++) {
                const p1 = m.project(lineCoords[j] as [number, number])
                const p2 = m.project(lineCoords[j + 1] as [number, number])
                const dist = pointToSegmentDist(pixel.x, pixel.y, p1.x, p1.y, p2.x, p2.y)
                if (dist < closestDist) { closestDist = dist; closestId = f.id }
              }
            } else if (f.geometry.type === 'Polygon') {
              const ring = (f.geometry.coordinates as number[][][])[0]
              for (let j = 0; j < ring.length - 1; j++) {
                const p1 = m.project(ring[j] as [number, number])
                const p2 = m.project(ring[j + 1] as [number, number])
                const dist = pointToSegmentDist(pixel.x, pixel.y, p1.x, p1.y, p2.x, p2.y)
                if (dist < closestDist) { closestDist = dist; closestId = f.id }
              }
            }
          }

          if (closestId && closestDist < tolerance) {
            showVertexMarkersRef.current(closestId)
          } else {
            showVertexMarkersRef.current(null)
          }
          return
        }

        // Eraser mode: click on/near a feature to delete it
        if (mode === 'eraser') {
          const pixel = e.point
          const tolerance = 20 // px
          const currentFeatures = featuresRef.current
          let closestIdx = -1
          let closestDist = Infinity

          for (let i = 0; i < currentFeatures.length; i++) {
            const f = currentFeatures[i]
            const geom = f.geometry

            if (geom.type === 'Point') {
              const projected = m.project(geom.coordinates as [number, number])
              const dx = projected.x - pixel.x
              const dy = projected.y - pixel.y
              const dist = Math.sqrt(dx * dx + dy * dy)
              if (dist < closestDist) { closestDist = dist; closestIdx = i }
            } else if (geom.type === 'LineString') {
              const lineCoords = geom.coordinates as number[][]
              for (let j = 0; j < lineCoords.length - 1; j++) {
                const p1 = m.project(lineCoords[j] as [number, number])
                const p2 = m.project(lineCoords[j + 1] as [number, number])
                const dist = pointToSegmentDist(pixel.x, pixel.y, p1.x, p1.y, p2.x, p2.y)
                if (dist < closestDist) { closestDist = dist; closestIdx = i }
              }
            } else if (geom.type === 'Polygon') {
              const ring = (geom.coordinates as number[][][])[0] || []
              // Check edges
              for (let j = 0; j < ring.length - 1; j++) {
                const p1 = m.project(ring[j] as [number, number])
                const p2 = m.project(ring[j + 1] as [number, number])
                const dist = pointToSegmentDist(pixel.x, pixel.y, p1.x, p1.y, p2.x, p2.y)
                if (dist < closestDist) { closestDist = dist; closestIdx = i }
              }
              // Point-in-polygon test (screen space)
              const projected = ring.map(c => m.project(c as [number, number]))
              let inside = false
              for (let j = 0, k = projected.length - 1; j < projected.length; k = j++) {
                const xi = projected[j].x, yi = projected[j].y
                const xk = projected[k].x, yk = projected[k].y
                if (((yi > pixel.y) !== (yk > pixel.y)) && (pixel.x < (xk - xi) * (pixel.y - yi) / (yk - yi) + xi)) {
                  inside = !inside
                }
              }
              if (inside) { closestDist = 0; closestIdx = i }
            }
          }

          if (closestIdx >= 0 && closestDist < tolerance) {
            const deleted = currentFeatures[closestIdx]
            const newFeatures = currentFeatures.filter((_, i) => i !== closestIdx)
            onFeaturesChangeRef.current(newFeatures)
          }
          return
        }

        if (mode === 'text') {
          onTextPlaceRef.current(coords)
          return
        }

        if (mode === 'point') {
          const newFeature: DrawFeature = {
            id: `point-${Date.now()}`,
            type: 'point',
            geometry: { type: 'Point', coordinates: coords },
            properties: { color },
          }
          onFeaturesChangeRef.current([...featuresRef.current, newFeature])
          return
        }

        if (mode === 'measure') {
          // Add point to measurement (only if not already finished)
          if (drawingRef.current.isDrawing || measureCoordsRef.current.length === 0) {
            measureCoordsRef.current.push(coords)
            drawingRef.current.isDrawing = true
            drawingRef.current.currentCoords = measureCoordsRef.current
            setMeasurePointCount(measureCoordsRef.current.length)
            updateMeasureDisplayRef.current()
          }
          return
        }

        if (mode === 'linestring' || mode === 'polygon' || mode === 'arrow' || mode === 'dangerzone') {
          if (!drawingRef.current.isDrawing) {
            drawingRef.current.isDrawing = true
            drawingRef.current.currentCoords = [coords]
            drawingRef.current.startPoint = coords
            setDrawingPointCount(1)
          } else {
            drawingRef.current.currentCoords.push(coords)
            setDrawingPointCount(drawingRef.current.currentCoords.length)
          }
          return
        }

        if (mode === 'rectangle' || mode === 'circle') {
          if (!drawingRef.current.startPoint) {
            drawingRef.current.startPoint = coords
            drawingRef.current.isDrawing = true
          } else {
            const start = drawingRef.current.startPoint
            if (mode === 'rectangle') {
              const feat: DrawFeature = {
                id: `rect-${Date.now()}`,
                type: 'rectangle',
                geometry: {
                  type: 'Polygon',
                  coordinates: [[
                    [start[0], start[1]], [coords[0], start[1]],
                    [coords[0], coords[1]], [start[0], coords[1]], [start[0], start[1]],
                  ]],
                },
                properties: { color, width },
              }
              onFeaturesChangeRef.current([...featuresRef.current, feat])
            } else {
              const circleCoords = generateCircleCoords(start, coords)
              const feat: DrawFeature = {
                id: `circle-${Date.now()}`,
                type: 'circle',
                geometry: { type: 'Polygon', coordinates: [circleCoords] },
                properties: { color, width },
              }
              onFeaturesChangeRef.current([...featuresRef.current, feat])
            }
            drawingRef.current.isDrawing = false
            drawingRef.current.startPoint = null
            drawingRef.current.currentCoords = []
            clearPreviewData()
          }
        }
      })

      m.on('dblclick', (e: maplibregl.MapMouseEvent) => {
        if (!drawingRef.current.isDrawing) return
        e.preventDefault()
        const mode = drawModeRef.current

        // Measure mode: stop adding points, keep line + labels, fetch elevation
        if (mode === 'measure') {
          drawingRef.current.isDrawing = false
          drawingRef.current.currentCoords = []
          drawingRef.current.startPoint = null
          clearPreviewData()
          fetchElevationsAndUpdateRef.current()
          return
        }

        if (!canEditRef.current) return
        const color = selectedColorRef.current
        const width = selectedWidthRef.current
        const coords = drawingRef.current.currentCoords

        if (coords.length < 2) {
          drawingRef.current.isDrawing = false
          drawingRef.current.currentCoords = []
          drawingRef.current.startPoint = null
          clearPreviewData()
          return
        }

        if (mode === 'linestring') {
          onFeaturesChangeRef.current([...featuresRef.current, {
            id: `line-${Date.now()}`, type: 'linestring',
            geometry: { type: 'LineString', coordinates: coords },
            properties: { color, width },
          }])
        } else if (mode === 'arrow') {
          onFeaturesChangeRef.current([...featuresRef.current, {
            id: `arrow-${Date.now()}`, type: 'arrow',
            geometry: { type: 'LineString', coordinates: coords },
            properties: { color, width, isArrow: true },
          }])
        } else if (mode === 'polygon' && coords.length >= 3) {
          onFeaturesChangeRef.current([...featuresRef.current, {
            id: `polygon-${Date.now()}`, type: 'polygon',
            geometry: { type: 'Polygon', coordinates: [[...coords, coords[0]]] },
            properties: { color, width },
          }])
        } else if (mode === 'dangerzone' && coords.length >= 3) {
          onFeaturesChangeRef.current([...featuresRef.current, {
            id: `danger-${Date.now()}`, type: 'dangerzone',
            geometry: { type: 'Polygon', coordinates: [[...coords, coords[0]]] },
            properties: { color: '#dc2626', width: 2, isDangerZone: true },
          }])
        }

        drawingRef.current.isDrawing = false
        drawingRef.current.currentCoords = []
        drawingRef.current.startPoint = null
        setDrawingPointCount(0)
        clearPreviewData()
      })

      const handleDrawStart = (e: maplibregl.MapMouseEvent | maplibregl.MapTouchEvent) => {
        if (!canEditRef.current || drawModeRef.current !== 'freehand') return
        // Multi-touch = zoom/pan, not drawing
        const origEvent = (e as maplibregl.MapTouchEvent).originalEvent as TouchEvent | undefined
        if (origEvent?.touches && origEvent.touches.length > 1) return
        drawingRef.current.isDrawing = true
        drawingRef.current.currentCoords = [[e.lngLat.lng, e.lngLat.lat]]
      }
      m.on('mousedown', handleDrawStart)
      m.on('touchstart', handleDrawStart)

      const handleDrawMove = (e: maplibregl.MapMouseEvent | maplibregl.MapTouchEvent) => {
        const mode = drawModeRef.current
        const color = selectedColorRef.current
        const coords: [number, number] = [e.lngLat.lng, e.lngLat.lat]

        if (mode === 'measure' && drawingRef.current.isDrawing && drawingRef.current.currentCoords.length > 0) {
          setPreview({
            type: 'FeatureCollection',
            features: [{
              type: 'Feature',
              geometry: { type: 'LineString', coordinates: [...drawingRef.current.currentCoords, coords] },
              properties: { color: '#fbbf24' },
            }],
          })
          return
        }

        if (mode === 'freehand' && drawingRef.current.isDrawing) {
          // Cancel freehand if second finger added (user wants to zoom)
          const origEvt = (e as maplibregl.MapTouchEvent).originalEvent as TouchEvent | undefined
          if (origEvt?.touches && origEvt.touches.length > 1) {
            drawingRef.current.isDrawing = false
            drawingRef.current.currentCoords = []
            clearPreviewData()
            return
          }
          drawingRef.current.currentCoords.push(coords)
          setPreview({
            type: 'FeatureCollection',
            features: [{
              type: 'Feature',
              geometry: { type: 'LineString', coordinates: drawingRef.current.currentCoords },
              properties: { color },
            }],
          })
          return
        }

        if (!drawingRef.current.isDrawing && !drawingRef.current.startPoint) return

        if (mode === 'linestring' || mode === 'arrow') {
          if (drawingRef.current.currentCoords.length > 0) {
            setPreview({
              type: 'FeatureCollection',
              features: [{
                type: 'Feature',
                geometry: { type: 'LineString', coordinates: [...drawingRef.current.currentCoords, coords] },
                properties: { color },
              }],
            })
          }
        } else if (mode === 'polygon' || mode === 'dangerzone') {
          if (drawingRef.current.currentCoords.length > 0) {
            const pc = [...drawingRef.current.currentCoords, coords, drawingRef.current.currentCoords[0]]
            const previewColor = mode === 'dangerzone' ? '#dc2626' : color
            setPreview({
              type: 'FeatureCollection',
              features: [{
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [pc] },
                properties: { color: previewColor },
              }],
            })
          }
        } else if (mode === 'rectangle' && drawingRef.current.startPoint) {
          const s = drawingRef.current.startPoint
          setPreview({
            type: 'FeatureCollection',
            features: [{
              type: 'Feature',
              geometry: { type: 'Polygon', coordinates: [[ [s[0], s[1]], [coords[0], s[1]], [coords[0], coords[1]], [s[0], coords[1]], [s[0], s[1]] ]] },
              properties: { color },
            }],
          })
        } else if (mode === 'circle' && drawingRef.current.startPoint) {
          const s = drawingRef.current.startPoint
          const cc = generateCircleCoords(s, coords)
          setPreview({
            type: 'FeatureCollection',
            features: [{
              type: 'Feature',
              geometry: { type: 'Polygon', coordinates: [cc] },
              properties: { color },
            }],
          })
        }
      }
      m.on('mousemove', handleDrawMove)
      m.on('touchmove', handleDrawMove)

      const handleDrawEnd = () => {
        if (!canEditRef.current || drawModeRef.current !== 'freehand' || !drawingRef.current.isDrawing) return
        const coords = drawingRef.current.currentCoords
        if (coords.length >= 2) {
          onFeaturesChangeRef.current([...featuresRef.current, {
            id: `freehand-${Date.now()}`, type: 'freehand',
            geometry: { type: 'LineString', coordinates: coords },
            properties: { color: selectedColorRef.current, width: selectedWidthRef.current },
          }])
        }
        drawingRef.current.isDrawing = false
        drawingRef.current.currentCoords = []
        drawingRef.current.startPoint = null
        clearPreviewData()
        // Apple-style: auto-return to select after freehand stroke
        onDrawModeChangeRef.current?.('select')
      }
      m.on('mouseup', handleDrawEnd)
      m.on('touchend', handleDrawEnd)
    })

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

  // Update map center only when a DIFFERENT project is loaded (by ID change)
  const prevProjectIdRef = useRef<string | null>(null)
  useEffect(() => {
    if (map.current && project && project.id !== prevProjectIdRef.current) {
      prevProjectIdRef.current = project.id
      map.current.flyTo({
        center: [project.mapCenter.lng, project.mapCenter.lat],
        zoom: project.mapZoom,
        duration: 1500,
      })
    }
  }, [project])

  // Select a symbol for Moveable editing
  const selectSymbol = useCallback((featureId: string, wrapperEl: HTMLElement, innerEl: HTMLElement) => {
    const prev = selectedSymbolRef.current

    // Save pending changes from previous symbol
    if (prev && prev.id !== featureId) {
      const updated = featuresRef.current.map(f =>
        f.id === prev.id
          ? { ...f, properties: { ...f.properties, rotation: prev.rotation, scale: prev.scale } }
          : f
      )
      onFeaturesChangeRef.current(updated)
      // Remove visual indicator from previous
      prev.wrapperEl.style.outline = ''
      prev.wrapperEl.style.outlineOffset = ''
      prev.wrapperEl.style.boxShadow = ''
    }

    const feat = featuresRef.current.find(f => f.id === featureId)
    if (!feat) return

    const rotation = (feat.properties.rotation as number) || 0
    const scale = (feat.properties.scale as number) || 1

    const featureType = feat.type === 'text' ? 'text' as const : 'symbol' as const
    const baseFontSize = featureType === 'text' ? ((feat.properties.fontSize as number) || 14) : undefined

    selectedSymbolRef.current = { id: featureId, wrapperEl, innerEl, rotation, scale, featureType, baseFontSize }

    // Visual selection indicator
    wrapperEl.style.outline = '2px solid #3b82f6'
    wrapperEl.style.outlineOffset = '4px'
    wrapperEl.style.boxShadow = '0 0 16px rgba(59,130,246,0.4)'

    // Bump key to force Moveable re-mount with new target
    setSymbolSelectKey(k => k + 1)
    setIsSymbolSelected(true)
  }, [])

  // Deselect symbol and save pending changes
  const deselectSymbol = useCallback(() => {
    const sel = selectedSymbolRef.current
    if (!sel) return

    // Save rotation/scale to features
    const updated = featuresRef.current.map(f =>
      f.id === sel.id
        ? { ...f, properties: { ...f.properties, rotation: sel.rotation, scale: sel.scale } }
        : f
    )
    onFeaturesChangeRef.current(updated)

    // Remove visual selection
    sel.wrapperEl.style.outline = ''
    sel.wrapperEl.style.outlineOffset = ''
    sel.wrapperEl.style.boxShadow = ''

    selectedSymbolRef.current = null
    setIsSymbolSelected(false)
  }, [])

  // Delete selected symbol
  const deleteSelectedSymbol = useCallback(() => {
    const sel = selectedSymbolRef.current
    if (!sel) return

    const updated = featuresRef.current.filter(f => f.id !== sel.id)
    onFeaturesChangeRef.current(updated)

    selectedSymbolRef.current = null
    setIsSymbolSelected(false)
  }, [])

  // Update features on map
  useEffect(() => {
    if (!map.current || !isMapLoaded) return

    // Remember selected symbol ID so we can re-attach after marker recreation
    const reselectedId = selectedSymbolRef.current?.id || null
    const reselectedRotation = selectedSymbolRef.current?.rotation || 0
    const reselectedScale = selectedSymbolRef.current?.scale || 1

    const source = map.current.getSource('draw-features') as maplibregl.GeoJSONSource
    if (source) {
      const geojsonFeatures = features
        .filter(f => f.type !== 'symbol' && f.type !== 'text')
        .map(f => ({
          type: 'Feature' as const,
          geometry: f.geometry as any,
          properties: {
            id: f.id,
            color: (f.properties.color as string) || '#000000',
            width: (f.properties.width as number) || 3,
            isDangerZone: f.properties.isDangerZone ? 1 : 0,
          },
        }))

      source.setData({
        type: 'FeatureCollection' as const,
        features: geojsonFeatures,
      })
    }

    // Clear old markers and their cleanup functions
    markerCleanupsRef.current.forEach(fn => fn())
    markerCleanupsRef.current = []
    markersRef.current.forEach(m => m.remove())
    markersRef.current = []

    // Add arrowheads for arrow features
    features
      .filter(f => f.type === 'arrow')
      .forEach(f => {
        if (f.geometry.type === 'LineString' && map.current) {
          const lineCoords = f.geometry.coordinates as number[][]
          if (lineCoords.length < 2) return

          // Geographic bearing from p1 to p2 (works for short distances)
          const p1 = lineCoords[lineCoords.length - 2]
          const p2 = lineCoords[lineCoords.length - 1]
          const dLng = p2[0] - p1[0]
          const dLat = p2[1] - p1[1]
          // atan2(dLng, dLat) gives angle from north (up), clockwise — matches CSS triangle ▲ default
          const geoBearing = Math.atan2(dLng, dLat) * (180 / Math.PI)

          const color = (f.properties.color as string) || '#000000'
          const arrowEl = document.createElement('div')
          arrowEl.style.cssText = `
            width: 0; height: 0;
            border-left: 12px solid transparent;
            border-right: 12px solid transparent;
            border-bottom: 24px solid ${color};
            transform: rotate(${geoBearing}deg);
            transform-origin: center center;
            pointer-events: none;
          `

          const marker = new maplibregl.Marker({ element: arrowEl, anchor: 'center', rotationAlignment: 'map' })
            .setLngLat(p2 as [number, number])
            .addTo(map.current)
          markersRef.current.push(marker)
        }
      })

    // Add line/polygon label markers at midpoints
    features
      .filter(f => f.properties.label && (f.geometry.type === 'LineString' || f.geometry.type === 'Polygon'))
      .forEach(f => {
        if (!map.current) return
        const label = f.properties.label as string
        let midpoint: [number, number]

        if (f.geometry.type === 'LineString') {
          const coords = f.geometry.coordinates as number[][]
          const midIdx = Math.floor(coords.length / 2)
          if (coords.length === 2) {
            midpoint = [(coords[0][0] + coords[1][0]) / 2, (coords[0][1] + coords[1][1]) / 2]
          } else {
            midpoint = coords[midIdx] as [number, number]
          }
        } else {
          // Polygon: use centroid of first ring
          const ring = (f.geometry.coordinates as number[][][])[0]
          const len = ring.length - 1 // exclude closing point
          let cx = 0, cy = 0
          for (let i = 0; i < len; i++) { cx += ring[i][0]; cy += ring[i][1] }
          midpoint = [cx / len, cy / len]
        }

        // Apply stored label offset if present
        const labelOffset = f.properties.labelOffset as [number, number] | undefined
        if (labelOffset) {
          midpoint = [midpoint[0] + labelOffset[0], midpoint[1] + labelOffset[1]]
        }

        const el = document.createElement('div')
        const isDanger = f.type === 'dangerzone'
        el.style.cssText = `
          background: ${isDanger ? 'rgba(220,38,38,0.85)' : 'rgba(0,0,0,0.82)'};
          color: #fff;
          padding: 3px 8px;
          border-radius: 4px;
          font-size: 13px;
          font-weight: 600;
          white-space: nowrap;
          pointer-events: ${canEdit ? 'auto' : 'none'};
          letter-spacing: 0.3px;
          border: 1px solid ${isDanger ? '#dc2626' : 'rgba(255,255,255,0.4)'};
          box-shadow: 0 1px 4px rgba(0,0,0,0.3);
          cursor: ${canEdit ? 'grab' : 'default'};
          transform: translate(0,0);
          will-change: transform;
        `
        // Build label content: label text + line length & hose count for LineStrings
        if (f.geometry.type === 'LineString') {
          const lineCoords = f.geometry.coordinates as number[][]
          let totalLen = 0
          for (let i = 1; i < lineCoords.length; i++) {
            totalLen += haversineDistance(lineCoords[i - 1], lineCoords[i])
          }
          const hoseLength = 20 // meters per hose piece
          const hoseCount = Math.ceil(totalLen / hoseLength)
          const lenText = totalLen < 1000 ? `${Math.round(totalLen)}m` : `${(totalLen / 1000).toFixed(2)}km`

          const labelLine = document.createElement('div')
          labelLine.textContent = label
          labelLine.style.cssText = 'font-size:13px;font-weight:700;line-height:1.3;'

          const infoLine = document.createElement('div')
          infoLine.textContent = `${lenText} / ${hoseCount} Schl.`
          infoLine.style.cssText = 'font-size:10px;opacity:0.85;line-height:1.3;font-weight:500;'

          el.appendChild(labelLine)
          el.appendChild(infoLine)
        } else {
          // Polygon: show label + area
          const ring = (f.geometry.coordinates as number[][][])[0]
          const area = polygonArea(ring)
          const areaText = area < 10000 ? `${Math.round(area)} m²` : `${(area / 10000).toFixed(2)} ha`

          const labelLine = document.createElement('div')
          labelLine.textContent = label
          labelLine.style.cssText = 'font-size:13px;font-weight:700;line-height:1.3;'

          const infoLine = document.createElement('div')
          infoLine.textContent = areaText
          infoLine.style.cssText = 'font-size:10px;opacity:0.85;line-height:1.3;font-weight:500;'

          el.appendChild(labelLine)
          el.appendChild(infoLine)
        }

        // Double-click to edit label — only in select mode
        if (canEdit) {
          el.addEventListener('dblclick', (e) => {
            e.stopPropagation()
            if (drawModeRef.current !== 'select') return
            const currentLabel = (featuresRef.current.find(feat => feat.id === f.id)?.properties.label as string) || ''
            setInlineEdit({ featureId: f.id, type: 'label', value: currentLabel })
          })
        }

        const marker = new maplibregl.Marker({ element: el, anchor: 'center', draggable: canEdit, rotationAlignment: 'map' })
          .setLngLat(midpoint)
          .addTo(map.current)

        // Save label position offset on drag end
        if (canEdit) {
          marker.on('dragend', () => {
            const newPos = marker.getLngLat()
            // Calculate midpoint without offset to get the base midpoint
            let baseMid: [number, number]
            const feat = featuresRef.current.find(feat => feat.id === f.id)
            if (!feat) return
            if (feat.geometry.type === 'LineString') {
              const coords = feat.geometry.coordinates as number[][]
              const midIdx = Math.floor(coords.length / 2)
              if (coords.length === 2) {
                baseMid = [(coords[0][0] + coords[1][0]) / 2, (coords[0][1] + coords[1][1]) / 2]
              } else {
                baseMid = coords[midIdx] as [number, number]
              }
            } else {
              const ring = (feat.geometry.coordinates as number[][][])[0]
              const len = ring.length - 1
              let cx = 0, cy = 0
              for (let i = 0; i < len; i++) { cx += ring[i][0]; cy += ring[i][1] }
              baseMid = [cx / len, cy / len]
            }
            const offset: [number, number] = [newPos.lng - baseMid[0], newPos.lat - baseMid[1]]
            const updated = featuresRef.current.map(pf =>
              pf.id === f.id ? { ...pf, properties: { ...pf.properties, labelOffset: offset } } : pf
            )
            onFeaturesChangeRef.current(updated)
          })
        }

        markersRef.current.push(marker)
      })

    // Add symbol markers
    features
      .filter(f => f.type === 'symbol')
      .forEach(f => {
        if (f.geometry.type === 'Point' && map.current) {
          const coords = f.geometry.coordinates as [number, number]
          const scale = (f.properties.scale as number) || 1
          const rotation = (f.properties.rotation as number) || 0
          const imageUrl = f.properties.imageUrl as string
          const iconId = f.properties.iconId as string

          // Resolve image URL: uploaded symbols have imageUrl, builtin use data URI
          let imgSrc = ''
          if (imageUrl) {
            imgSrc = imageUrl
          } else {
            const symbolDef = getSymbolById(iconId)
            if (symbolDef) imgSrc = getSymbolDataUri(symbolDef)
          }

          const baseSize = 32
          const placementZoom = (f.properties.placementZoom as number) || 17
          const currentZoom = map.current.getZoom()
          const zoomFactor = Math.pow(2, currentZoom - placementZoom)
          const size = Math.max(8, Math.min(400, baseSize * scale * zoomFactor))

          // Wrapper: used as MapLibre marker element (MapLibre sets transform on this)
          const wrapper = document.createElement('div')
          wrapper.className = 'symbol-marker-wrapper'
          wrapper.dataset.featureId = f.id
          wrapper.dataset.baseScale = String(scale)
          wrapper.dataset.placementZoom = String(placementZoom)
          wrapper.style.width = `${size}px`
          wrapper.style.height = `${size}px`
          wrapper.style.cursor = canEdit ? 'move' : 'default'

          // Inner element: rotation/scale applied here (won't interfere with MapLibre positioning)
          const inner = document.createElement('div')
          inner.className = 'symbol-marker'
          inner.style.width = '100%'
          inner.style.height = '100%'
          inner.style.transformOrigin = 'center center'
          inner.style.transform = `rotate(${rotation}deg)`
          inner.style.transition = 'transform 0.1s'

          // Try primary image source, with fallback chain for broken/deleted icons
          const applyImage = (src: string) => {
            inner.style.backgroundImage = `url("${src}")`
            inner.style.backgroundSize = 'contain'
            inner.style.backgroundRepeat = 'no-repeat'
            inner.style.backgroundPosition = 'center'
          }

          if (imgSrc) {
            applyImage(imgSrc)
            // If image fails to load (404 from deleted icon), try fallback
            const testImg = new Image()
            testImg.onload = () => {} // OK
            testImg.onerror = () => {
              // Fallback 1: Try API endpoint with iconId
              if (iconId) {
                const fallbackUrl = `/api/icons/${iconId}/image`
                if (fallbackUrl !== imgSrc) {
                  const test2 = new Image()
                  test2.onload = () => applyImage(fallbackUrl)
                  test2.onerror = () => {
                    // Fallback 2: Show broken symbol indicator
                    inner.style.backgroundImage = 'none'
                    inner.style.display = 'flex'
                    inner.style.alignItems = 'center'
                    inner.style.justifyContent = 'center'
                    inner.style.border = '2px dashed #ef4444'
                    inner.style.borderRadius = '4px'
                    inner.style.backgroundColor = 'rgba(239,68,68,0.1)'
                    inner.innerHTML = '<span style="font-size:10px;color:#ef4444;text-align:center">⚠️</span>'
                  }
                  test2.src = fallbackUrl
                } else {
                  inner.style.backgroundImage = 'none'
                  inner.style.display = 'flex'
                  inner.style.alignItems = 'center'
                  inner.style.justifyContent = 'center'
                  inner.style.border = '2px dashed #ef4444'
                  inner.style.borderRadius = '4px'
                  inner.style.backgroundColor = 'rgba(239,68,68,0.1)'
                  inner.innerHTML = '<span style="font-size:10px;color:#ef4444;text-align:center">⚠️</span>'
                }
              }
            }
            testImg.src = imgSrc
          } else if (iconId) {
            // No imageUrl at all — try loading via API
            applyImage(`/api/icons/${iconId}/image`)
          }

          wrapper.appendChild(inner)

          // Click/tap to select symbol for Moveable editing — ONLY in 'select' mode
          if (canEdit) {
            let dragHappened = false
            wrapper.addEventListener('mousedown', () => { dragHappened = false })
            wrapper.addEventListener('mousemove', () => { dragHappened = true })
            wrapper.addEventListener('click', (e) => {
              if (dragHappened) return
              if (drawModeRef.current !== 'select') return // Only select in pointer/select mode
              e.stopPropagation()
              selectSymbol(f.id, wrapper, inner)
            })
            let touchStartPos: { x: number; y: number } | null = null
            wrapper.addEventListener('touchstart', (e) => {
              touchStartPos = { x: e.touches[0].clientX, y: e.touches[0].clientY }
            }, { passive: true })
            wrapper.addEventListener('touchend', (e) => {
              if (!touchStartPos) return
              if (drawModeRef.current !== 'select') { touchStartPos = null; return }
              const touch = e.changedTouches[0]
              const dist = Math.hypot(touch.clientX - touchStartPos.x, touch.clientY - touchStartPos.y)
              if (dist < 10) selectSymbol(f.id, wrapper, inner)
              touchStartPos = null
            })
            wrapper.addEventListener('contextmenu', (e) => e.preventDefault())
          }

          try {
            const marker = new maplibregl.Marker({ element: wrapper, draggable: canEdit, anchor: 'center', rotationAlignment: 'map' })
              .setLngLat(coords)
              .addTo(map.current)

            if (canEdit) {
              marker.on('dragstart', () => {
                wrapper.style.outline = '3px solid #3b82f6'
                wrapper.style.outlineOffset = '2px'
                wrapper.style.borderRadius = '6px'
                wrapper.style.boxShadow = '0 0 12px rgba(59,130,246,0.5)'
                wrapper.style.zIndex = '100'
              })
              marker.on('dragend', () => {
                wrapper.style.outline = ''
                wrapper.style.outlineOffset = ''
                wrapper.style.boxShadow = ''
                wrapper.style.zIndex = ''
                const newPos = marker.getLngLat()
                const updated = featuresRef.current.map(feat =>
                  feat.id === f.id
                    ? { ...feat, geometry: { ...feat.geometry, coordinates: [newPos.lng, newPos.lat] } }
                    : feat
                )
                onFeaturesChangeRef.current(updated)
              })
            }

            markersRef.current.push(marker)
          } catch (err) {
            console.error('[Markers] Symbol marker error:', f.id, err)
          }
        }
      })

    // Add text markers
    features
      .filter(f => f.type === 'text')
      .forEach(f => {
        if (f.geometry.type === 'Point' && map.current) {
          const coords = f.geometry.coordinates as [number, number]
          const fontSize = (f.properties.fontSize as number) || 14
          const scale = (f.properties.scale as number) || 1

          // Wrapper for Moveable targeting (like symbols)
          const wrapper = document.createElement('div')
          wrapper.className = 'symbol-marker-wrapper'
          wrapper.dataset.featureId = f.id
          wrapper.dataset.baseScale = String(scale)
          wrapper.style.cursor = canEdit ? 'move' : 'default'
          wrapper.style.display = 'inline-block'

          const el = document.createElement('div')
          el.className = 'text-marker symbol-marker'
          el.style.cssText = `
            font-size: ${fontSize * scale}px;
            font-weight: bold;
            color: ${(f.properties.color as string) || '#000000'};
            white-space: nowrap;
            text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
            user-select: none;
            pointer-events: auto;
            transform-origin: center center;
          `
          el.textContent = (f.properties.text as string) || ''
          wrapper.appendChild(el)

          const marker = new maplibregl.Marker({ element: wrapper, draggable: canEdit, anchor: 'center', rotationAlignment: 'map' })
            .setLngLat(coords)
            .addTo(map.current)

          if (canEdit) {
            marker.on('dragend', () => {
              const newPos = marker.getLngLat()
              const updated = featuresRef.current.map(feat =>
                feat.id === f.id
                  ? { ...feat, geometry: { ...feat.geometry, coordinates: [newPos.lng, newPos.lat] } }
                  : feat
              )
              onFeaturesChangeRef.current(updated)
            })

            // Click to select for Moveable editing (resize) — ONLY in select mode
            let dragHappened = false
            wrapper.addEventListener('mousedown', () => { dragHappened = false })
            wrapper.addEventListener('mousemove', () => { dragHappened = true })
            wrapper.addEventListener('click', (e) => {
              if (dragHappened) return
              if (drawModeRef.current !== 'select') return
              e.stopPropagation()
              selectSymbol(f.id, wrapper, el)
            })
            let touchStartPos: { x: number; y: number } | null = null
            wrapper.addEventListener('touchstart', (e) => {
              touchStartPos = { x: e.touches[0].clientX, y: e.touches[0].clientY }
            }, { passive: true })
            wrapper.addEventListener('touchend', (e) => {
              if (!touchStartPos) return
              if (drawModeRef.current !== 'select') { touchStartPos = null; return }
              const touch = e.changedTouches[0]
              const dist = Math.hypot(touch.clientX - touchStartPos.x, touch.clientY - touchStartPos.y)
              if (dist < 10) selectSymbol(f.id, wrapper, el)
              touchStartPos = null
            })

            // Double-click to edit text — only in select mode
            el.addEventListener('dblclick', (e) => {
              e.stopPropagation()
              if (drawModeRef.current !== 'select') return
              const currentText = (featuresRef.current.find(feat => feat.id === f.id)?.properties.text as string) || ''
              setInlineEdit({ featureId: f.id, type: 'text', value: currentText })
            })
          }

          markersRef.current.push(marker)
        }
      })

    // Re-attach Moveable selection to new DOM elements after marker recreation
    if (reselectedId) {
      const newWrapper = document.querySelector(`.symbol-marker-wrapper[data-feature-id="${reselectedId}"]`) as HTMLElement | null
      if (newWrapper) {
        const newInner = newWrapper.querySelector('.symbol-marker') as HTMLElement | null
        if (newInner) {
          const reselFeat = features.find(f => f.id === reselectedId)
          selectedSymbolRef.current = {
            id: reselectedId,
            wrapperEl: newWrapper,
            innerEl: newInner,
            rotation: reselectedRotation,
            scale: reselectedScale,
            featureType: reselFeat?.type === 'text' ? 'text' : 'symbol',
            baseFontSize: reselFeat?.type === 'text' ? ((reselFeat.properties.fontSize as number) || 14) : undefined,
          }
          // Re-apply visual selection indicator
          newWrapper.style.outline = '2px solid #3b82f6'
          newWrapper.style.outlineOffset = '4px'
          newWrapper.style.boxShadow = '0 0 16px rgba(59,130,246,0.4)'
          // Bump key to re-mount Moveable with new target
          setSymbolSelectKey(k => k + 1)
        } else {
          selectedSymbolRef.current = null
          setIsSymbolSelected(false)
        }
      } else {
        // Symbol was deleted
        selectedSymbolRef.current = null
        setIsSymbolSelected(false)
      }
    }

    // Re-show vertex markers if a line/polygon is still selected after feature update
    if (selectedLineIdRef.current) {
      const stillExists = features.find(f => f.id === selectedLineIdRef.current)
      if (stillExists) {
        showVertexMarkersRef.current(selectedLineIdRef.current)
      } else {
        showVertexMarkersRef.current(null)
      }
    }
  }, [features, isMapLoaded, canEdit])

  // Reset measurement state when leaving measure mode (but keep info panel — user closes it)
  useEffect(() => {
    if (drawMode !== 'measure') {
      measureMarkersRef.current.forEach(m => m.remove())
      measureMarkersRef.current = []
      measureCoordsRef.current = []
      setMeasurePointCount(0)
      setMeasureFinished(false)
      // Clear the measure line source
      if (map.current) {
        const src = map.current.getSource('measure-line') as maplibregl.GeoJSONSource
        if (src) src.setData({ type: 'FeatureCollection', features: [] })
      }
      // Info panel stays — user closes it with X
    }
  }, [drawMode])

  // Keep Moveable box in sync with symbol position during map pan/zoom
  // AND update symbol sizes on zoom (geo-scaling: symbols scale with the map)
  useEffect(() => {
    if (!map.current) return
    const handleMapMove = () => {
      if (moveableRef.current && selectedSymbolRef.current) {
        moveableRef.current.updateRect()
      }
    }
    const handleZoom = () => {
      handleMapMove()
      // Update all symbol marker sizes based on current zoom
      const currentZoom = map.current?.getZoom() || 17
      const baseSize = 32
      document.querySelectorAll<HTMLElement>('.symbol-marker-wrapper').forEach(wrapper => {
        const bScale = parseFloat(wrapper.dataset.baseScale || '1')
        const pZoom = parseFloat(wrapper.dataset.placementZoom || '17')
        const zoomFactor = Math.pow(2, currentZoom - pZoom)
        const size = Math.max(8, Math.min(400, baseSize * bScale * zoomFactor))
        wrapper.style.width = `${size}px`
        wrapper.style.height = `${size}px`
      })
    }
    map.current.on('move', handleMapMove)
    map.current.on('zoom', handleZoom)
    return () => {
      map.current?.off('move', handleMapMove)
      map.current?.off('zoom', handleZoom)
    }
  }, [isSymbolSelected])

  // When not in 'select' mode, make symbol/text markers click-through so drawing works through them
  useEffect(() => {
    const pointerEvents = drawMode === 'select' ? 'auto' : 'none'
    markersRef.current.forEach(m => {
      const el = m.getElement()
      if (el && (el.classList.contains('symbol-marker-wrapper') || el.classList.contains('text-marker'))) {
        el.style.pointerEvents = pointerEvents
      }
    })
    // Also deselect any selected symbol when switching away from select mode
    if (drawMode !== 'select' && selectedSymbolRef.current) {
      deselectSymbol()
    }
    // Clear vertex editing when leaving select mode
    if (drawMode !== 'select' && selectedLineIdRef.current) {
      showVertexMarkersRef.current(null)
    }
  }, [drawMode, deselectSymbol])

  // ESC to cancel drawing, DEL to delete selected symbol/line/polygon
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      // DEL / Backspace → delete selected symbol or line/polygon
      if (e.key === 'Delete' || e.key === 'Backspace') {
        const tag = (e.target as HTMLElement)?.tagName
        if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (e.target as HTMLElement)?.isContentEditable) return
        // Delete selected symbol/text
        if (selectedSymbolRef.current) {
          e.preventDefault()
          deleteSelectedSymbol()
          return
        }
        // Delete selected line/polygon/arrow (vertex-editing selection)
        if (selectedLineIdRef.current) {
          e.preventDefault()
          const updated = featuresRef.current.filter(f => f.id !== selectedLineIdRef.current)
          onFeaturesChangeRef.current(updated)
          showVertexMarkersRef.current(null)
          return
        }
      }
      if (e.key === 'Escape') {
        // In measure mode: finalize (keep line + labels), just stop adding
        if (drawModeRef.current === 'measure' && drawingRef.current.isDrawing) {
          drawingRef.current.isDrawing = false
          drawingRef.current.currentCoords = []
          drawingRef.current.startPoint = null
          if (map.current) {
            const src = map.current.getSource('draw-preview') as maplibregl.GeoJSONSource
            if (src) src.setData({ type: 'FeatureCollection', features: [] })
          }
          // Fetch elevation for finalized measurement
          fetchElevationsAndUpdateRef.current()
          return
        }
        // Other modes: cancel
        drawingRef.current.isDrawing = false
        drawingRef.current.currentCoords = []
        drawingRef.current.startPoint = null
        if (map.current) {
          const src = map.current.getSource('draw-preview') as maplibregl.GeoJSONSource
          if (src) src.setData({ type: 'FeatureCollection', features: [] })
        }
      }
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [deleteSelectedSymbol])

  // Drop zone for symbols — use stable ref connection (no inline ref callback)
  const [, drop] = useDrop(() => ({
    accept: 'SYMBOL',
    drop: (item: { iconId: string; imageUrl?: string }, monitor) => {
      if (!map.current || !mapContainer.current) return
      const offset = monitor.getClientOffset()
      if (!offset) return
      const rect = mapContainer.current.getBoundingClientRect()
      const lngLat = map.current.unproject([offset.x - rect.left, offset.y - rect.top])
      onSymbolDropRef.current(item.iconId, [lngLat.lng, lngLat.lat], item.imageUrl)
      // Reset draw mode to select after placing a symbol
      onDrawModeChangeRef.current?.('select')
      return { dropped: true }
    },
  }), [])

  // Connect drop target via useEffect (avoids inline ref re-registration during drag)
  useEffect(() => {
    if (mapContainer.current) {
      drop(mapContainer.current)
    }
  }, [drop])

  // Set cursor directly on the MapLibre canvas to override its default grab cursor
  useEffect(() => {
    if (!map.current) return
    const canvas = map.current.getCanvas()
    if (!canvas) return

    let cursor = ''
    if (canEdit) {
      switch (drawMode) {
        case 'point': case 'linestring': case 'polygon':
        case 'rectangle': case 'circle': case 'arrow': case 'freehand':
        case 'measure': case 'dangerzone':
          cursor = 'crosshair'
          break
        case 'eraser':
          cursor = 'pointer'
          break
        case 'text':
          cursor = 'text'
          break
        default:
          cursor = ''
      }
    }

    if (cursor) {
      canvas.style.cursor = cursor
      map.current.getContainer().style.cursor = cursor
    } else {
      canvas.style.cursor = ''
      map.current.getContainer().style.cursor = ''
    }
  }, [drawMode, canEdit, isMapLoaded])

  // Cursor-following tooltip for drawing/measuring modes
  useEffect(() => {
    const container = mapContainer.current
    if (!container) return

    const onMove = (e: MouseEvent) => {
      const rect = container.getBoundingClientRect()
      const x = e.clientX - rect.left + 16
      const y = e.clientY - rect.top - 10
      tooltipPosRef.current = { x, y }
      const tip = tooltipRef.current
      if (tip) {
        tip.style.left = `${x}px`
        tip.style.top = `${y}px`
      }
    }
    container.addEventListener('mousemove', onMove)
    return () => container.removeEventListener('mousemove', onMove)
  }, [isMapLoaded])

  // Finish line/polygon/arrow drawing handler
  const finishDrawing = useCallback(() => {
    const mode = drawModeRef.current
    const coords = drawingRef.current.currentCoords
    const color = selectedColorRef.current
    const width = selectedWidthRef.current

    if (coords.length < 2) {
      drawingRef.current.isDrawing = false
      drawingRef.current.currentCoords = []
      drawingRef.current.startPoint = null
      setDrawingPointCount(0)
      if (map.current) {
        const src = map.current.getSource('draw-preview') as maplibregl.GeoJSONSource
        if (src) src.setData({ type: 'FeatureCollection', features: [] })
      }
      return
    }

    if (mode === 'linestring') {
      onFeaturesChangeRef.current([...featuresRef.current, {
        id: `line-${Date.now()}`, type: 'linestring',
        geometry: { type: 'LineString', coordinates: coords },
        properties: { color, width },
      }])
    } else if (mode === 'arrow') {
      onFeaturesChangeRef.current([...featuresRef.current, {
        id: `arrow-${Date.now()}`, type: 'arrow',
        geometry: { type: 'LineString', coordinates: coords },
        properties: { color, width, isArrow: true },
      }])
    } else if (mode === 'polygon' && coords.length >= 3) {
      onFeaturesChangeRef.current([...featuresRef.current, {
        id: `polygon-${Date.now()}`, type: 'polygon',
        geometry: { type: 'Polygon', coordinates: [[...coords, coords[0]]] },
        properties: { color, width },
      }])
    } else if (mode === 'dangerzone' && coords.length >= 3) {
      onFeaturesChangeRef.current([...featuresRef.current, {
        id: `danger-${Date.now()}`, type: 'dangerzone',
        geometry: { type: 'Polygon', coordinates: [[...coords, coords[0]]] },
        properties: { color: '#dc2626', width: 2, isDangerZone: true },
      }])
    }

    drawingRef.current.isDrawing = false
    drawingRef.current.currentCoords = []
    drawingRef.current.startPoint = null
    setDrawingPointCount(0)
    if (map.current) {
      const src = map.current.getSource('draw-preview') as maplibregl.GeoJSONSource
      if (src) src.setData({ type: 'FeatureCollection', features: [] })
    }
    // Apple-style: auto-return to select mode after finishing drawing
    onDrawModeChangeRef.current?.('select')
  }, [])

  // Finish measurement handler
  const finishMeasure = useCallback(() => {
    drawingRef.current.isDrawing = false
    drawingRef.current.currentCoords = []
    drawingRef.current.startPoint = null
    setMeasureFinished(true)
    if (map.current) {
      const src = map.current.getSource('draw-preview') as maplibregl.GeoJSONSource
      if (src) src.setData({ type: 'FeatureCollection', features: [] })
    }
    fetchElevationsAndUpdateRef.current()
    // Apple-style: auto-return to select mode after finishing measurement
    onDrawModeChangeRef.current?.('select')
  }, [])

  return (
    <div className="relative w-full h-full" onClick={() => { if (isSymbolSelected) deselectSymbol() }}>
      <div
        ref={mapContainer}
        className="w-full h-full"
      />

      {/* Moveable controls for selected symbol */}
      {isSymbolSelected && selectedSymbolRef.current && canEdit && (
        <>
          <Moveable
            key={symbolSelectKey}
            ref={moveableRef}
            target={selectedSymbolRef.current.wrapperEl}
            rotatable={selectedSymbolRef.current.featureType === 'symbol'}
            resizable={true}
            keepRatio={selectedSymbolRef.current.featureType === 'symbol'}
            throttleRotate={1}
            renderDirections={['nw', 'ne', 'sw', 'se']}
            rotationPosition={'top'}
            origin={false}
            padding={{ left: 6, top: 6, right: 6, bottom: 6 }}
            onRotateStart={({ set }) => {
              set(selectedSymbolRef.current?.rotation || 0)
            }}
            onRotate={({ rotation }) => {
              if (selectedSymbolRef.current) {
                selectedSymbolRef.current.innerEl.style.transform = `rotate(${rotation}deg)`
                selectedSymbolRef.current.rotation = rotation
              }
            }}
            onRotateEnd={() => {
              const sel = selectedSymbolRef.current
              if (sel) {
                const updated = featuresRef.current.map(f =>
                  f.id === sel.id ? { ...f, properties: { ...f.properties, rotation: sel.rotation, scale: sel.scale } } : f
                )
                onFeaturesChangeRef.current(updated)
              }
            }}
            onResizeStart={({ setOrigin }) => {
              setOrigin(['%', '%'])
              if (selectedSymbolRef.current) {
                selectedSymbolRef.current.resizeStartWidth = selectedSymbolRef.current.wrapperEl.getBoundingClientRect().width
                selectedSymbolRef.current.resizeStartScale = selectedSymbolRef.current.scale
              }
            }}
            onResize={({ width, height }) => {
              if (selectedSymbolRef.current) {
                if (selectedSymbolRef.current.featureType === 'text') {
                  // For text: scale fontSize proportionally based on initial width
                  const baseFontSize = selectedSymbolRef.current.baseFontSize || 14
                  const startW = selectedSymbolRef.current.resizeStartWidth || 1
                  const startScale = selectedSymbolRef.current.resizeStartScale || 1
                  const ratio = width / startW
                  selectedSymbolRef.current.scale = Math.max(0.2, Math.min(10, startScale * ratio))
                  selectedSymbolRef.current.innerEl.style.fontSize = `${baseFontSize * selectedSymbolRef.current.scale}px`
                } else {
                  // For symbols: resize wrapper, use ratio from start to preserve zoom-aware scale
                  selectedSymbolRef.current.wrapperEl.style.width = `${width}px`
                  selectedSymbolRef.current.wrapperEl.style.height = `${height}px`
                  const startW = selectedSymbolRef.current.resizeStartWidth || 1
                  const startScale = selectedSymbolRef.current.resizeStartScale || 1
                  selectedSymbolRef.current.scale = Math.max(0.1, Math.min(10, startScale * (width / startW)))
                }
              }
            }}
            onResizeEnd={() => {
              const sel = selectedSymbolRef.current
              if (sel) {
                const updated = featuresRef.current.map(f =>
                  f.id === sel.id ? { ...f, properties: { ...f.properties, rotation: sel.rotation, scale: sel.scale } } : f
                )
                onFeaturesChangeRef.current(updated)
              }
            }}
          />

          {/* Floating action bar for selected symbol */}
          <div
            className="absolute bottom-4 left-1/2 -translate-x-1/2 z-[10000] flex items-center gap-2 bg-white/95 backdrop-blur-sm rounded-xl shadow-xl border border-gray-200 px-3 py-2"
            onClick={(e) => e.stopPropagation()}
          >
            <span className="text-xs font-semibold text-gray-600 mr-1">
              {Math.round(selectedSymbolRef.current.rotation)}°
            </span>
            <div className="w-px h-6 bg-gray-200" />
            <span className="text-xs font-semibold text-gray-600 mr-1">
              {selectedSymbolRef.current.scale.toFixed(1)}x
            </span>
            <div className="w-px h-6 bg-gray-200" />
            <button
              onClick={deleteSelectedSymbol}
              className="px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white text-xs font-semibold rounded-lg transition-colors"
            >
              Löschen
            </button>
            <button
              onClick={(e) => { e.stopPropagation(); deselectSymbol() }}
              className="px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white text-xs font-semibold rounded-lg transition-colors"
            >
              Fertig
            </button>
          </div>
        </>
      )}

      {/* Layer selector dropdown */}
      <div className="absolute top-3 right-3 z-10">
        <button
          onClick={() => setLayerDropdownOpen(v => !v)}
          className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[11px] font-medium shadow-md border backdrop-blur-sm transition-all"
          style={{
            background: activeBaseLayer !== 'osm' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.92)',
            color: activeBaseLayer !== 'osm' ? '#fff' : '#1f2937',
            borderColor: activeBaseLayer !== 'osm' ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)',
          }}
        >
          <svg className="w-3.5 h-3.5 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
          {{ osm: 'OpenStreetMap', satellite: 'Satellit', swisstopo: 'Swisstopo' }[activeBaseLayer]}
          <svg className={`w-3 h-3 opacity-50 transition-transform ${layerDropdownOpen ? 'rotate-180' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 9l6 6 6-6"/></svg>
        </button>
        {layerDropdownOpen && (
          <>
            <div className="fixed inset-0 z-10" onClick={() => setLayerDropdownOpen(false)} />
            <div className="absolute right-0 mt-1 z-20 min-w-[160px] rounded-lg shadow-xl border overflow-hidden backdrop-blur-md"
              style={{ background: 'rgba(255,255,255,0.95)', borderColor: 'rgba(0,0,0,0.08)' }}>
              {([
                { key: 'osm', label: 'OpenStreetMap' },
                { key: 'satellite', label: 'Satellit (Esri)' },
                { key: 'swisstopo', label: 'Swisstopo Karte' },
              ] as const).map(({ key, label }) => (
                <button
                  key={key}
                  onClick={() => {
                    if (!map.current) return
                    const allLayers: Array<'osm' | 'satellite' | 'swisstopo'> = ['osm', 'satellite', 'swisstopo']
                    for (const l of allLayers) {
                      map.current.setLayoutProperty(l, 'visibility', l === key ? 'visible' : 'none')
                    }
                    setActiveBaseLayer(key)
                    setLayerDropdownOpen(false)
                  }}
                  className={`w-full text-left px-3 py-2 text-[11px] font-medium transition-colors ${activeBaseLayer === key ? 'bg-blue-50 text-blue-700' : 'text-gray-700 hover:bg-gray-50'}`}
                >
                  {activeBaseLayer === key && <span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-500 mr-2 align-middle" />}
                  {label}
                </button>
              ))}
            </div>
          </>
        )}
      </div>

      {/* Zeichnung abschliessen Button (Linie/Polygon/Pfeil) */}
      {(drawMode === 'linestring' || drawMode === 'polygon' || drawMode === 'arrow' || drawMode === 'dangerzone') && drawingPointCount >= 2 && (
        <button
          onClick={finishDrawing}
          style={{
            position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)',
            zIndex: 1000, padding: '12px 28px', fontSize: '15px', fontWeight: 700,
            background: '#22c55e', color: '#fff', border: 'none', borderRadius: '10px',
            cursor: 'pointer', boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
          }}
        >
          ✓ Zeichnung abschliessen ({drawingPointCount} Punkte)
        </button>
      )}

      {/* Messung abschliessen Button */}
      {drawMode === 'measure' && measurePointCount >= 2 && !measureFinished && (
        <button
          onClick={finishMeasure}
          style={{
            position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)',
            zIndex: 1000, padding: '12px 28px', fontSize: '15px', fontWeight: 700,
            background: '#fbbf24', color: '#000', border: 'none', borderRadius: '10px',
            cursor: 'pointer', boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
          }}
        >
          ✓ Messung abschliessen ({measurePointCount} Punkte)
        </button>
      )}

      {/* Neue Messung starten */}
      {drawMode === 'measure' && measureFinished && (
        <button
          onClick={() => {
            measureMarkersRef.current.forEach(m => m.remove())
            measureMarkersRef.current = []
            measureCoordsRef.current = []
            setMeasurePointCount(0)
            setMeasureFinished(false)
            if (map.current) {
              const src = map.current.getSource('measure-line') as maplibregl.GeoJSONSource
              if (src) src.setData({ type: 'FeatureCollection', features: [] })
            }
          }}
          style={{
            position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)',
            zIndex: 1000, padding: '12px 28px', fontSize: '15px', fontWeight: 700,
            background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '10px',
            cursor: 'pointer', boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
          }}
        >
          ↻ Neue Messung
        </button>
      )}

      {/* Messergebnis + Leitungsrechner (React-Panel, ersetzt das alte DOM-Overlay) */}
      {measureResult && (
        <MeasurePanel
          measurement={measureResult.measurement}
          hoseTypes={measureResult.hoseTypes}
          onClose={() => setMeasureResult(null)}
        />
      )}

      {/* Inline edit overlay — replaces native prompt() to prevent fullscreen exit */}
      {inlineEdit && (
        <div
          className="absolute top-3 left-1/2 -translate-x-1/2 z-[10000] flex items-center gap-2 bg-white/95 backdrop-blur-sm rounded-xl shadow-xl border border-gray-200 px-3 py-2"
          onClick={(e) => e.stopPropagation()}
        >
          <span className="text-xs font-semibold text-gray-500 whitespace-nowrap">
            {inlineEdit.type === 'label' ? 'Bezeichnung:' : 'Text:'}
          </span>
          <input
            ref={inlineEditInputRef}
            type="text"
            value={inlineEdit.value}
            onChange={(e) => setInlineEdit({ ...inlineEdit, value: e.target.value })}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                const val = inlineEdit.value.trim()
                const prop = inlineEdit.type === 'label' ? 'label' : 'text'
                const updated = featuresRef.current.map(feat =>
                  feat.id === inlineEdit.featureId
                    ? { ...feat, properties: { ...feat.properties, [prop]: val || undefined } }
                    : feat
                )
                onFeaturesChangeRef.current(updated)
                setInlineEdit(null)
              }
              if (e.key === 'Escape') setInlineEdit(null)
            }}
            autoFocus
            className="w-48 px-2 py-1 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400"
            placeholder={inlineEdit.type === 'label' ? 'Bezeichnung...' : 'Text...'}
          />
          <button
            onClick={() => {
              const val = inlineEdit.value.trim()
              const prop = inlineEdit.type === 'label' ? 'label' : 'text'
              const updated = featuresRef.current.map(feat =>
                feat.id === inlineEdit.featureId
                  ? { ...feat, properties: { ...feat.properties, [prop]: val || undefined } }
                  : feat
              )
              onFeaturesChangeRef.current(updated)
              setInlineEdit(null)
            }}
            className="px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white text-xs font-semibold rounded-lg"
          >
            OK
          </button>
          <button
            onClick={() => setInlineEdit(null)}
            className="px-2 py-1 bg-gray-200 hover:bg-gray-300 text-gray-700 text-xs font-semibold rounded-lg"
          >
            ✕
          </button>
        </div>
      )}

      {/* Cursor-following tooltip — always mounted for stable ref */}
      <div
        ref={tooltipRef}
        className="pointer-events-none absolute z-[9999] hidden md:block"
        style={{
          left: tooltipPosRef.current.x,
          top: tooltipPosRef.current.y,
          display: (() => {
            if (!canEdit || drawMode === 'select') return 'none'
            if (drawMode === 'measure' && measureFinished) return 'none'
            return undefined // let className handle (hidden on mobile, block on md+)
          })(),
        }}
      >
        <div className="bg-gray-900/85 text-white text-xs font-medium px-2.5 py-1.5 rounded-md shadow-lg whitespace-nowrap backdrop-blur-sm">
          {(() => {
            const isDrawing = drawingPointCount > 0
            const isMeasuring = measurePointCount > 0
            switch (drawMode) {
              case 'linestring':
                return isDrawing ? 'Klicke für nächsten Punkt · Doppelklick beenden' : 'Klicke für den ersten Punkt'
              case 'polygon':
              case 'dangerzone':
                return isDrawing ? 'Klicke für nächsten Punkt · Doppelklick beenden' : 'Klicke für den ersten Punkt'
              case 'arrow':
                return isDrawing ? 'Klicke für nächsten Punkt · Doppelklick beenden' : 'Klicke für den Startpunkt'
              case 'measure':
                return isMeasuring ? 'Klicke für nächsten Messpunkt · Doppelklick beenden' : 'Klicke, um die Messung zu starten'
              case 'rectangle':
                return isDrawing ? 'Klicke für die gegenüberliegende Ecke' : 'Klicke für die erste Ecke'
              case 'circle':
                return isDrawing ? 'Klicke, um den Radius zu setzen' : 'Klicke für den Mittelpunkt'
              case 'freehand':
                return 'Halten und zeichnen'
              case 'point':
                return 'Klicke, um einen Punkt zu setzen'
              case 'text':
                return 'Klicke, um Text zu platzieren'
              case 'eraser':
                return 'Klicke auf ein Element zum Löschen'
              default:
                return ''
            }
          })()}
        </div>
      </div>
    </div>
  )
}
