<template>
  <div
    ref="mapRef"
    data-testid="kestrel-map"
    class="kestrel-map-container relative h-full w-full min-h-[300px]"
  >
    <div
      v-if="contextMenu.type"
      ref="contextMenuRef"
      class="pointer-events-auto absolute z-[1000] min-w-[120px] rounded border border-kestrel-border bg-kestrel-surface py-1 shadow-glow [box-shadow:0_0_20px_-4px_rgba(34,201,201,0.2)]"
      :style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
    >
      <template v-if="contextMenu.type === 'map'">
        <button
          type="button"
          class="block w-full px-3 py-1.5 text-left text-sm text-kestrel-text hover:bg-kestrel-border"
          @click="openAddPoiModal(contextMenu.latlng)"
        >
          Add POI here
        </button>
      </template>
      <template v-else-if="contextMenu.type === 'poi'">
        <button
          type="button"
          class="block w-full px-3 py-1.5 text-left text-sm text-kestrel-text hover:bg-kestrel-border"
          @click="openEditPoiModal(contextMenu.poi)"
        >
          Edit
        </button>
        <button
          type="button"
          class="block w-full px-3 py-1.5 text-left text-sm text-red-400 hover:bg-kestrel-border"
          @click="openDeletePoiModal(contextMenu.poi)"
        >
          Delete
        </button>
      </template>
    </div>

    <!-- POI modal (Add / Edit) -->
    <Teleport to="body">
      <Transition name="modal">
        <div
          v-if="showPoiModal"
          class="fixed inset-0 z-[2000] flex items-center justify-center p-4"
          role="dialog"
          aria-modal="true"
          :aria-labelledby="poiModalMode === 'delete' ? 'delete-poi-title' : 'poi-modal-title'"
          @keydown.escape="closePoiModal"
        >
          <button
            type="button"
            class="absolute inset-0 bg-black/60 transition-opacity"
            aria-label="Close"
            @click="closePoiModal"
          />
          <!-- Add / Edit form -->
          <div
            v-if="poiModalMode === 'add' || poiModalMode === 'edit'"
            ref="poiModalRef"
            class="relative w-full max-w-md rounded-lg border border-kestrel-border bg-kestrel-surface p-6 shadow-glow [box-shadow:0_0_32px_-8px_rgba(34,201,201,0.25)]"
            @click.stop
          >
            <h2
              id="poi-modal-title"
              class="mb-4 text-lg font-semibold tracking-wide text-kestrel-text [text-shadow:0_0_8px_rgba(34,201,201,0.25)]"
            >
              {{ poiModalMode === 'edit' ? 'Edit POI' : 'Add POI' }}
            </h2>
            <form
              class="space-y-4"
              @submit.prevent="submitPoiModal"
            >
              <div>
                <label
                  for="add-poi-label"
                  class="mb-1.5 block text-xs font-medium uppercase tracking-wider text-kestrel-muted"
                >
                  Label (optional)
                </label>
                <input
                  id="add-poi-label"
                  v-model="poiForm.label"
                  type="text"
                  placeholder="e.g. Rally point"
                  class="w-full rounded border border-kestrel-border bg-kestrel-bg px-3 py-2 text-sm text-kestrel-text placeholder:text-kestrel-muted outline-none transition-colors focus:border-kestrel-accent"
                  autocomplete="off"
                >
              </div>
              <div>
                <label
                  class="mb-1.5 block text-xs font-medium uppercase tracking-wider text-kestrel-muted"
                >
                  Icon type
                </label>
                <div
                  :ref="el => iconDropdownOpen && (iconDropdownRef.value = el)"
                  class="relative inline-block w-full"
                >
                  <button
                    type="button"
                    class="flex w-full min-w-0 items-center justify-between gap-2 rounded border border-kestrel-border bg-kestrel-bg px-3 py-2 text-left text-sm text-kestrel-text transition-colors hover:border-kestrel-accent/50"
                    :aria-expanded="iconDropdownOpen"
                    aria-haspopup="listbox"
                    :aria-label="`Icon type: ${poiForm.iconType}`"
                    @click="iconDropdownOpen = !iconDropdownOpen"
                  >
                    <span class="flex items-center gap-2 capitalize">
                      <Icon
                        :name="POI_ICONIFY_IDS[poiForm.iconType]"
                        class="size-4 shrink-0"
                      />
                      {{ poiForm.iconType }}
                    </span>
                    <span
                      class="text-kestrel-muted transition-transform"
                      :class="iconDropdownOpen && 'rotate-180'"
                    >
                      ▾
                    </span>
                  </button>
                  <Transition
                    enter-active-class="transition duration-100 ease-out"
                    enter-from-class="opacity-0 scale-95"
                    enter-to-class="opacity-100 scale-100"
                    leave-active-class="transition duration-75 ease-in"
                    leave-from-class="opacity-100 scale-100"
                    leave-to-class="opacity-0 scale-95"
                  >
                    <div
                      v-show="iconDropdownOpen"
                      class="absolute left-0 right-0 top-full z-10 mt-1 rounded border border-kestrel-border bg-kestrel-surface py-1 shadow-glow [box-shadow:0_4px_12px_-2px_rgba(34,201,201,0.15)]"
                      role="listbox"
                    >
                      <button
                        v-for="opt in POI_ICON_TYPES"
                        :key="opt"
                        type="button"
                        role="option"
                        :aria-selected="poiForm.iconType === opt"
                        class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm capitalize transition-colors"
                        :class="poiForm.iconType === opt
                          ? 'bg-kestrel-accent-dim text-kestrel-accent'
                          : 'text-kestrel-text hover:bg-kestrel-border'"
                        @click="poiForm.iconType = opt; iconDropdownOpen = false"
                      >
                        <Icon
                          :name="POI_ICONIFY_IDS[opt]"
                          class="size-4 shrink-0"
                        />
                        {{ opt }}
                      </button>
                    </div>
                  </Transition>
                </div>
              </div>
              <div class="flex justify-end gap-2 pt-2">
                <button
                  type="button"
                  class="rounded border border-kestrel-border px-4 py-2 text-sm text-kestrel-text transition-colors hover:bg-kestrel-border"
                  @click="closePoiModal"
                >
                  Cancel
                </button>
                <button
                  type="submit"
                  class="rounded bg-kestrel-accent px-4 py-2 text-sm font-medium text-kestrel-bg transition-opacity hover:opacity-90"
                >
                  {{ poiModalMode === 'edit' ? 'Save changes' : 'Add POI' }}
                </button>
              </div>
            </form>
          </div>
          <!-- Delete confirmation -->
          <div
            v-if="poiModalMode === 'delete'"
            ref="poiModalRef"
            class="relative w-full max-w-sm rounded-lg border border-kestrel-border bg-kestrel-surface p-6 shadow-glow [box-shadow:0_0_32px_-8px_rgba(34,201,201,0.25)]"
            @click.stop
          >
            <h2
              id="delete-poi-title"
              class="mb-2 text-lg font-semibold tracking-wide text-kestrel-text [text-shadow:0_0_8px_rgba(34,201,201,0.25)]"
            >
              Delete POI?
            </h2>
            <p class="mb-4 text-sm text-kestrel-muted">
              {{ deletePoi?.label ? `“${deletePoi.label}” will be removed.` : 'This POI will be removed.' }}
            </p>
            <div class="flex justify-end gap-2">
              <button
                type="button"
                class="rounded border border-kestrel-border px-4 py-2 text-sm text-kestrel-text transition-colors hover:bg-kestrel-border"
                @click="closePoiModal"
              >
                Cancel
              </button>
              <button
                type="button"
                class="rounded bg-red-600 px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90"
                @click="confirmDeletePoi"
              >
                Delete
              </button>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<script setup>
