<script setup lang="ts">
import { LngLatBounds, Map as MapLibreMap, Marker, NavigationControl, Popup, setWorkerUrl } from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import type { Coord } from '#shared/types'
import { midpointAt } from '#shared/polyline'

setWorkerUrl('/maplibre-gl-worker.mjs')

interface Trace {
  mode: string
  color: string
  geometry: Coord[]
}

interface TraceMeta {
  mode: string
  color: string
  labelTop: string
}

const props = withDefaults(defineProps<{
  traces?: Trace[]
  metas?: TraceMeta[]
  from?: Coord | null
  to?: Coord | null
  highlight?: string | null
}>(), {
  traces: () => [],
  metas: () => [],
  from: null,
  to: null,
  highlight: null,
})

const container = ref<HTMLDivElement | null>(null)
let map: MapLibreMap | null = null
let markers: Marker[] = []
let bubbles: Map<string, Marker> = new Map()
let drawn: string[] = []

/**
 * Fond raster de l'IGN Geoplateforme : service public, gratuit, sans clef.
 * Le raster n'a pas besoin de Web Worker, contrairement aux tuiles vectorielles
 * que le contexte de production empechait silencieusement de decoder.
 */
const STYLE = {
  version: 8 as const,
  sources: {
    ign: {
      type: 'raster' as const,
      tiles: [
        'https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile'
        + '&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&STYLE=normal&FORMAT=image/png'
        + '&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}',
      ],
      tileSize: 256,
      maxzoom: 19,
      attribution: '© <a href="https://www.ign.fr/">IGN</a>',
    },
  },
  layers: [{ id: 'ign', type: 'raster' as const, source: 'ign' }],
}

const FRANCE: Coord = [2.5, 46.6]

function clear() {
  if (!map) return
  for (const m of markers) m.remove()
  markers = []
  for (const b of bubbles.values()) b.remove()
  bubbles.clear()
  // On retire ce qui a reellement ete dessine, pas ce que les props contiennent
  // maintenant : les deux listes different des que le trajet change.
  for (const id of drawn) {
    if (map.getLayer(id)) map.removeLayer(id)
    if (map.getSource(id)) map.removeSource(id)
  }
  drawn = []
}

function draw() {
  if (!map || !props.traces.length) return
  const b = new LngLatBounds()

  for (const t of props.traces) {
    if (t.geometry.length < 2) continue
    const id = `trace-${t.mode}`
    if (map.getLayer(id)) map.removeLayer(id)
    if (map.getSource(id)) map.removeSource(id)

    map.addSource(id, {
      type: 'geojson',
      data: {
        type: 'Feature',
        properties: {},
        geometry: { type: 'LineString', coordinates: t.geometry },
      },
    })

    map.addLayer({
      id,
      type: 'line',
      source: id,
      layout: { 'line-join': 'round', 'line-cap': 'round' },
      paint: { 'line-color': t.color, 'line-width': 4, 'line-opacity': 0.85 },
    })

    drawn.push(id)
    for (const c of t.geometry) b.extend(c)
  }

  for (const [coord, label] of [[props.from, 'Départ'], [props.to, 'Arrivée']] as const) {
    if (!coord) continue
    markers.push(
      new Marker({ color: '#10231c' })
        .setLngLat(coord)
        .setPopup(new Popup({ offset: 24 }).setText(label))
        .addTo(map),
    )
    b.extend(coord)
  }

  if (!b.isEmpty()) map.fitBounds(b, { padding: 48, duration: 600 })
  drawBubbles()
  applyHighlight()
}

function drawBubbles() {
  if (!map) return
  // Offset vertical par mode pour que les 4 bulles ne se superposent pas
  // quand les traces sont quasi confondues (typique des trajets moyennes distances).
  const offsets: Record<string, number> = {
    train: -46,
    bike: -16,
    bus: 14,
    electric_car: 44,
  }
  for (const t of props.traces) {
    if (t.geometry.length < 2) continue
    const meta = props.metas.find(m => m.mode === t.mode)
    if (!meta) continue

    const el = document.createElement('div')
    el.className = 'lcv-bubble'
    el.style.borderLeftColor = t.color
    el.innerHTML = `<span class="lcv-bubble-dot" style="background:${t.color}"></span><span>${meta.labelTop}</span>`

    const bubble = new Marker({
      element: el,
      offset: [0, offsets[t.mode] ?? 0],
    })
      .setLngLat(midpointAt(t.geometry, 0.5))
      .addTo(map)

    bubbles.set(t.mode, bubble)
  }
}

function applyHighlight() {
  if (!map) return
  for (const t of props.traces) {
    const id = `trace-${t.mode}`
    if (map.getLayer(id)) {
      const actif = !props.highlight || props.highlight === t.mode
      map.setPaintProperty(id, 'line-opacity', actif ? 0.9 : 0.15)
      map.setPaintProperty(id, 'line-width', props.highlight === t.mode ? 6 : 4)
    }
    const bubble = bubbles.get(t.mode)
    if (bubble) {
      const el = bubble.getElement()
      // Sur mobile, on n affiche qu une seule bulle max : celle du mode
      // selectionne, sinon aucune. Les 4 bulles superposees etaient illisibles.
      const isMobile = window.matchMedia('(max-width: 767px)').matches
      const cachee = isMobile
        ? !props.highlight || props.highlight !== t.mode
        : Boolean(props.highlight && props.highlight !== t.mode)
      el.style.display = cachee ? 'none' : ''
    }
  }
}

/** Le style peut ne pas etre pret quand les traces arrivent : on attend l'evenement. */
function redraw() {
  if (!map) return
  const run = () => {
    try {
      clear()
      draw()
    }
    catch (e) {
      console.error('[carte] echec du trace', e)
    }
  }
  if (map.isStyleLoaded()) run()
  else map.once('idle', run)
}

onMounted(() => {
  if (!container.value) return
  map = new MapLibreMap({
    container: container.value,
    style: STYLE,
    center: props.from ?? FRANCE,
    zoom: props.from ? 8 : 5,
  })



  map.addControl(new NavigationControl({ showCompass: false }), 'bottom-right')
  map.on('error', e => console.error('[carte]', e.error ?? e))
  map.on('load', redraw)
  window.addEventListener('resize', applyHighlight)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', applyHighlight)
  map?.remove()
  map = null
})

watch(() => props.traces, redraw, { deep: true })
watch(() => props.highlight, applyHighlight)
</script>

<template>
  <div
    ref="container"
    class="h-full w-full"
    role="img"
    aria-label="Carte des itinéraires comparés"
  />
</template>

<style>
.lcv-bubble {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: white;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 600;
  color: #10231c;
  border: 1px solid #ccc;
  border-left-width: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
  white-space: nowrap;
  transition: opacity 0.2s;
  pointer-events: none;
}
.lcv-bubble-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.lcv-bubble.dimmed {
  opacity: 0.45;
}
</style>