// LumoraMap: Leaflet + OpenTopoMap. Single consolidated effect to avoid hook ordering issues.

window.LumoraMap = function LumoraMap(props) {
  const containerRef = useRef(null);
  const stateRef = useRef({ map: null, routes: {}, markers: {}, hiker: null });

  // Init effect — mount Leaflet once
  useEffect(function initMap() {
    if (!containerRef.current) return;
    if (stateRef.current.map) return;

    try {
      const map = L.map(containerRef.current, {
        zoomControl: true,
        scrollWheelZoom: true,
        doubleClickZoom: true,
        boxZoom: false,
        keyboard: true,
        dragging: true,
        touchZoom: true,
        zoomSnap: 0.25,
      }).setView([28.3, 84.5], 7);

      L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
        maxZoom: 17,
        attribution: 'Map: © OpenStreetMap, SRTM | Style: © OpenTopoMap (CC-BY-SA)',
      }).addTo(map);

      stateRef.current.map = map;

      const data = window.LUMORA_DATA;
      data.treks.forEach(function (trek) {
        const stopLatLngs = trek.stops.map(function (s) { return [s.lat, s.lng]; });
        const latlngs = smoothRoute(stopLatLngs, trek.id);
        const underlay = L.polyline(latlngs, {
          // White halo casing — separates the route from topo features
          color: '#FFFFFF', weight: 7, opacity: 0.85, lineCap: 'round', interactive: false,
        }).addTo(map);
        const main = L.polyline(latlngs, {
          // Deep burgundy: brand-adjacent, high-contrast vs topo, doesn't
          // collide with OSM road colors (which are red/orange).
          color: '#5E1B14', weight: 3, opacity: 0.95, dashArray: '6 5', lineCap: 'round',
        }).addTo(map);
        main.on('click', function () {
          const cb = stateRef.current.onPickTrek;
          if (cb) cb(trek.id);
        });
        stateRef.current.routes[trek.id] = { underlay: underlay, main: main, latlngs: latlngs, stopLatLngs: stopLatLngs, progress: null };

        const markers = trek.stops.map(function (stop, idx) {
          const html = markerHTML(stop, false, false, false, false);
          const icon = L.divIcon({ className: 'lumora-marker', html: html, iconSize: [18, 18], iconAnchor: [9, 9] });
          const m = L.marker([stop.lat, stop.lng], { icon: icon, riseOnHover: true });
          m.bindTooltip('<b>' + stop.name + '</b><br><span style="color:#7A6A5A">' + stop.alt + '</span>', {
            direction: 'right', offset: [10, 0], className: 'lumora-tip',
          });
          // Click a stop — advance the narrative
          m.on('click', function () {
            const cb = stateRef.current.onPickStop;
            if (cb) cb(trek.id, idx);
          });
          m.addTo(map);
          return m;
        });
        stateRef.current.markers[trek.id] = markers;
      });

      const hikerIcon = L.divIcon({
        className: 'lumora-hiker',
        html: '<div class="hiker-pulse"></div><div class="hiker-dot"></div>',
        iconSize: [20, 20], iconAnchor: [10, 10],
      });
      const hiker = L.marker([0, 0], { icon: hikerIcon, interactive: false, opacity: 0 }).addTo(map);
      stateRef.current.hiker = hiker;

      map.fitBounds(window.LUMORA_DATA.worldBounds, { padding: [60, 60], animate: false });

      // Force a redraw after layout settles so leaflet uses correct size
      setTimeout(function () { try { map.invalidateSize(); } catch (e) {} }, 50);
    } catch (e) {
      console.error('LumoraMap init error msg:', e && e.message, 'stack:', e && e.stack);
    }
  }, []);

  // Keep latest pick callback fresh for marker handlers
  useEffect(function syncCb() {
    stateRef.current.onPickStop = props.onPickStop;
    stateRef.current.onPickTrek = props.onPickTrek;
  }, [props.onPickStop, props.onPickTrek]);

  // Update emphasis + camera in a single effect
  useEffect(function updateMap() {
    const s = stateRef.current;
    const map = s.map;
    if (!map) return;

    try {
      const data = window.LUMORA_DATA;
      const activeTrekId = props.activeTrekId;
      const activeStopIndex = props.activeStopIndex;
      const cameraTarget = props.cameraTarget;
      const panAnchor = props.panAnchor || 'left';

      // Update each route's emphasis
      data.treks.forEach(function (trek) {
        const isActive = trek.id === activeTrekId;
        const dim = activeTrekId && !isActive;
        const r = s.routes[trek.id];
        if (!r) return;

        r.underlay.setStyle({ opacity: dim ? 0.25 : 0.85 });
        r.main.setStyle({ opacity: dim ? 0.2 : 0.95, weight: isActive ? 3.5 : 3 });

        if (isActive && activeStopIndex != null && activeStopIndex >= 0) {
          // Build a smoothed sub-path that ends at the current named stop
          const stopsSoFar = r.stopLatLngs.slice(0, Math.min(r.stopLatLngs.length, activeStopIndex + 1));
          const sub = stopsSoFar.length > 1 ? smoothRoute(stopsSoFar, trek.id) : stopsSoFar;
          if (!r.progress) {
            // Walked portion: solid brand orange, sits on top of the
            // burgundy dashed line so it visually "lights up".
            r.progress = L.polyline(sub, { color: '#C05A2A', weight: 4, opacity: 1, lineCap: 'round' }).addTo(map);
          } else {
            r.progress.setLatLngs(sub);
            r.progress.setStyle({ opacity: 1 });
          }
          const stop = trek.stops[Math.min(activeStopIndex, trek.stops.length - 1)];
          if (s.hiker) { s.hiker.setLatLng([stop.lat, stop.lng]); s.hiker.setOpacity(1); }
        } else if (r.progress) {
          r.progress.setStyle({ opacity: 0 });
        }

        const markers = s.markers[trek.id] || [];
        markers.forEach(function (m, i) {
          const stop = trek.stops[i];
          const visited = isActive && activeStopIndex != null && i <= activeStopIndex;
          const upcoming = isActive && activeStopIndex != null && activeStopIndex >= 0 && i > activeStopIndex;
          const focused = isActive && i === activeStopIndex;
          const html = markerHTML(stop, focused, visited, upcoming, dim);
          m.setIcon(L.divIcon({
            className: 'lumora-marker', html: html,
            iconSize: focused ? [26, 26] : [18, 18],
            iconAnchor: focused ? [13, 13] : [9, 9],
          }));
          m.setOpacity(dim ? 0.4 : 1);
        });
      });

      if ((!activeTrekId || activeStopIndex == null || activeStopIndex < 0) && s.hiker) {
        s.hiker.setOpacity(0);
      }

      // Camera
      if (cameraTarget) {
        const w = map.getSize().x || 1200;
        const cardPad = Math.min(560, Math.max(380, w * 0.4));
        const padLeft  = panAnchor === 'left'  ? cardPad : 40;
        const padRight = panAnchor === 'right' ? cardPad : 40;

        if (cameraTarget.kind === 'world') {
          // Generous padding so the entire country breathes \u2014 stops the
          // initial framing from biasing east toward Khumbu.
          map.flyToBounds(window.LUMORA_DATA.worldBounds, {
            paddingTopLeft: [padLeft + 40, 120], paddingBottomRight: [padRight + 40, 100],
            duration: 1.6, easeLinearity: 0.25,
          });
        } else if (cameraTarget.kind === 'trek') {
          const trek = data.treks.find(function (t) { return t.id === cameraTarget.trekId; });
          if (trek) {
            const bounds = L.latLngBounds(trek.stops.map(function (st) { return [st.lat, st.lng]; }));
            map.flyToBounds(bounds, {
              paddingTopLeft: [padLeft, 100], paddingBottomRight: [padRight, 80],
              duration: 1.8, easeLinearity: 0.25,
            });
          }
        } else if (cameraTarget.kind === 'stop') {
          const trek = data.treks.find(function (t) { return t.id === cameraTarget.trekId; });
          if (trek) {
            const stop = trek.stops[cameraTarget.stopIndex];
            const next = trek.stops[cameraTarget.stopIndex + 1];
            const pts = next ? [[stop.lat, stop.lng], [next.lat, next.lng]] : [[stop.lat, stop.lng]];
            const bounds = L.latLngBounds(pts).pad(0.5);
            map.flyToBounds(bounds, {
              paddingTopLeft: [padLeft, 100], paddingBottomRight: [padRight, 80],
              maxZoom: 13, duration: 1.6, easeLinearity: 0.25,
            });
          }
        }
      }
    } catch (e) {
      console.error('LumoraMap update error msg:', e && e.message, 'stack:', e && e.stack);
    }
  }, [
    props.activeTrekId,
    props.activeStopIndex,
    props.panAnchor,
    props.cameraTarget ? props.cameraTarget.kind : null,
    props.cameraTarget ? props.cameraTarget.trekId : null,
    props.cameraTarget ? props.cameraTarget.stopIndex : null,
  ]);

  // Invalidate map size whenever the container may have resized (layout tweak).
  useEffect(function resize() {
    const map = stateRef.current.map;
    if (!map) return;
    const ro = new ResizeObserver(function () {
      try { map.invalidateSize({ animate: false }); } catch (e) {}
    });
    if (containerRef.current) ro.observe(containerRef.current);
    return function () { ro.disconnect(); };
  }, []);

  return React.createElement('div', {
    ref: containerRef,
    style: { width: '100%', height: '100%', background: '#E8E0C5' },
  });
};

