/* mapbox-map.jsx
   Componente <MapboxMap /> — mapa de tracking en vivo (Mapbox GL JS).

   El mapa es "tonto": no simula ni decide nada. Dibuja los 3 puntos del
   viaje (moto, restaurante, cliente), ajusta el encuadre con fitBounds y
   expone una API imperativa vía ref para que el dueño de los datos
   (TrackingDetail) lo actualice — sea con el simulador o con eventos
   reales de producción:

     ref.current.updateDriverLocation(lat, lng)  — mueve el marcador de la moto
     ref.current.setRouteLine(lngLatArr)         — dibuja la polilínea de la ruta

   Props:
     stage    {number}  — etapa del pedido (0-4); con 4 el ícono pasa a check
     orderId  {string}  — ID del pedido (accesibilidad)
     zone     {string}  — zona de entrega (texto informativo del badge)
     lat,lng  {number}  — opcionales; si llegan por props también mueven la moto

   Globals esperados: TREEGO_CONFIG, mapboxgl, y las coordenadas de
   tracking-sim.js (motoStartCoords, restauranteCoords, clienteCoords).
*/

/* ── Iconos personalizados (/assets/tracking/opt/, pre-optimizados) ──
   Se activan con window.TREEGO_TRACKING_ICONS = true (hoy solo en
   test-tracking.html; al aprobar se enciende también en el panel). */
const USE_TRACKING_ICONS = typeof TREEGO_TRACKING_ICONS !== 'undefined' && TREEGO_TRACKING_ICONS;
/* TREEGO_ASSET_BASE permite servir los iconos desde subcarpetas (p. ej. '/admin' usa '../') */
const TRACK_ICON_BASE =
  (typeof TREEGO_ASSET_BASE !== 'undefined' ? TREEGO_ASSET_BASE : '') + '/assets/tracking/opt/';

/* Rumbo (grados desde el norte, horario) al que apunta el PNG de la moto
   en reposo. "moto-bien.png" es vista cenital apuntando al norte → 0.
   Ajustar este valor si se cambia el arte del icono. */
const MOTO_HEADING_OFFSET = 0;

/* ── CSS de los marcadores (se inyecta en <head> una sola vez) ───── */
let __mapboxStyleInjected = false;
const __MARKER_CSS = `
  .treego-marker {
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--color-primary, #0D9488);
    display: grid; place-items: center; color: #fff;
    box-shadow: 0 4px 18px -2px rgba(var(--color-primary-rgb,13,148,136),.6), 0 2px 6px rgba(0,0,0,.2);
    cursor: pointer;
    border: 2.5px solid rgba(255,255,255,.6);
  }
  .treego-marker svg { pointer-events: none; display: block; }

  .mapboxgl-ctrl-attrib { font-size: 10px !important; opacity: .7; }
  .mapboxgl-ctrl-group { background: var(--panel, #1a1613) !important; border: 1px solid var(--line, #2c2520) !important; }
  .mapboxgl-ctrl-group button { background-color: transparent !important; }
  .mapboxgl-ctrl-icon { filter: invert(0.75); }
  body.theme-light .mapboxgl-ctrl-group { background: #fff !important; border-color: var(--line, #ece4db) !important; }
  body.theme-light .mapboxgl-ctrl-icon { filter: none; }

  .track-live-dot {
    display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    background: #22c55e; margin-right: 6px; vertical-align: 1px;
    animation: treego-live-pulse 1.6s ease-in-out infinite;
  }
  @keyframes treego-live-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(34,197,94,.55); }
    50%      { box-shadow: 0 0 0 5px rgba(34,197,94,0); }
  }

  /* Marcadores con iconos isométricos personalizados */
  .treego-img-marker {
    display: block; height: auto; cursor: pointer;
    filter: drop-shadow(0 7px 9px rgba(0,0,0,.35));
  }
  /* Moto cenital: el <img> es el propio elemento del marcador.
     OJO: sin "position" ni "transform" propios — Mapbox usa el transform
     del marcador para posicionarlo y rotarlo (marker.setRotation). */
  .treego-moto-img {
    width: 60px; height: 60px; object-fit: contain;
    transform-origin: center;
    display: block; cursor: pointer;
    filter: drop-shadow(0 6px 8px rgba(0,0,0,.4));
  }
`;

