'use strict';
/* Chart plotter — OpenLayers based.
   Exposes window.GPSMap with methods used by app.js */

const GPSMap = (function () {

  // ── Map setup ──────────────────────────────────────────────────────────────

  // Fondo oceánico sólido — visible siempre aunque no haya internet.
  // Las cartas ENC (land/depth) renderizan encima con zIndex 2+.
  const bgLayer = new ol.layer.Tile({
    source: new ol.source.XYZ({
      // Tile vacío 1×1px azul — no hace peticiones de red
      tileUrlFunction: function() { return null; },
    }),
    zIndex: 0,
  });

  // OSM — se carga cuando hay internet, falla silenciosamente si no hay.
  const osmLayer = new ol.layer.Tile({
    source: new ol.source.OSM({ crossOrigin: 'anonymous' }),
    zIndex: 1,
    opacity: 0.82,   // leve transparencia: las cartas ENC resaltan encima
  });

  const trackSource = new ol.source.Vector();
  const trackLayer  = new ol.layer.Vector({
    source: trackSource,
    style: new ol.style.Style({
      stroke: new ol.style.Stroke({ color: '#00c8e8cc', width: 2.5 }),
    }),
    zIndex: 20,
  });

  const wptSource = new ol.source.Vector();
  const wptLayer  = new ol.layer.Vector({ source: wptSource, zIndex: 22 });

  const routeSource = new ol.source.Vector();
  const routeLayer  = new ol.layer.Vector({ source: routeSource, zIndex: 21 });

  // Capa de marcas (POI: pesca, marina, buceo, etc.) — zIndex 23, encima de WPTs
  const marksSource = new ol.source.Vector();
  const marksLayer  = new ol.layer.Vector({ source: marksSource, zIndex: 23 });

  const ownSource = new ol.source.Vector();
  const ownLayer  = new ol.layer.Vector({ source: ownSource, zIndex: 25 });

  // ── Draw mode — must be declared BEFORE ol.Map (used in layers array) ──────
  // 'none' | 'wpt' | 'route'
  let _drawMode = 'none';
  let _routeDraftCoords = [];   // [[lon,lat], ...] accumulating route
  let _routeDraftFeature = null;
  const routeDraftSource = new ol.source.Vector();
  const routeDraftLayer  = new ol.layer.Vector({
    source: routeDraftSource, zIndex: 30,
    style: new ol.style.Style({
      stroke: new ol.style.Stroke({ color: '#fbbf24', width: 2, lineDash: [8,5] }),
      image: new ol.style.Circle({
        radius: 5,
        fill: new ol.style.Fill({ color: '#fbbf24' }),
        stroke: new ol.style.Stroke({ color: '#fff', width: 1.5 }),
      }),
    }),
  });

  const map = new ol.Map({
    target: 'map',
    layers: [bgLayer, osmLayer, trackLayer, routeLayer, ownLayer, wptLayer, marksLayer, routeDraftLayer],
    // Default: Miami (IALA-B, área de prueba GPS).  GPS auto-centra en cuanto llega fix.
    view: new ol.View({ center: ol.proj.fromLonLat([-80.19, 25.77]), zoom: 12 }),
    controls: [],   // OL 9.x: ol.control.defaults eliminado; controles custom en toolbar
    // NOTA: NO añadir pixelRatio:1 — en Qt5 WebEngine la causa del desfase de coordenadas
    // era document.documentElement.style.zoom (eliminado de index.html), NO el devicePixelRatio.
  });

  // ── State ──────────────────────────────────────────────────────────────────
  let _lat = null, _lon = null, _cog = 0, _sog = 0;
  let _trackVis = true;
  let _orientation = 'N'; // 'N' or 'C'
  let _trackCoords = [];   // [lon,lat] ordered
  let _ownFeature = null;

  // ── Own ship arrow ─────────────────────────────────────────────────────────
  function _arrowCanvas(col = '#00d8f0', sz = 28) {
    const c = document.createElement('canvas');
    c.width = c.height = sz;
    const ctx = c.getContext('2d');
    const cx = sz / 2, cy = sz / 2, r = sz * 0.45;
    ctx.save();
    ctx.translate(cx, cy);
    // Sombra exterior para visibilidad sobre cualquier tile OSM
    ctx.shadowBlur = 5; ctx.shadowColor = 'rgba(0,0,0,0.60)';
    // Arrow pointing up (north = 0°)
    ctx.beginPath();
    ctx.moveTo(0, -r);
    ctx.lineTo(r * 0.52, r * 0.70);
    ctx.lineTo(0, r * 0.28);
    ctx.lineTo(-r * 0.52, r * 0.70);
    ctx.closePath();
    ctx.fillStyle = col;
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
    ctx.restore();
    return c;
  }

  // Cache del dataURL de la flecha — se regenera solo si cambia el COG (rotación vía style)
  // Color: magenta S-52 OWNSHIP — inconfundible sobre el agua, estándar ECDIS
  var _arrowDataUrl = null;

  function _updateOwnShip() {
    if (_lat == null || _lon == null) return;
    const coord = ol.proj.fromLonLat([_lon, _lat]);

    if (!_ownFeature) {
      _ownFeature = new ol.Feature({ geometry: new ol.geom.Point(coord) });
      ownSource.addFeature(_ownFeature);
    } else {
      _ownFeature.getGeometry().setCoordinates(coord);
    }

    // Qt5 WebEngine: img:canvas NO funciona en ol.style.Icon — usar src:dataURL.
    if (!_arrowDataUrl) {
      var cv = _arrowCanvas('#cc00ff', 32);   // magenta S-52 OWNSHIP
      try { _arrowDataUrl = cv.toDataURL('image/png'); } catch(e) { _arrowDataUrl = null; }
    }

    var ownStyle;
    if (_arrowDataUrl) {
      ownStyle = new ol.style.Style({
        image: new ol.style.Icon({
          src: _arrowDataUrl,
          anchor: [0.5, 0.5],
          anchorXUnits: 'fraction',
          anchorYUnits: 'fraction',
          scale: 1,
          rotation: ol.math.toRadians(_cog),
          rotateWithView: _orientation === 'C',
        }),
      });
    } else {
      // Fallback si toDataURL falla: triángulo con RegularShape
      ownStyle = new ol.style.Style({
        image: new ol.style.RegularShape({
          points: 3,
          radius: 10,
          fill:   new ol.style.Fill({ color: '#00d8f0' }),
          stroke: new ol.style.Stroke({ color: '#fff', width: 2 }),
          rotation: ol.math.toRadians(_cog),
        }),
      });
    }
    _ownFeature.setStyle(ownStyle);

    if (_orientation === 'C') {
      map.getView().setRotation(-ol.math.toRadians(_cog));
    }
  }

  // ── Track ──────────────────────────────────────────────────────────────────
  function _appendTrack(lon, lat) {
    _trackCoords.push(ol.proj.fromLonLat([lon, lat]));
    if (_trackCoords.length < 2) return;
    trackSource.clear();
    trackSource.addFeature(new ol.Feature({
      geometry: new ol.geom.LineString(_trackCoords),
    }));
  }

  function loadTrack(points) {
    _trackCoords = points.map(p => ol.proj.fromLonLat([p.lon, p.lat]));
    trackSource.clear();
    if (_trackCoords.length >= 2) {
      trackSource.addFeature(new ol.Feature({
        geometry: new ol.geom.LineString(_trackCoords),
      }));
    }
  }

  // ── ECDIS helpers ─────────────────────────────────────────────────────────
  function _brg(lat1, lon1, lat2, lon2) {
    const φ1 = lat1*Math.PI/180, φ2 = lat2*Math.PI/180;
    const Δλ = (lon2-lon1)*Math.PI/180;
    const y = Math.sin(Δλ)*Math.cos(φ2);
    const x = Math.cos(φ1)*Math.sin(φ2) - Math.sin(φ1)*Math.cos(φ2)*Math.cos(Δλ);
    return (Math.atan2(y,x)*180/Math.PI+360)%360;
  }
  function _nm(lat1, lon1, lat2, lon2) {
    const R = 3440.065;
    const φ1=lat1*Math.PI/180, φ2=lat2*Math.PI/180;
    const Δφ=(lat2-lat1)*Math.PI/180, Δλ=(lon2-lon1)*Math.PI/180;
    const a = Math.sin(Δφ/2)**2 + Math.cos(φ1)*Math.cos(φ2)*Math.sin(Δλ/2)**2;
    return R*2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  }

  // ── Iconos de MARCAS POI ──────────────────────────────────────────────────
  // Pin shape: círculo con punta abajo. Anchor = [0.5, 1.0] → la punta toca la posición.
  // size=34x40: viewBox "0 0 34 40", círculo cx=17 cy=17 r=15, punta en (17,40).
  var _MARK_DEFS = {
    fishing:     { emoji: '🎣', label: 'Pesca',      col: '#2eaaff' },
    marina:      { emoji: '⚓', label: 'Marina',      col: '#00d8f0' },
    fuel:        { emoji: '⛽', label: 'Combustible', col: '#f8cc38' },
    restaurant:  { emoji: '🍴', label: 'Restaurante', col: '#ff8844' },
    dive:        { emoji: '🤿', label: 'Buceo',       col: '#00ccaa' },
    anchorage:   { emoji: '⚓', label: 'Fondeo',      col: '#a8d8ea' },
    beach:       { emoji: '🏖️', label: 'Playa',       col: '#f8e87a' },
    ramp:        { emoji: '🚤', label: 'Rampa',       col: '#88ccff' },
    repair:      { emoji: '🔧', label: 'Taller',      col: '#b0b0b0' },
    hospital:    { emoji: '🏥', label: 'Emergencia',  col: '#ff4444' },
    hotel:       { emoji: '🏨', label: 'Hotel',       col: '#cc88ff' },
    customs:     { emoji: '🛂', label: 'Aduana',      col: '#ffaa44' },
    danger:      { emoji: '⚠️', label: 'Peligro',     col: '#ff4444' },
    poi:         { emoji: '📍', label: 'POI',         col: '#ff6688' },
    waypoint:    { emoji: null, label: 'WPT Nav',     col: '#00d8f0' },  // símbolo ECDIS
  };

  function _markSvg(markType, active) {
    var d = _MARK_DEFS[markType] || _MARK_DEFS['poi'];
    var col = active ? '#ffcc00' : d.col;
    var pinPath = 'M17,0 C9.3,0 3,6.3 3,14 C3,22 17,40 17,40 C17,40 31,22 31,14 C31,6.3 24.7,0 17,0 Z';
    var emojiSize = 15;
    var svgInner = d.emoji
      ? ('<text x="17" y="20" text-anchor="middle" dominant-baseline="central" font-size="' + emojiSize + '" font-family="Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif">' + d.emoji + '</text>')
      : ('<line x1="17" y1="7" x2="17" y2="21" stroke="white" stroke-width="1.4"/>' +
         '<line x1="10" y1="14" x2="24" y2="14" stroke="white" stroke-width="1.4"/>' +
         '<circle cx="17" cy="14" r="2.5" fill="white"/>');
    return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(
      '<svg xmlns="http://www.w3.org/2000/svg" width="34" height="40" viewBox="0 0 34 40">' +
      '<filter id="shd" x="-30%" y="-10%" width="160%" height="160%">' +
        '<feDropShadow dx="0" dy="1.5" stdDeviation="1.5" flood-color="rgba(0,0,0,0.55)"/>' +
      '</filter>' +
      '<path d="' + pinPath + '" fill="' + col + '" stroke="white" stroke-width="1.8" filter="url(#shd)"/>' +
      '<circle cx="17" cy="14" r="11" fill="rgba(0,0,0,0.18)"/>' +
      svgInner +
      '</svg>'
    );
  }

  // Icono SVG ECDIS: círculo con cruz y punto central (IEC 61174 — planned position)
  function _wptSvg(active) {
    const col  = active ? '#ffcc00' : '#00d8f0';
    const fill = active ? '0.22'    : '0.10';
    const sw   = active ? '2.2'     : '1.8';
    return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(
      '<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">' +
      '<circle cx="11" cy="11" r="8" fill="' + col + '" fill-opacity="' + fill + '" stroke="' + col + '" stroke-width="' + sw + '"/>' +
      '<line x1="11" y1="3.5" x2="11" y2="18.5" stroke="' + col + '" stroke-width="1.3"/>' +
      '<line x1="3.5" y1="11" x2="18.5" y2="11" stroke="' + col + '" stroke-width="1.3"/>' +
      '<circle cx="11" cy="11" r="2" fill="' + col + '"/>' +
      '</svg>'
    );
  }

  // ── Waypoints ──────────────────────────────────────────────────────────────
  let _activeNavWpt = null;

  function renderWaypoints(wpts) {
    wptSource.clear();
    (wpts || []).forEach(w => {
      const f = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat([w.lon, w.lat])),
        wpt: w,
      });
      f.setId('wpt_' + w.id);
      const isActive = _activeNavWpt && _activeNavWpt.id === w.id;
      var isLocked = !!w.locked;
      var col = isActive ? '#ffcc00' : '#00d8f0';
      var styles = [new ol.style.Style({
        image: new ol.style.Icon({
          src: _wptSvg(isActive),
          anchor: [0.5, 0.5], anchorXUnits: 'fraction', anchorYUnits: 'fraction',
          scale: 1,
        }),
        text: new ol.style.Text({
          text: w.name,
          offsetX: 14, offsetY: -8,
          font: 'bold 10px "JetBrains Mono", monospace',
          textAlign: 'left',
          fill: new ol.style.Fill({ color: col }),
          stroke: new ol.style.Stroke({ color: 'rgba(3,8,16,0.92)', width: 3 }),
        }),
      })];
      if (isLocked) {
        styles.push(new ol.style.Style({
          text: new ol.style.Text({
            text: '🔒', offsetX: 10, offsetY: 10,
            font: '10px sans-serif', textAlign: 'left',
          }),
        }));
      }
      f.setStyle(styles);
      wptSource.addFeature(f);
    });
  }

  function renderRoutes(routes, wptMap) {
    routeSource.clear();
    (routes || []).forEach(r => {
      const wpts = (r.wpt_ids || []).map(id => wptMap[id]).filter(Boolean);
      if (wpts.length < 2) return;
      const coords = wpts.map(w => ol.proj.fromLonLat([w.lon, w.lat]));

      // Línea de ruta — cyan sólida ECDIS (IEC 61174 planned route)
      const lineFeat = new ol.Feature({ geometry: new ol.geom.LineString(coords), route: r });
      lineFeat.setStyle(new ol.style.Style({
        stroke: new ol.style.Stroke({ color: 'rgba(0,210,240,0.88)', width: 1.8 }),
      }));
      routeSource.addFeature(lineFeat);

      // Etiqueta de cada tramo: rumbo verdadero y distancia en NM
      for (let i = 0; i < wpts.length - 1; i++) {
        const w1 = wpts[i], w2 = wpts[i+1];
        const c1 = ol.proj.fromLonLat([w1.lon, w1.lat]);
        const c2 = ol.proj.fromLonLat([w2.lon, w2.lat]);
        const mid = [(c1[0]+c2[0])/2, (c1[1]+c2[1])/2];
        const brg = _brg(w1.lat, w1.lon, w2.lat, w2.lon);
        const dst = _nm(w1.lat, w1.lon, w2.lat, w2.lon);
        const lbl = String(Math.round(brg)).padStart(3,'0') + '°T  ' + dst.toFixed(2) + ' NM';

        const midFeat = new ol.Feature({ geometry: new ol.geom.Point(mid) });
        midFeat.setStyle(new ol.style.Style({
          text: new ol.style.Text({
            text: lbl,
            font: '9px "JetBrains Mono", monospace',
            offsetY: -10,
            fill: new ol.style.Fill({ color: '#00e8ff' }),
            stroke: new ol.style.Stroke({ color: 'rgba(3,8,16,0.90)', width: 3 }),
            backgroundFill: new ol.style.Fill({ color: 'rgba(6,14,28,0.72)' }),
            padding: [2, 5, 2, 5],
          }),
          image: new ol.style.RegularShape({   // pequeño rombo marcador de tramo
            points: 4, radius: 3, angle: Math.PI/4,
            fill: new ol.style.Fill({ color: 'rgba(0,210,240,0.80)' }),
          }),
        }));
        routeSource.addFeature(midFeat);
      }
    });
  }

  // ── Marcas POI ────────────────────────────────────────────────────────────
  function renderMarks(marks) {
    marksSource.clear();
    (marks || []).forEach(function(m) {
      var f = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat([m.lon, m.lat])),
        mark: m,
      });
      f.setId('mark_' + m.id);
      var isLocked = !!m.locked;
      var markStyles = [new ol.style.Style({
        image: new ol.style.Icon({
          src: _markSvg(m.mark_type, false),
          anchor: [0.5, 1.0],
          anchorXUnits: 'fraction', anchorYUnits: 'fraction',
          scale: 1,
        }),
        text: new ol.style.Text({
          text: m.name,
          offsetY: -44, offsetX: 0,
          font: 'bold 10px "Inter", sans-serif',
          textAlign: 'center',
          fill: new ol.style.Fill({ color: (_MARK_DEFS[m.mark_type] || _MARK_DEFS.poi).col }),
          stroke: new ol.style.Stroke({ color: 'rgba(3,8,16,0.92)', width: 3 }),
        }),
      })];
      if (isLocked) {
        markStyles.push(new ol.style.Style({
          text: new ol.style.Text({
            text: '🔒', offsetX: 12, offsetY: -26,
            font: '11px sans-serif', textAlign: 'left',
          }),
        }));
      }
      f.setStyle(markStyles);
      marksSource.addFeature(f);
    });
  }

  // ── Translate interaction — WPTs y marcas arrastrables ───────────────────
  // Se inicializa tarde (en _initTranslate) porque necesita el mapa ya creado.
  function _initTranslate() {
    var tr = new ol.interaction.Translate({
      layers: [wptLayer, marksLayer],
      hitTolerance: 8,
      filter: function(feature) {
        var w = feature.get('wpt');
        var m = feature.get('mark');
        if (w && w.locked) return false;
        if (m && m.locked) return false;
        return true;
      },
    });
    tr.on('translateend', function(evt) {
      evt.features.forEach(function(f) {
        var newLonLat = ol.proj.toLonLat(f.getGeometry().getCoordinates());
        var wpt  = f.get('wpt');
        var mark = f.get('mark');
        if (wpt) {
          wpt.lon = newLonLat[0]; wpt.lat = newLonLat[1];
          window.onWptDrag && window.onWptDrag(wpt);
        }
        if (mark) {
          mark.lon = newLonLat[0]; mark.lat = newLonLat[1];
          window.onMarkDrag && window.onMarkDrag(mark);
        }
      });
    });
    map.addInteraction(tr);
  }
  _initTranslate();

  // ── COG vector ────────────────────────────────────────────────────────────
  let _cogFeature = null;
  function _updateCogVector() {
    if (_lat == null || _sog < 0.3) {
      if (_cogFeature) { ownSource.removeFeature(_cogFeature); _cogFeature = null; }
      return;
    }
    const distM = _sog * 0.514444 * 360;  // 6 min ahead
    const h = _cog * Math.PI / 180;
    const mLat = 111320, mLon = 111320 * Math.cos(_lat * Math.PI / 180);
    const tipLon = _lon + (distM * Math.sin(h)) / mLon;
    const tipLat = _lat + (distM * Math.cos(h)) / mLat;
    const from = ol.proj.fromLonLat([_lon, _lat]);
    const to   = ol.proj.fromLonLat([tipLon, tipLat]);
    if (!_cogFeature) {
      _cogFeature = new ol.Feature({ geometry: new ol.geom.LineString([from, to]) });
      _cogFeature.setStyle(new ol.style.Style({
        stroke: new ol.style.Stroke({ color: 'rgba(204,0,255,0.80)', width: 2.0, lineDash: [7,4] }),
      }));
      ownSource.addFeature(_cogFeature);
    } else {
      _cogFeature.getGeometry().setCoordinates([from, to]);
    }
  }

  // ── Map click ─────────────────────────────────────────────────────────────
  map.on('click', evt => {
    const [lon, lat] = ol.proj.toLonLat(evt.coordinate);

    if (_drawMode === 'wpt') {
      window.onMapClickWpt && window.onMapClickWpt(lat, lon);
      return;
    }

    if (_drawMode === 'mark') {
      window.onMapClickMark && window.onMapClickMark(lat, lon);
      return;
    }

    if (_drawMode === 'route') {
      _routeDraftCoords.push([lon, lat]);
      _updateRouteDraft();
      window.onMapClickRoute && window.onMapClickRoute(lat, lon, _routeDraftCoords.length);
      return;
    }

    // Normal mode: click en WPT o MARCA para info
    const f = map.forEachFeatureAtPixel(evt.pixel, f => f, { hitTolerance: 10 });
    if (f && f.get('wpt'))  { window.onWptMapClick  && window.onWptMapClick(f.get('wpt')); }
    if (f && f.get('mark')) { window.onMarkMapClick && window.onMarkMapClick(f.get('mark')); }
  });

  map.on('dblclick', evt => {
    if (_drawMode === 'route' && _routeDraftCoords.length >= 2) {
      window.onMapDblClickRoute && window.onMapDblClickRoute(_routeDraftCoords.slice());
      _clearRouteDraft();
      evt.preventDefault();
    }
  });

  // ── Draft route rendering ─────────────────────────────────────────────────
  function _updateRouteDraft() {
    routeDraftSource.clear();
    if (_routeDraftCoords.length < 1) return;
    // Line
    if (_routeDraftCoords.length >= 2) {
      routeDraftSource.addFeature(new ol.Feature({
        geometry: new ol.geom.LineString(_routeDraftCoords.map(c => ol.proj.fromLonLat(c))),
      }));
    }
    // Dots at each waypoint
    _routeDraftCoords.forEach(c => {
      routeDraftSource.addFeature(new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat(c)),
      }));
    });
  }

  function _clearRouteDraft() {
    _routeDraftCoords = [];
    routeDraftSource.clear();
  }

  // ── Pointer coordinates + cursor feedback ────────────────────────────────
  map.on('pointermove', evt => {
    const [lon, lat] = ol.proj.toLonLat(evt.coordinate);
    const el = document.getElementById('map-coords');
    if (el) el.textContent = `LAT ${lat.toFixed(5)}°  LON ${lon.toFixed(5)}°`;
    // Crosshair cursor in draw modes
    map.getTargetElement().style.cursor = _drawMode !== 'none' ? 'crosshair' : '';
  });

  // ── Public API ────────────────────────────────────────────────────────────
  function update(lat, lon, cog, sog) {
    const moved = (_lat !== lat || _lon !== lon);
    _lat = lat; _lon = lon; _cog = cog || 0; _sog = sog || 0;
    _updateOwnShip();
    _updateCogVector();
    if (moved) _appendTrack(lon, lat);
  }

  function centerOnGPS() {
    // Push-button: centra una sola vez, sin auto-follow
    if (_lat == null) return;
    map.getView().animate({ center: ol.proj.fromLonLat([_lon, _lat]), duration: 300 });
  }

  function setOrientation(mode) {
    _orientation = mode;
    document.getElementById('btn-north').classList.toggle('active', mode === 'N');
    document.getElementById('btn-course').classList.toggle('active', mode === 'C');
    if (mode === 'N') map.getView().setRotation(0);
    _updateOwnShip();
  }

  function toggleTrack(vis) {
    _trackVis = vis !== undefined ? vis : !_trackVis;
    trackLayer.setVisible(_trackVis);
    const btn = document.getElementById('btn-track');
    if (btn) btn.classList.toggle('active', _trackVis);
  }

  function clearTrackMap() {
    _trackCoords = [];
    trackSource.clear();
  }

  function setActiveNav(wpt) {
    _activeNavWpt = wpt;
  }

  function getCenter() {
    return ol.proj.toLonLat(map.getView().getCenter());
  }

  function getOLMap() { return map; }

  function zoomIn()  {
    var v = map.getView();
    v.animate({ zoom: Math.min((v.getZoom() || 12) + 1, 20), duration: 220 });
  }
  function zoomOut() {
    var v = map.getView();
    v.animate({ zoom: Math.max((v.getZoom() || 12) - 1, 2),  duration: 220 });
  }

  function setDrawMode(mode) {
    _drawMode = mode; // 'none' | 'wpt' | 'route'
    if (mode === 'none') _clearRouteDraft();
    map.getTargetElement().style.cursor = mode !== 'none' ? 'crosshair' : '';
  }

  function cancelDraw() {
    _drawMode = 'none';
    _clearRouteDraft();
    map.getTargetElement().style.cursor = '';
  }

  function getDrawMode() { return _drawMode; }

  // Control de opacidad del layer OSM — permite modo night/dusk sin filtrar las ayudas IALA.
  // Las ayudas (encLayer) están en otra capa OL y no se ven afectadas por este ajuste.
  function setOsmOpacity(v) {
    osmLayer.setOpacity(v == null ? 0.82 : Math.max(0, Math.min(1, v)));
  }

  // Fondo del canvas #map — cambia el color de océano base según modo
  function setMapBackground(color) {
    var el = document.getElementById('map');
    if (el) el.style.background = color || '#a8c8e8';
  }

  return {
    update, centerOnGPS, setOrientation, toggleTrack,
    clearTrackMap, renderWaypoints, renderRoutes, renderMarks,
    setActiveNav, getCenter, loadTrack, getOLMap,
    setDrawMode, cancelDraw, getDrawMode, zoomIn, zoomOut,
    setOsmOpacity, setMapBackground,
    marksSource,
  };
})();

// Bind toolbar buttons
function centerOnGPS()    { GPSMap.centerOnGPS(); }
function setOrientation(m){ GPSMap.setOrientation(m); }
function toggleTrack()    { GPSMap.toggleTrack(); }
function mapZoomIn()      { GPSMap.zoomIn(); }
function mapZoomOut()     { GPSMap.zoomOut(); }
function clearTrack()     {
  if (!window.py) return;
  if (!confirm('Clear the GPS track log?')) return;
  window.py.clear_track();
  GPSMap.clearTrackMap();
}
function addWptAtCenter() {
  const [lon, lat] = GPSMap.getCenter();
  window.openWptModal && window.openWptModal({ lat, lon });
}