// Densify a polyline by inserting intermediate waypoints with perpendicular
// "terrain wiggle" offsets, then smooth with Catmull-Rom-to-Bezier sampling.
// Deterministic per trek so the shape is stable across re-renders.
function smoothRoute(points, seed) {
  if (!points || points.length < 2) return points || [];
  // 1) Add intermediate waypoints with bounded perpendicular jitter
  const rng = mulberry32(hashStr(seed || 'x'));
  const dense = [];
  for (let i = 0; i < points.length - 1; i++) {
    const a = points[i], b = points[i + 1];
    dense.push(a);
    const dx = b[1] - a[1], dy = b[0] - a[0];
    const len = Math.hypot(dx, dy);
    if (len < 1e-6) continue;
    // perpendicular unit vector (in lng/lat space)
    const px = -dy / len, py = dx / len;
    // 3 intermediate waypoints with small offsets — rivers/ridges meander
    const segs = 3;
    for (let k = 1; k <= segs; k++) {
      const t = k / (segs + 1);
      const mx = a[1] + dx * t;
      const my = a[0] + dy * t;
      // amplitude scaled to segment length, organic curve via sin
      const amp = len * 0.06 * Math.sin(t * Math.PI) * (0.6 + rng() * 0.8) * (k % 2 === 0 ? -1 : 1);
      dense.push([my + py * amp, mx + px * amp]);
    }
  }
  dense.push(points[points.length - 1]);

  // 2) Catmull-Rom interpolation between dense points → many sub-samples
  const out = [];
  const samples = 14;
  const n = dense.length;
  for (let i = 0; i < n - 1; i++) {
    const p0 = dense[Math.max(0, i - 1)];
    const p1 = dense[i];
    const p2 = dense[i + 1];
    const p3 = dense[Math.min(n - 1, i + 2)];
    for (let j = 0; j < samples; j++) {
      const t = j / samples;
      const t2 = t * t, t3 = t2 * t;
      const lat = 0.5 * ((2 * p1[0]) +
        (-p0[0] + p2[0]) * t +
        (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 +
        (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3);
      const lng = 0.5 * ((2 * p1[1]) +
        (-p0[1] + p2[1]) * t +
        (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 +
        (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3);
      out.push([lat, lng]);
    }
  }
  out.push(dense[n - 1]);
  return out;
}

function hashStr(s) {
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}
function mulberry32(a) {
  return function () {
    let t = a += 0x6D2B79F5;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function markerHTML(stop, focused, visited, upcoming, dim) {
  const k = stop.kind;
  const fill = focused ? '#C05A2A' : (visited ? '#C05A2A' : '#FFFFFF');
  const stroke = '#C05A2A';
  const size = focused ? 18 : 12;
  const ring = focused ? '<div class="m-ring"></div>' : '';
  if (k === 'peak' || k === 'view') {
    return ring + '<svg width="' + (size + 4) + '" height="' + (size + 4) + '" viewBox="0 0 16 16" style="display:block">' +
      '<polygon points="8,2 14,13 2,13" fill="' + (focused ? '#C05A2A' : '#E8A07A') + '" stroke="' + stroke + '" stroke-width="1.6"/></svg>';
  }
  if (k === 'camp' || k === 'end') {
    return ring + '<svg width="' + (size + 4) + '" height="' + (size + 4) + '" viewBox="0 0 16 16" style="display:block">' +
      '<polygon points="8,2 14,13 2,13" fill="#C05A2A" stroke="#2E2822" stroke-width="1.4"/></svg>';
  }
  if (k === 'flight') {
    return ring + '<div class="m-circle" style="width:' + size + 'px;height:' + size + 'px;border:2.5px solid ' + stroke + ';background:#fff;"><div class="m-inner" style="background:' + stroke + '"></div></div>';
  }
  return ring + '<div class="m-circle" style="width:' + size + 'px;height:' + size + 'px;border:2.5px solid ' + stroke + ';background:' + fill + ';"></div>';
}