import 'leaflet/dist/leaflet.css'

const props = defineProps({
  devices: {
    type: Array,
    default: () => [],
  },
  pois: {
    type: Array,
    default: () => [],
  },
  liveSessions: {
    type: Array,
    default: () => [],
  },
  canEditPois: {
    type: Boolean,
    default: false,
  },
})

const emit = defineEmits(['select', 'selectLive', 'refreshPois'])
const CONTEXT_MENU_EMPTY = Object.freeze({ type: null, poi: null, latlng: null, x: 0, y: 0 })
const mapRef = ref(null)
const contextMenuRef = ref(null)
const leafletRef = shallowRef(null)
const mapContext = ref(null)
const markersRef = ref([])
const poiMarkersRef = ref({})
const liveMarkersRef = ref({})
const contextMenu = ref({ ...CONTEXT_MENU_EMPTY })

const showPoiModal = ref(false)
const poiModalRef = ref(null)
const poiModalMode = ref('add') // 'add' | 'edit' | 'delete'
const addPoiLatlng = ref(null)
const editPoi = ref(null)
const deletePoi = ref(null)
const poiForm = ref({ label: '', iconType: 'pin' })
const iconDropdownOpen = ref(false)
const iconDropdownRef = ref(null)

const TILE_URL = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'
const TILE_SUBDOMAINS = 'abcd'
const ATTRIBUTION = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
const DEFAULT_VIEW = [37.7749, -122.4194]
const DEFAULT_ZOOM = 17
const MARKER_ICON_PATH = '/'
const POI_ICON_TYPES = ['pin', 'flag', 'waypoint']
const POI_TOOLTIP_CLASS = 'kestrel-poi-tooltip'