/* ── Iconos SVG del marcador del repartidor ─────────────────────── */
const DRIVER_MOTO_SVG  = '<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:17px;height:17px;display:block"><circle cx="6" cy="17" r="3"/><circle cx="18" cy="17" r="3"/><path d="M6 14L9 10h5l2 4h3"/><path d="M14 7h2.5v3"/><path d="M16.5 7H19.5"/></svg>';
const DRIVER_CHECK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width:15px;height:15px;display:block"><path d="M5 12.5l4.5 4.5L19 7"/></svg>';

/* ── Coordenadas de referencia (zonas demo del panel) ────────────── */
const CAJICA_CENTER_LNG = -74.0271;
const CAJICA_CENTER_LAT =   4.9176;

const ZONE_COORDS = {
  "Cajicá · Capellanía":  [-74.0185,  4.9402],
  "Cajicá · Río Frío":    [-74.0150,  4.9020],
  "Cajicá · Centro":      [-74.0253,  4.9183],
  "Chía · Bojacá":        [-73.9420,  4.8610],
  "Chía · La Balsa":      [-73.9540,  4.8640],
  "Tabio · Centro":       [-74.0980,  4.9175],
  "Tabio · Río Frío":     [-74.0960,  4.9050],
  "Cota · Centro":        [-74.1002,  4.8107],
};

function getZoneCoord(zone) {
  if (zone && ZONE_COORDS[zone]) return ZONE_COORDS[zone];
  if (zone) {
    const key = Object.keys(ZONE_COORDS).find((k) => zone.includes(k.split(' · ')[0]));
    if (key) return ZONE_COORDS[key];
  }
  return [CAJICA_CENTER_LNG, CAJICA_CENTER_LAT];
}

/* GeoJSON de una polilínea [lng,lat][] */
function lineGeoJSON(coords) {
  return { type: 'Feature', geometry: { type: 'LineString', coordinates: coords } };
}

/* ── Componente ─────────────────────────────────────────────────── */
/* trip (opcional): coordenadas reales del viaje { moto?, rest, cli } con {lat,lng}.
   Si no se pasa, usa las coordenadas demo de tracking-sim.js.
   Nota: el mapa se inicializa una vez — al cambiar de pedido usar key={...}
   para remontar el componente con el nuevo trip. */