/** Tabler icon names (Nuxt Icon / Iconify) – modern technical aesthetic. */
const POI_ICONIFY_IDS = { pin: 'tabler:map-pin', flag: 'tabler:flag', waypoint: 'tabler:current-location' }
const POI_ICON_COLORS = { pin: '#22c9c9', flag: '#e53e3e', waypoint: '#a78bfa' }

const ICON_SIZE = 28

/** Embedded SVGs so each POI type has a distinct marker (no network, always correct). */
function getPoiIconSvg(type) {
  const c = POI_ICON_COLORS[type] || POI_ICON_COLORS.pin
  const shapes = {
    pin: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8c0 4.5-6 9-6 9s-6-4.5-6-9a6 6 0 0 1 12 0z"/><circle cx="12" cy="8" r="2" fill="${c}"/></svg>`,
    flag: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4v16"/><path d="M6 6h10l4 4-4 4H6"/></svg>`,
    waypoint: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="2" fill="${c}"/></svg>`,
  }
  return shapes[type] || shapes.pin
}

function getPoiIcon(L, poi) {
  const type = poi.icon_type === 'pin' || poi.icon_type === 'flag' || poi.icon_type === 'waypoint' ? poi.icon_type : 'pin'
  const html = getPoiIconSvg(type)
  return L.divIcon({
    className: 'poi-div-icon',
    html: `<span class="poi-icon-svg poi-icon-${type}">${html}</span>`,
    iconSize: [ICON_SIZE, ICON_SIZE],
    iconAnchor: [ICON_SIZE / 2, ICON_SIZE],
  })
}

const LIVE_ICON_COLOR = '#22c9c9'
function getLiveSessionIcon(L) {
  const html = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${LIVE_ICON_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="2" fill="${LIVE_ICON_COLOR}"/></svg>`
  return L.divIcon({
    className: 'poi-div-icon live-session-icon',
    html: `<span class="poi-icon-svg">${html}</span>`,
    iconSize: [ICON_SIZE, ICON_SIZE],
    iconAnchor: [ICON_SIZE / 2, ICON_SIZE],
  })
}

function createMap(initialCenter) {
  const { L, offlineApi } = leafletRef.value || {}
  if (typeof document === 'undefined' || !mapRef.value || !L?.map) return

  const center = Array.isArray(initialCenter) && initialCenter.length === 2
    ? initialCenter
    : DEFAULT_VIEW

  const map = L.map(mapRef.value, { zoomControl: false, attributionControl: false }).setView(center, DEFAULT_ZOOM)
  L.control.zoom({ position: 'topleft' }).addTo(map)

  const locateControl = L.control({ position: 'topleft' })
  locateControl.onAdd = function () {
    const el = document.createElement('button')
    el.type = 'button'
    el.className = 'leaflet-bar leaflet-control-locate'
    el.title = 'Center on my location'
    el.setAttribute('aria-label', 'Center on my location')
    el.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="20" height="20" aria-hidden="true"><circle cx="12" cy="12" r="8"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><circle cx="12" cy="12" r="2" fill="currentColor"/></svg>`
    el.addEventListener('click', () => {
      if (!navigator?.geolocation) return
      navigator.geolocation.getCurrentPosition(
        (pos) => {
          const { latitude, longitude } = pos.coords
          map.setView([latitude, longitude], DEFAULT_ZOOM, { animate: true })
        },
        () => {},
        { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 },
      )
    })
    return el
  }
  locateControl.addTo(map)

  const baseLayer = L.tileLayer(TILE_URL, {
    attribution: ATTRIBUTION,
    subdomains: TILE_SUBDOMAINS,
    minZoom: 1,
    maxZoom: 19,
  })
  baseLayer.addTo(map)

  const control = (() => {
    if (!offlineApi?.tileLayerOffline || !offlineApi.savetiles) return null
    const layer = offlineApi.tileLayerOffline(TILE_URL, { attribution: ATTRIBUTION, subdomains: TILE_SUBDOMAINS, minZoom: 1, maxZoom: 19 })
    const c = offlineApi.savetiles(layer, {
      zoomlevels: [10, 11, 12, 13, 14, 15],
      position: 'topleft',
      saveText: 'Save tiles',
      rmText: 'Clear tiles',
    })
    c.addTo(map)
    return c
  })()

  map.on('contextmenu', (e) => {
    if (!props.canEditPois) return
    e.originalEvent?.preventDefault()
    const pt = map.latLngToContainerPoint(e.latlng)
    contextMenu.value = { type: 'map', latlng: e.latlng, x: pt.x, y: pt.y }
  })

  mapContext.value = { map, layer: baseLayer, control, locateControl }
  updateMarkers()
  updatePoiMarkers()
  updateLiveMarkers()
}

function updateMarkers() {
  const ctx = mapContext.value
  const { L } = leafletRef.value || {}
  if (!ctx?.map || !L) return

  markersRef.value.forEach((m) => {
    if (m) m.remove()
  })

  const validSources = (props.devices || []).filter(f => typeof f?.lat === 'number' && typeof f?.lng === 'number')
  markersRef.value = validSources.map(item =>
    L.marker([item.lat, item.lng]).addTo(ctx.map).on('click', () => emit('select', item)),
  )
}

function updatePoiMarkers() {
  const ctx = mapContext.value
  const { L } = leafletRef.value || {}
  if (!ctx?.map || !L) return

  const pois = (props.pois || []).filter(p => typeof p?.lat === 'number' && typeof p?.lng === 'number' && p?.id)
  const byId = Object.fromEntries(pois.map(p => [p.id, p]))
  const prev = poiMarkersRef.value

  Object.keys(prev).forEach((id) => {
    if (!byId[id]) prev[id]?.remove()
  })

  const next = pois.reduce((acc, poi) => {
    const existing = prev[poi.id]
    const icon = getPoiIcon(L, poi)
    if (existing) {
      existing.setLatLng([poi.lat, poi.lng])
      if (icon) existing.setIcon(icon)
      existing.getTooltip()?.setContent(poi.label || '')
      if (!existing.getTooltip()?.isOpen() && (poi.label || props.canEditPois)) existing.bindTooltip(poi.label || poi.id, { permanent: false, className: POI_TOOLTIP_CLASS })
      return { ...acc, [poi.id]: existing }
    }
    const marker = L.marker([poi.lat, poi.lng], icon ? { icon } : undefined).addTo(ctx.map)
    if (poi.label || props.canEditPois) marker.bindTooltip(poi.label || poi.id, { permanent: false, className: POI_TOOLTIP_CLASS })
    if (props.canEditPois) {
      marker.on('contextmenu', (e) => {
        e.originalEvent?.preventDefault()
        e.originalEvent?.stopPropagation()
        const pt = ctx.map.latLngToContainerPoint(e.latlng)
        contextMenu.value = { type: 'poi', poi, x: pt.x, y: pt.y }
      })
    }
    return { ...acc, [poi.id]: marker }
  }, {})
  poiMarkersRef.value = next
}

function updateLiveMarkers() {
  const ctx = mapContext.value
  const { L } = leafletRef.value || {}
  if (!ctx?.map || !L) return

  const sessions = (props.liveSessions || []).filter(
    s => typeof s?.lat === 'number' && typeof s?.lng === 'number' && s?.id,
  )
  const byId = Object.fromEntries(sessions.map(s => [s.id, s]))
  const prev = liveMarkersRef.value
  const icon = getLiveSessionIcon(L)

  Object.keys(prev).forEach((id) => {
    if (!byId[id]) prev[id]?.remove()
  })

  const next = sessions.reduce((acc, session) => {
    const content = `<div class="kestrel-live-popup"><strong>${escapeHtml(session.label)}</strong>${session.hasStream ? ' <span style="color:#22c9c9">● Live</span>' : ''}</div>`
    const existing = prev[session.id]
    if (existing) {
      existing.setLatLng([session.lat, session.lng])
      existing.setIcon(icon)
      existing.getPopup()?.setContent(content)
      return { ...acc, [session.id]: existing }
    }
    const marker = L.marker([session.lat, session.lng], { icon })
      .addTo(ctx.map)
      .bindPopup(content, { className: 'kestrel-live-popup-wrap', maxWidth: 360 })
      .on('click', () => emit('selectLive', session))
    return { ...acc, [session.id]: marker }
  }, {})
  liveMarkersRef.value = next
}

function escapeHtml(text) {
  const div = document.createElement('div')
  div.textContent = text
  return div.innerHTML
}

function closeContextMenu() {
  contextMenu.value = { ...CONTEXT_MENU_EMPTY }
}

function openAddPoiModal(latlng) {
  closeContextMenu()
  poiModalMode.value = 'add'
  addPoiLatlng.value = { lat: latlng.lat, lng: latlng.lng }
  editPoi.value = null
  deletePoi.value = null
  poiForm.value = { label: '', iconType: 'pin' }
  iconDropdownOpen.value = false
  showPoiModal.value = true
}

function openEditPoiModal(poi) {
  closeContextMenu()
  poiModalMode.value = 'edit'
  editPoi.value = poi
  addPoiLatlng.value = null
  deletePoi.value = null
  poiForm.value = { label: (poi.label ?? '').trim(), iconType: poi.icon_type || 'pin' }
  iconDropdownOpen.value = false
  showPoiModal.value = true
}

function openDeletePoiModal(poi) {
  closeContextMenu()
  poiModalMode.value = 'delete'
  deletePoi.value = poi
  addPoiLatlng.value = null
  editPoi.value = null
  showPoiModal.value = true
}

function closePoiModal() {
  showPoiModal.value = false
  poiModalMode.value = 'add'
  iconDropdownOpen.value = false
  addPoiLatlng.value = null
  editPoi.value = null
  deletePoi.value = null
}

function onPoiModalDocumentClick(e) {
  if (!showPoiModal.value) return
  if (iconDropdownOpen.value && iconDropdownRef.value && !iconDropdownRef.value.contains(e.target)) {
    iconDropdownOpen.value = false
  }
}

async function submitPoiModal() {
  if (poiModalMode.value === 'add') {
    const latlng = addPoiLatlng.value
    if (!latlng) return
    const { label, iconType } = poiForm.value
    try {
      await $fetch('/api/pois', { method: 'POST', body: { lat: latlng.lat, lng: latlng.lng, label: (label ?? '').trim(), iconType: iconType || 'pin' } })
      emit('refreshPois')
      closePoiModal()
    }
    catch { /* ignore */ }
    return
  }
  if (poiModalMode.value === 'edit' && editPoi.value) {
    const { label, iconType } = poiForm.value
    try {
      await $fetch(`/api/pois/${editPoi.value.id}`, { method: 'PATCH', body: { label: (label ?? '').trim(), iconType: iconType || 'pin' } })
      emit('refreshPois')
      closePoiModal()
    }
    catch { /* ignore */ }
  }
}

async function confirmDeletePoi() {
  const poi = deletePoi.value
  if (!poi?.id) return
  try {
    await $fetch(`/api/pois/${poi.id}`, { method: 'DELETE' })
    emit('refreshPois')
    closePoiModal()
  }
  catch { /* ignore */ }
}

function destroyMap() {
  markersRef.value.forEach((m) => {
    if (m) m.remove()
  })
  markersRef.value = []
  Object.values(poiMarkersRef.value).forEach(m => m?.remove())
  poiMarkersRef.value = {}
  Object.values(liveMarkersRef.value).forEach(m => m?.remove())
  liveMarkersRef.value = {}

  const ctx = mapContext.value
  if (ctx) {
    if (ctx.control && ctx.map) ctx.map.removeControl(ctx.control)
    if (ctx.locateControl && ctx.map) ctx.map.removeControl(ctx.locateControl)
    if (ctx.layer && ctx.map) ctx.map.removeLayer(ctx.layer)
    if (ctx.map) ctx.map.remove()
    mapContext.value = null
  }
}

function initMapWithLocation() {
  if (!navigator?.geolocation) {
    createMap(DEFAULT_VIEW)
    return
  }
  navigator.geolocation.getCurrentPosition(
    (pos) => {
      const { latitude, longitude } = pos.coords
      createMap([latitude, longitude])
    },
    () => {
      createMap(DEFAULT_VIEW)
    },
    { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 },
  )
}

onMounted(async () => {
  if (!import.meta.client || typeof document === 'undefined') return
  const [leaflet, offline] = await Promise.all([
    import('leaflet'),
    import('leaflet.offline'),
  ])
  const L = leaflet.default

  if (L.Icon?.Default?.mergeOptions) {
    L.Icon.Default.mergeOptions({
      iconUrl: `${MARKER_ICON_PATH}marker-icon.png`,
      iconRetinaUrl: `${MARKER_ICON_PATH}marker-icon-2x.png`,
      shadowUrl: `${MARKER_ICON_PATH}marker-shadow.png`,
    })
  }

  leafletRef.value = { L, offlineApi: offline }
  initMapWithLocation()
  document.addEventListener('click', onDocumentClick)
  document.addEventListener('click', onPoiModalDocumentClick)
})

function onDocumentClick(e) {
  if (contextMenu.value.type && contextMenuRef.value && !contextMenuRef.value.contains(e.target)) closeContextMenu()
}

onBeforeUnmount(() => {
  document.removeEventListener('click', onDocumentClick)
  document.removeEventListener('click', onPoiModalDocumentClick)
  destroyMap()
})

watch(() => props.devices, () => updateMarkers(), { deep: true })
watch([() => props.pois, () => props.canEditPois], () => updatePoiMarkers(), { deep: true })
watch(() => props.liveSessions, () => updateLiveMarkers(), { deep: true })
</script>

<style scoped>
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.2s ease;
}
.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}
.modal-enter-active .relative,
.modal-leave-active .relative {
  transition: transform 0.2s ease;
}
.modal-enter-from .relative,
.modal-leave-to .relative {
  transform: scale(0.96);
}

/* Unrendered/loading tiles show black instead of white when panning */
.kestrel-map-container {
  background: #000 !important;
}
:deep(.leaflet-tile-pane),
:deep(.leaflet-map-pane),
:deep(.leaflet-tile-container) {
  background: #000 !important;
}
:deep(img.leaflet-tile) {
  background: #000 !important;
  /* Override Leaflet’s plus-lighter so unloaded/empty tiles don’t flash white */
  mix-blend-mode: normal;
}
/* Leaflet injects divIcon HTML into the map; :deep() so these styles apply to that content */
:deep(.poi-div-icon) {
  background: none;
  border: none;
}
:deep(.poi-icon-svg) {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Dark-themed tooltip for POI labels (Leaflet creates these in the map container) */
:deep(.kestrel-poi-tooltip) {
  background: #1e293b;
  border: 1px solid rgba(34, 201, 201, 0.35);
  border-radius: 6px;
  color: #e2e8f0;
  font-size: 12px;
  font-family: inherit;
  padding: 6px 10px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
:deep(.kestrel-poi-tooltip::before),
:deep(.kestrel-poi-tooltip::after) {
  border-top-color: #1e293b;
  border-bottom-color: #1e293b;
  border-left-color: #1e293b;
  border-right-color: #1e293b;
}

/* Live session popup (content injected by Leaflet) */
:deep(.kestrel-live-popup-wrap .leaflet-popup-content) {
  margin: 8px 12px;
  min-width: 200px;
}
:deep(.kestrel-live-popup) {
  color: #e2e8f0;
  font-size: 12px;
}
:deep(.kestrel-live-popup img) {
  display: block;
  max-height: 160px;
  width: auto;
  border-radius: 4px;
  background: #0f172a;
}
:deep(.live-session-icon) {
  animation: live-pulse 1.5s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

/* Map controls – dark theme with cyan glow (zoom, locate, save/clear tiles) */
:deep(.leaflet-control-zoom),
:deep(.leaflet-control-locate),
:deep(.savetiles.leaflet-bar) {
  border: 1px solid rgba(34, 201, 201, 0.35) !important;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 0 12px -2px rgba(34, 201, 201, 0.15);
  font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace;
}
:deep(.leaflet-control-zoom a),
:deep(.leaflet-control-locate),
:deep(.savetiles.leaflet-bar a) {
  width: 32px !important;
  height: 32px !important;
  line-height: 32px !important;
  background: #0d1424 !important;
  color: #b8c9e0 !important;
  border: none !important;
  border-radius: 0 !important;
  font-size: 18px !important;
  font-weight: 600;
  text-decoration: none !important;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s, text-shadow 0.15s;
}
:deep(.leaflet-control-zoom a + a) {
  border-top: 1px solid rgba(34, 201, 201, 0.2) !important;
}
:deep(.leaflet-control-zoom a:hover),
:deep(.leaflet-control-locate:hover),
:deep(.savetiles.leaflet-bar a:hover) {
  background: #111a2e !important;
  color: #22c9c9 !important;
  box-shadow: 0 0 16px -2px rgba(34, 201, 201, 0.25);
  text-shadow: 0 0 8px rgba(34, 201, 201, 0.35);
}
:deep(.leaflet-control-locate) {
  display: flex !important;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}
:deep(.leaflet-control-locate svg) {
  color: currentColor;
}
/* Save/Clear tiles – text buttons */
:deep(.savetiles.leaflet-bar) {
  display: flex;
  flex-direction: column;
}
:deep(.savetiles.leaflet-bar a) {
  width: auto !important;
  min-width: 5.5em;
  height: auto !important;
  line-height: 1.25 !important;
  padding: 6px 10px !important;
  white-space: nowrap;
  text-align: center;
  font-size: 11px !important;
  font-weight: 500;
  letter-spacing: 0.02em;
}
:deep(.savetiles.leaflet-bar a + a) {
  border-top: 1px solid rgba(34, 201, 201, 0.2) !important;
}
</style>