const MapboxMap = React.forwardRef(function MapboxMap({ lat, lng, stage = 0, orderId, zone, trip, repartidorId, pedidoId }, ref) {
  const containerRef = React.useRef(null);
  const mapRef       = React.useRef(null);
  const driverRef    = React.useRef(null);
  const loadedRef    = React.useRef(false);
  const initDoneRef  = React.useRef(false);
  /* Datos que llegan antes de que el mapa termine de cargar */
  const pendingRef   = React.useRef({ pos: null, route: null, rot: 0 });
  /* Última polilínea dibujada (diagnóstico: window.__treegoTrack) */
  const routeCoordsRef = React.useRef(null);
  /* Rotación de la moto según el rumbo del movimiento */
  const lastPosRef   = React.useRef(null);

  /* ── Telemetría de velocidad ──────────────────────────────────── */
  const lastTsRef     = React.useRef(null);   /* timestamp de la última posición */
  const limitRef      = React.useRef(null);   /* { limite, fuente, tipoVia, calle } */
  const overSinceRef  = React.useRef(null);   /* ts en que empezó a exceder el límite */
  const firedRef      = React.useRef(false);  /* alerta ya disparada en este episodio */
  const targetSpeedRef = React.useRef(0);     /* velocidad objetivo (sin suavizar) */
  const [dispSpeed, setDispSpeed]   = React.useState(null); /* velocidad suavizada mostrada */
  const [speedState, setSpeedState] = React.useState(0);    /* 0 normal · 1 sobre límite · 2 infracción */
  /* Estado del deslizamiento del marcador (interpolación entre posiciones
     recibidas → movimiento continuo aunque los datos lleguen a saltos). */
  const glideRef = React.useRef({ from: null, to: null, start: 0, dur: 0, cur: null });

  /* Coordenadas del viaje: las reales del pedido (prop trip) o las demo */
  const FALLBACK = { lat: CAJICA_CENTER_LAT, lng: CAJICA_CENTER_LNG };
  const TRIP = {
    moto: (trip && (trip.moto || trip.rest)) || (typeof motoStartCoords   !== 'undefined' ? motoStartCoords   : FALLBACK),
    rest: (trip && trip.rest)                || (typeof restauranteCoords !== 'undefined' ? restauranteCoords : FALLBACK),
    cli:  (trip && trip.cli)                 || (typeof clienteCoords     !== 'undefined' ? clienteCoords     : FALLBACK),
  };

  if (!__mapboxStyleInjected) {
    const s = document.createElement('style');
    s.id = 'treego-mapbox-styles';
    s.textContent = __MARKER_CSS;
    document.head.appendChild(s);
    __mapboxStyleInjected = true;
  }

  /* Encuadre automático: origen + restaurante + cliente (+ ruta) siempre visibles */
  function fitAll(map, extraCoords, animate) {
    const b = new mapboxgl.LngLatBounds();
    [[TRIP.moto.lng, TRIP.moto.lat], [TRIP.rest.lng, TRIP.rest.lat], [TRIP.cli.lng, TRIP.cli.lat]]
      .forEach((c) => b.extend(c));
    if (extraCoords) extraCoords.forEach((c) => b.extend(c));
    map.fitBounds(b, {
      padding:  { top: 48, bottom: 36, left: 44, right: 44 },
      duration: animate ? 800 : 0,
      maxZoom:  15.5,
    });
  }

  /* ── API imperativa para el dueño de los datos ──────────────── */
  React.useImperativeHandle(ref, () => ({
    updateDriverLocation(newLat, newLng) {
      /* — Telemetría: velocidad real del tramo + guardas —
         El guard de window.TreegoTelemetry permite que el marcador siga
         moviéndose aunque osm-maxspeed.js no haya cargado (degradación). */
      const now     = Date.now();
      const prevPos = lastPosRef.current;
      const dt      = lastTsRef.current ? now - lastTsRef.current : 0;
      const target  = [newLng, newLat];

      let teleport = false;
      if (prevPos && dt > 0 && window.TreegoTelemetry) {
        const raw = window.TreegoTelemetry.speedKmh(prevPos, { lat: newLat, lng: newLng }, dt);
        if (raw > 130) {
          /* Salto imposible (reinicio de ciclo o brinco de GPS): NO es
             velocidad real → ignorar para no marcar locuras (p. ej. 51.239). */
          teleport = true;
          targetSpeedRef.current = 0;
          overSinceRef.current   = null;
          firedRef.current       = false;
          setSpeedState(0);
        } else {
          /* tope realista: una moto urbana no supera ~85 km/h */
          const spd = Math.min(raw, 85);
          targetSpeedRef.current = spd;

          /* Límite de la vía (async, cacheado). La detección de infracción
             se evalúa DENTRO del .then para usar siempre el límite de esta
             posición (sin desfase de un tick al cambiar de tipo de vía). */
          window.TreegoTelemetry.getSpeedLimit(newLat, newLng).then((lim) => {
            limitRef.current = lim;
            const thresholdMs = window.TREEGO_SPEED_VIOLATION_MS || 30000;
            if (lim && spd > lim.limite) {
              if (overSinceRef.current == null) overSinceRef.current = now;
              const elapsed = now - overSinceRef.current;
              if (elapsed >= thresholdMs && !firedRef.current) {
                firedRef.current = true;
                setSpeedState(2);
                window.dispatchEvent(new CustomEvent('treego-speed-alert', { detail: {
                  repartidorId: repartidorId ?? null,
                  pedidoId:     pedidoId ?? orderId ?? null,
                  velocidad:    Math.round(spd),
                  limite:       lim.limite,
                  fuenteLimite: lim.fuente,
                  tipoVia:      lim.tipoVia,
                  calle:        lim.calle,
                  lat: newLat, lng: newLng,
                  segundos:     Math.round(elapsed / 1000),
                  hora:         new Date().toISOString(),
                } }));
              } else if (!firedRef.current) {
                setSpeedState(1);
              }
            } else {
              overSinceRef.current = null;
              firedRef.current = false;
              setSpeedState(0);
            }
          });
        }
      }
      lastTsRef.current = now;

      /* — Movimiento continuo: en vez de saltar a la nueva posición, se
         programa un deslizamiento (glide) que el loop rAF interpola sobre
         el intervalo real entre updates (suave aun con realtime cada 3 s). */
      const g = glideRef.current;
      if (!driverRef.current) {
        pendingRef.current.pos = target;
        g.cur = target.slice();
      } else if (teleport || !g.cur || dt <= 0) {
        g.cur = target.slice(); g.from = target.slice(); g.to = target.slice();
        g.start = now; g.dur = 0;
        driverRef.current.setLngLat(target);   /* snap (primer punto o teletransporte) */
      } else {
        g.from  = g.cur.slice();
        g.to    = target;
        g.start = now;
        g.dur   = Math.min(dt, 4000);           /* no esperar más de 4 s por tramo */
      }

      /* Rotar la moto hacia el rumbo del movimiento (bearing).
         Se usa marker.setRotation(), que compone el rotate con el
         translate de posicionamiento — escribir transform a mano sobre
         el elemento del marcador rompería su posición. */
      if (USE_TRACKING_ICONS && prevPos && !teleport) {
        const dLat = newLat - prevPos.lat;
        const dLng = newLng - prevPos.lng;
        /* umbral: ignora micro-movimientos para que el ángulo no tiemble */
        if (Math.hypot(dLng, dLat) > 1e-6) {
          const bearing = Math.atan2(
            dLng * Math.cos(newLat * Math.PI / 180),
            dLat
          ) * 180 / Math.PI;
          const rot = bearing - MOTO_HEADING_OFFSET;
          if (driverRef.current) driverRef.current.setRotation(rot);
          else pendingRef.current.rot = rot;
        }
      }
      lastPosRef.current = { lat: newLat, lng: newLng };
    },
    /* legA = moto → restaurante (gris azulado), legB = restaurante → cliente (naranja) */
    setRouteLine(legA, legB) {
      const combined = legA.concat(legB.slice(1));
      routeCoordsRef.current = combined;
      const map = mapRef.current;
      if (map && loadedRef.current && map.getSource('treego-route-a')) {
        map.getSource('treego-route-a').setData(lineGeoJSON(legA));
        map.getSource('treego-route-b').setData(lineGeoJSON(legB));
        fitAll(map, combined, true);
      } else {
        pendingRef.current.route = { a: legA, b: legB };
      }
    },
  }));

  /* Diagnóstico en vivo desde la consola / tests E2E */
  React.useEffect(() => {
    window.__treegoTrack = {
      driver: () => (driverRef.current ? driverRef.current.getLngLat() : null),
      route:  () => routeCoordsRef.current,
      loaded: () => loadedRef.current,
    };
    return () => { delete window.__treegoTrack; };
  }, []);

  /* Suaviza la velocidad mostrada hacia el objetivo (evita saltos entre ticks) */
  React.useEffect(() => {
    let raf;
    let shown = 0;
    const tick = () => {
      /* No mostrar el velocímetro hasta recibir al menos una posición
         (repartidor activo): hasta entonces dispSpeed sigue null. */
      if (lastTsRef.current == null) {
        raf = requestAnimationFrame(tick);
        return;
      }
      const target = targetSpeedRef.current;
      shown += (target - shown) * 0.18;
      if (Math.abs(target - shown) < 0.4) shown = target;
      setDispSpeed((prev) => {
        const next = Math.round(shown);
        return prev === next ? prev : next;
      });
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  /* Desliza el marcador entre las posiciones recibidas: el movimiento es
     continuo aunque los datos lleguen a saltos (realtime cada 3 s o sim). */
  React.useEffect(() => {
    let raf;
    const tick = () => {
      const g = glideRef.current;
      if (g.to && g.from && driverRef.current) {
        const t = g.dur > 0 ? Math.min(1, (Date.now() - g.start) / g.dur) : 1;
        const lng = g.from[0] + (g.to[0] - g.from[0]) * t;
        const lat = g.from[1] + (g.to[1] - g.from[1]) * t;
        g.cur = [lng, lat];
        driverRef.current.setLngLat([lng, lat]);
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  /* ── Inicialización del mapa (una sola vez por instancia) ───── */
  React.useEffect(() => {
    if (initDoneRef.current || !containerRef.current) return;

    if (typeof mapboxgl === 'undefined') {
      console.error('[MapboxMap] mapboxgl no está disponible.');
      return;
    }
    if (typeof TREEGO_CONFIG === 'undefined' || !TREEGO_CONFIG.mapboxToken || TREEGO_CONFIG.mapboxToken.includes('TU_')) {
      console.warn('[MapboxMap] TREEGO_CONFIG.mapboxToken no configurado.');
      return;
    }

    initDoneRef.current = true;
    mapboxgl.accessToken = TREEGO_CONFIG.mapboxToken;

    const map = new mapboxgl.Map({
      container: containerRef.current,
      style:     TREEGO_CONFIG.mapboxStyleUrl || 'mapbox://styles/mapbox/streets-v12',
      center:    [TRIP.rest.lng, TRIP.rest.lat],
      zoom:      13,
      attributionControl: false,
      logoPosition: 'bottom-right',
    });

    map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');
    map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-right');

    /* Interactividad: arrastrar y pinch-zoom siempre activos */
    map.dragPan.enable();
    map.touchZoomRotate.enable();

    /* Scroll zoom solo con el cursor encima (no captura el scroll de la página) */
    map.scrollZoom.disable();
    const mapContainer = containerRef.current;
    const onEnter = () => map.scrollZoom.enable();
    const onLeave = () => map.scrollZoom.disable();
    mapContainer.addEventListener('mouseenter', onEnter);
    mapContainer.addEventListener('mouseleave', onLeave);

    map.on('load', () => {
      /* Marcador del restaurante (origen del pedido) */
      let restEl;
      if (USE_TRACKING_ICONS) {
        restEl = document.createElement('img');
        restEl.src = TRACK_ICON_BASE + 'restaurante.png?v=2';
        restEl.alt = 'Restaurante';
        restEl.className = 'treego-img-marker';
        restEl.style.width = '58px';
      } else {
        restEl = document.createElement('div');
        restEl.style.cssText = 'font-size:22px;line-height:1;cursor:pointer;filter:drop-shadow(0 2px 4px rgba(0,0,0,.4));';
        restEl.textContent = '🍽️';
      }
      restEl.title = 'Restaurante';
      new mapboxgl.Marker({ element: restEl, anchor: 'center' })
        .setLngLat([TRIP.rest.lng, TRIP.rest.lat])
        .setPopup(new mapboxgl.Popup({ offset: 18, closeButton: false, className: 'treego-popup' })
          .setHTML('<div style="font-size:12px;font-weight:600;padding:2px 4px">Restaurante</div>'))
        .addTo(map);

      /* Marcador del cliente (destino final) */
      let clientEl;
      if (USE_TRACKING_ICONS) {
        clientEl = document.createElement('img');
        clientEl.src = TRACK_ICON_BASE + 'cliente.png?v=2';
        clientEl.alt = 'Cliente';
        clientEl.className = 'treego-img-marker';
        clientEl.style.width = '46px';
      } else {
        clientEl = document.createElement('div');
        clientEl.style.cssText = 'font-size:22px;line-height:1;cursor:pointer;filter:drop-shadow(0 2px 4px rgba(0,0,0,.4));';
        clientEl.textContent = '🏠';
      }
      clientEl.title = 'Cliente';
      new mapboxgl.Marker({ element: clientEl, anchor: 'center' })
        .setLngLat([TRIP.cli.lng, TRIP.cli.lat])
        .setPopup(new mapboxgl.Popup({ offset: 16, closeButton: false, className: 'treego-popup' })
          .setHTML('<div style="font-size:12px;font-weight:600;padding:2px 4px">Cliente</div>'))
        .addTo(map);

      /* Capas de ruta: dos tramos con color propio, línea continua.
         (rectas provisionales; setRouteLine las reemplaza por la ruta real) */
      const initA = (pendingRef.current.route && pendingRef.current.route.a) ||
        [[TRIP.moto.lng, TRIP.moto.lat], [TRIP.rest.lng, TRIP.rest.lat]];
      const initB = (pendingRef.current.route && pendingRef.current.route.b) ||
        [[TRIP.rest.lng, TRIP.rest.lat], [TRIP.cli.lng, TRIP.cli.lat]];
      routeCoordsRef.current = initA.concat(initB.slice(1));

      map.addSource('treego-route-a', { type: 'geojson', data: lineGeoJSON(initA) });
      map.addSource('treego-route-b', { type: 'geojson', data: lineGeoJSON(initB) });
      map.addLayer({
        id:     'treego-route-a',          /* moto → restaurante */
        type:   'line',
        source: 'treego-route-a',
        layout: { 'line-join': 'round', 'line-cap': 'round' },
        paint:  {
          'line-color':   '#3B82F6',
          'line-width':   3.5,
          'line-opacity': 0.9,
        },
      });
      map.addLayer({
        id:     'treego-route-b',          /* restaurante → cliente */
        type:   'line',
        source: 'treego-route-b',
        layout: { 'line-join': 'round', 'line-cap': 'round' },
        paint:  {
          'line-color':   window.TREEGO_BRAND.primary,
          'line-width':   3.5,
          'line-opacity': 0.9,
        },
      });

      /* Marcador de la moto (móvil) */
      let driverEl;
      if (USE_TRACKING_ICONS) {
        /* Vista cenital: el <img> es el elemento del marcador y rota por bearing */
        driverEl = document.createElement('img');
        driverEl.src = TRACK_ICON_BASE + 'moto-bien.png?v=2';
        driverEl.alt = 'Moto del domiciliario';
        driverEl.className = 'treego-moto-img';
      } else {
        driverEl = document.createElement('div');
        driverEl.className = 'treego-marker';
        driverEl.innerHTML = stage >= 4 ? DRIVER_CHECK_SVG : DRIVER_MOTO_SVG;
      }
      driverEl.setAttribute('aria-label', `Domiciliario del pedido ${orderId || ''} en ruta`);
      driverRef.current = new mapboxgl.Marker({
        element: driverEl,
        anchor:  'center',
        rotationAlignment: 'map', /* la rotación es relativa al norte del mapa */
        pitchAlignment:    'map',
        rotation: pendingRef.current.rot || 0,
      })
        .setLngLat(pendingRef.current.pos || [TRIP.moto.lng, TRIP.moto.lat])
        .addTo(map);

      loadedRef.current = true;

      /* Encuadre inicial (las capas ya incluyen la ruta pendiente si llegó antes) */
      fitAll(map, routeCoordsRef.current, false);
      pendingRef.current.route = null;
      pendingRef.current.pos   = null;
    });

    mapRef.current = map;

    return () => {
      mapContainer.removeEventListener('mouseenter', onEnter);
      mapContainer.removeEventListener('mouseleave', onLeave);
      if (mapRef.current) {
        mapRef.current.remove();
        mapRef.current      = null;
        driverRef.current   = null;
        lastPosRef.current  = null;
        loadedRef.current   = false;
        initDoneRef.current = false;
        /* reiniciar telemetría para que el siguiente pedido no herede
           el cronómetro/infracción del anterior */
        lastTsRef.current      = null;
        overSinceRef.current   = null;
        firedRef.current       = false;
        targetSpeedRef.current = 0;
        glideRef.current       = { from: null, to: null, start: 0, dur: 0, cur: null };
      }
    };
  }, []); // eslint-disable-line

  /* ── Compatibilidad: coords reales por props también mueven la moto ── */
  React.useEffect(() => {
    if (lat == null || lng == null) return;
    if (driverRef.current) driverRef.current.setLngLat([lng, lat]);
    else pendingRef.current.pos = [lng, lat];
  }, [lat, lng]); // eslint-disable-line

  /* ── Ícono de la moto según etapa (check al entregar) ─────────── */
  React.useEffect(() => {
    if (!driverRef.current) return;
    /* Con la moto cenital no hay cambio de ícono: el estado "Entregado"
       lo comunica el timeline */
    if (USE_TRACKING_ICONS) return;
    const el = driverRef.current.getElement();
    if (el) el.innerHTML = stage >= 4 ? DRIVER_CHECK_SVG : DRIVER_MOTO_SVG;
  }, [stage]); // eslint-disable-line

  return (
    <div className="track-map2" style={{ position: 'relative' }}>
      <div
        ref={containerRef}
        style={{ width: '100%', height: '100%', position: 'absolute', inset: 0 }}
      />
      <span className="track-coord" style={{ zIndex: 2, pointerEvents: 'none' }}>
        <span className="track-live-dot"></span>
        En vivo{zone ? ` — ${zone}` : ''}
      </span>
      {dispSpeed != null && (
        <div
          className={'treego-speedo'
            + (String(dispSpeed).length >= 4 ? ' sp-4' : String(dispSpeed).length === 3 ? ' sp-3' : '')
            + (speedState === 2 ? ' is-violation' : speedState === 1 ? ' is-over' : '')}
          aria-label={`Velocidad ${dispSpeed} kilómetros por hora`}
        >
          <span className="treego-speedo-num">{dispSpeed}</span>
          <span className="treego-speedo-unit">km/h</span>
        </div>
      )}
    </div>
  );
});

Object.assign(window, { MapboxMap, getZoneCoord });
