// ═══════════════════════════════════════════════════════════════════
// Enduro Trails - Phase 5 Redesign
// Theme system (auto/light/dark + SunCalc), skeleton, swipe, animations
// ═══════════════════════════════════════════════════════════════════

// ─── Theme System ──────────────────────────────────────────────────
let themeMode = localStorage.getItem('enduro-theme-mode') || 'auto'; // 'auto' | 'light' | 'dark'
let userLat = null;
let userLon = null;
let themeAutoInterval = null;

function isDarkTheme() {
  return document.body.classList.contains('theme-dark');
}

function applyTheme() {
  if (themeMode === 'light') {
    document.body.className = 'theme-light';
  } else if (themeMode === 'dark') {
    document.body.className = 'theme-dark';
  } else {
    // auto: use SunCalc
    applyAutoTheme();
  }
  updateThemeButtonIcon();
  switchMapStyle();
}

function applyAutoTheme() {
  if (themeMode !== 'auto') return;
  const now = new Date();
  const lat = userLat || 55.75;
  const lon = userLon || 37.62;
  let isDay = true;
  try {
    if (typeof SunCalc !== 'undefined') {
      const times = SunCalc.getTimes(now, lat, lon);
      isDay = now >= times.sunrise && now < times.sunset;
    } else {
      // Fallback: assume day if 6am-8pm
      const h = now.getHours();
      isDay = h >= 6 && h < 20;
    }
  } catch(e) {
    const h = now.getHours();
    isDay = h >= 6 && h < 20;
  }
  document.body.className = isDay ? 'theme-light' : 'theme-dark';
  updateThemeButtonIcon();
}

function toggleTheme() {
  // Cycle: auto → light → dark → auto
  if (themeMode === 'auto') themeMode = 'light';
  else if (themeMode === 'light') themeMode = 'dark';
  else themeMode = 'auto';

  localStorage.setItem('enduro-theme-mode', themeMode);
  applyTheme();
}

function updateThemeButtonIcon() {
  const sunIcon = document.getElementById('theme-icon-sun');
  const moonIcon = document.getElementById('theme-icon-moon');
  const label = document.getElementById('theme-label');
  if (!sunIcon || !moonIcon) return;

  const dark = isDarkTheme();

  if (themeMode === 'auto') {
    // Dynamic icon based on actual theme
    sunIcon.style.display = dark ? 'none' : 'block';
    moonIcon.style.display = dark ? 'block' : 'none';
    if (label) label.textContent = 'Авто';
  } else if (themeMode === 'light') {
    sunIcon.style.display = 'block';
    moonIcon.style.display = 'none';
    if (label) label.textContent = 'День';
  } else {
    sunIcon.style.display = 'none';
    moonIcon.style.display = 'block';
    if (label) label.textContent = 'Ночь';
  }
}

function switchMapStyle() {
  const map = window._map;
  if (!map) return;
  const dark = isDarkTheme();
  const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || '';
  const tileBase = window.location.origin + basePath;
  const styleUrl = dark ? basePath + '/style-dark.json' : basePath + '/style.json';

  // Save current position before style change
  const center = map.getCenter();
  const zoom = map.getZoom();
  const bearing = map.getBearing();
  const pitch = map.getPitch();

  fetch(styleUrl).then(r => {
    if (r.ok) return r.json();
    throw new Error('Style not available');
  }).then(style => {
    // Fix tile URLs to absolute (same as initMap)
    if (style.sources && style.sources['trails-tiles'] && style.sources['trails-tiles'].tiles) {
      style.sources['trails-tiles'].tiles = [`${tileBase}/api/tiles/{z}/{x}/{y}.mvt`];
    }
    map.setStyle(style);
    // Restore position and overlays after style loads
    map.once('idle', () => {
      map.jumpTo({ center, zoom, bearing, pitch });
      rebuildMapOverlays();
    });
  }).catch(() => {
    // Network error or style not available, don't switch
  });
}

// Re-add layers after style change
function onMapStyleLoad() {
  const map = window._map;
  if (!map) return;
  // Re-add any active route layers, markers, etc.
  rebuildMapOverlays();
}

function rebuildMapOverlays() {
  // ET-007: восстановить выбранную подложку первой — чтобы terrain/trails/POI
  // оказались поверх неё (см. ADR-004, TRZ §5.5).
  if (typeof restoreBaseLayerState === 'function') {
    restoreBaseLayerState();
  }
  // Re-apply terrain and trails after style change
  restoreTerrainState();
  restoreTrailsState();
  restorePoiState();
  // ET-008: публичные GPS-треки
  if (typeof restorePublicTracksState === 'function') {
    restorePublicTracksState();
  }

  // Re-apply recon circle if active
  if (reconMode && reconCenter) {
    doRecon(reconCenter[0], reconCenter[1]);
  }
  // Re-draw route if active
  if (routeMode && routeResults.length > 0) {
    const savedResults = [...routeResults];
    const savedIdx = activeRouteIdx;
    routeResults = [];
    drawRouteResults(savedResults, savedIdx);
  }
  // Re-draw scenic routes
  if (scenicMode && scenicRoutes.length > 0) {
    const savedRoutes = [...scenicRoutes];
    scenicRoutes = [];
    drawScenicRoutes(savedRoutes, activeScenicIdx);
  }
  // Re-draw link routes
  if (linkMode && linkPoints.length >= 2) {
    buildLinkRoute();
  }
  // Re-draw ruler
  if (rulerMode && rulerPoints.length > 0) {
    const pts = [...rulerPoints];
    rulerPoints = [];
    rulerTotal = 0;
    rulerMarkers.forEach(m => m.remove());
    rulerMarkers = [];
    const map = window._map;
    if (map.getSource('ruler')) map.removeSource('ruler');
    if (map.getLayer('ruler-line')) map.removeLayer('ruler-line');
    pts.forEach(pt => addRulerPoint({ lng: pt[0], lat: pt[1] }));
  }
  // Re-render named markers
  renderMarkers();
  // ET-006: восстановить GPX-слои после смены стиля карты (ADR-002, REQ-F-13)
  if (typeof rebuildGpxOverlays === 'function') rebuildGpxOverlays();
}

// ─── Utilities ──────────────────────────────────────────────────────

function formatDuration(seconds) {
  const totalMin = Math.round(seconds / 60);
  if (totalMin < 60) return totalMin + ' мин';
  const days = Math.floor(totalMin / 1440);
  const hours = Math.floor((totalMin % 1440) / 60);
  const mins = totalMin % 60;
  if (days > 0) {
    if (hours === 0 && mins === 0) return `${days} дн`;
    if (mins === 0) return `${days} дн ${hours} ч`;
    return `${days} дн ${hours} ч ${mins} мин`;
  }
  if (mins === 0) return `${hours} ч`;
  return `${hours} ч ${mins} мин`;
}

// ET-005: форматирование расстояний централизовано в units.js (ADR-0001).
function formatDist(m) {
  if (!m) return '-';
  return Units.formatDistance(m);
}

// ─── Sheet Management ──────────────────────────────────────────────

// >>> ET-014 sheet-popup yield block (см. ADR-019)
// При открытии любого bottom-sheet'а принудительно закрываем
// #terrain-popup. Это устраняет z-index конфликт (popup z=500 над
// sheet z=400) и убирает anti-pattern «два меню открыты одновременно»
// на desktop. См. docs/work-items/ET-014/06-adr/ADR-019-*.
function closeTerrainPopup() {
  const popup = document.getElementById('terrain-popup');
  const btn = document.getElementById('terrain-toggle');
  if (!popup || popup.style.display === 'none') return;
  popup.style.display = 'none';
  if (btn) btn.classList.remove('active');
  document.removeEventListener('click', closeTerrainOnOutside);
}

function openSheet(id) {
  const sheet = document.getElementById(id);
  if (!sheet) return;
  // ET-014: terrain-popup yields to any opening sheet (ADR-019).
  closeTerrainPopup();
  // Close all other sheets first
  document.querySelectorAll('.bottom-sheet.open').forEach(s => {
    if (s.id !== id) closeSheet(s.id);
  });
  sheet.classList.add('open');
  const backdrop = document.getElementById('sheet-backdrop');
  backdrop.classList.add('visible');
}
// <<< ET-014 sheet-popup yield block <<<

function closeSheet(id) {
  const sheet = document.getElementById(id);
  if (!sheet) return;
  sheet.classList.remove('open');
  sheet.style.transform = '';
  // Check if any sheets still open
  const anyOpen = document.querySelector('.bottom-sheet.open');
  if (!anyOpen) {
    document.getElementById('sheet-backdrop').classList.remove('visible');
  }
}

// Close sheet panel but keep the mode active (route stays on map)
function minimizeSheet(id) {
  closeSheet(id);
  if (id === 'sheet-route' && routeResults.length > 0) {
    showMiniRouteSheet();
  }
}

function closeAllSheets() {
  document.querySelectorAll('.bottom-sheet.open').forEach(s => {
    s.classList.remove('open');
    s.style.transform = '';
  });
  document.getElementById('sheet-backdrop').classList.remove('visible');
}

// ─── Swipe-down to close sheets ────────────────────────────────────

function initSheetSwipe() {
  document.querySelectorAll('.bottom-sheet').forEach(sheet => {
    let startY = 0;
    let isDragging = false;

    sheet.addEventListener('touchstart', (e) => {
      const rect = sheet.getBoundingClientRect();
      const touchY = e.touches[0].clientY;
      // Only initiate swipe from the handle area (top 50px of sheet)
      if (touchY < rect.top + 50 || e.target.closest('.sheet-handle')) {
        isDragging = true;
        startY = touchY;
        sheet.classList.add('swiping');
      }
    }, { passive: true });

    sheet.addEventListener('touchmove', (e) => {
      if (!isDragging) return;
      const dy = e.touches[0].clientY - startY;
      if (dy > 0) {
        sheet.style.transform = `translateY(${dy}px)`;
      }
    }, { passive: true });

    sheet.addEventListener('touchend', (e) => {
      if (!isDragging) return;
      isDragging = false;
      sheet.classList.remove('swiping');
      const dy = e.changedTouches[0].clientY - startY;
      if (dy > 80) {
        const sheetId = sheet.id;
        if (sheetId === 'sheet-route' && routeResults && routeResults.length > 0) {
          minimizeSheet(sheetId);
        } else {
          closeSheet(sheetId);
          // Deactivate corresponding mode
          if (sheetId === 'sheet-route' && routeMode) toggleRouteMode();
          else if (sheetId === 'sheet-recon' && reconMode) toggleReconMode();
          else if (sheetId === 'sheet-scenic' && scenicMode) toggleScenicMode();
          else if (sheetId === 'sheet-link' && linkMode) toggleLinkMode();
        }
        sheet.style.transform = '';
      } else {
        sheet.style.transform = '';
      }
    }, { passive: true });
  });
}

// ─── Skeleton Loading ──────────────────────────────────────────────

function showSkeleton(containerId, count) {
  const container = document.getElementById(containerId);
  if (!container) return;
  count = count || 2;
  let html = '';
  for (let i = 0; i < count; i++) {
    html += `<div class="skeleton-card">
      <div class="skeleton skeleton-line h20 w60"></div>
      <div class="skeleton skeleton-line w80"></div>
      <div class="skeleton skeleton-line w40"></div>
    </div>`;
  }
  container.innerHTML = html;
}

// ─── Deactivate All Modes ──────────────────────────────────────────

function deactivateAllModes() {
  // Deactivate all input modes but preserve route/scenic/link data on map
  if (routeMode) { routeMode = false; document.getElementById('tb-route').classList.remove('active'); closeSheet('sheet-route'); /* NOT clearRoute - keep line on map */ }
  if (rulerMode) toggleRuler();
  if (markerMode) toggleMarkerMode();
  if (typeof reconMode !== 'undefined' && reconMode) toggleReconMode();
  if (typeof linkMode !== 'undefined' && linkMode) toggleLinkMode();
  if (typeof scenicMode !== 'undefined' && scenicMode) toggleScenicMode();
  if (window._map) window._map.getCanvas().style.cursor = '';
  updateMapModeClass();
}

function updateMapModeClass() {
  const has = routeMode || rulerMode || markerMode || reconMode || linkMode || scenicMode;
  document.body.classList.toggle('has-map-mode', !!has);
}

// ─── Компас ────────────────────────────────────────────────────────
let compassLocked = false;

function toggleCompass() {
  const map = window._map;
  if (!map) return;
  const btn = document.getElementById('btn-compass');
  compassLocked = !compassLocked;
  if (compassLocked) {
    map.rotateTo(0, { duration: 300 });
    map.dragRotate.disable();
    map.touchZoomRotate.disableRotation();
    btn.classList.add('active');
  } else {
    map.dragRotate.enable();
    map.touchZoomRotate.enableRotation();
    btn.classList.remove('active');
  }
}

// ─── Геолокация ────────────────────────────────────────────────────
let locationMarker = null;

function locateMe() {
  if (!navigator.geolocation) {
    alert('Геолокация недоступна в этом браузере');
    return;
  }
  navigator.geolocation.getCurrentPosition(
    (pos) => {
      const { longitude, latitude } = pos.coords;
      const map = window._map;
      userLat = latitude;
      userLon = longitude;
      map.flyTo({ center: [longitude, latitude], zoom: 13, duration: 800 });
      if (locationMarker) {
        locationMarker.setLngLat([longitude, latitude]);
      } else {
        const el = document.createElement('div');
        el.className = 'my-location-marker';
        el.innerHTML = '<div class="my-location-dot"></div><div class="my-location-pulse"></div>';
        locationMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
          .setLngLat([longitude, latitude])
          .addTo(map);
      }
      // If in auto theme mode, recalculate with real coordinates
      if (themeMode === 'auto') applyAutoTheme();
    },
    (err) => {
      alert('Не удалось определить местоположение: ' + err.message);
    },
    { enableHighAccuracy: true, timeout: 10000 }
  );
}

// ─── Layer visibility state ────────────────────────────────────────
const layerState = { tracks: true, paths: true, poi: true, basemap: true };
const layerGroups = {
  tracks:  ['trails-track', 'trails-asphalt'],
  paths:   ['trails-path-bridleway'],
  poi:     ['poi-circles', 'poi-labels'],
  basemap: ['osm-base'],
};

function toggleLayer(group) {
  layerState[group] = !layerState[group];
  const btn = document.getElementById('btn-' + group);
  btn.classList.toggle('active', layerState[group]);
  const visibility = layerState[group] ? 'visible' : 'none';
  layerGroups[group].forEach(id => {
    if (window._map && window._map.getLayer(id)) {
      window._map.setLayoutProperty(id, 'visibility', visibility);
    }
  });
}

// ─── Роутинг - состояние ───────────────────────────────────────────
const ROUTE_COLORS = ['#0066ff', '#00aa44', '#9933cc', '#ff8800', '#888888'];
let routeMode = false;
let routeWaypoints = [];
let routeResults = [];
let activeRouteIdx = 0;
let waypointMarkers = [];
let addingWaypoint = false;
let buildDebounceTimer = null;

function getBasePath() {
  return window.location.pathname.replace(/\/[^/]*$/, '') || '';
}

// ─── Режим маршрута ────────────────────────────────────────────────
function toggleRouteMode() {
  const btn = document.getElementById('tb-route');

  if (routeMode) {
    // If sheet is open - close sheet but stay in mode
    const sheet = document.getElementById('sheet-route');
    if (sheet && sheet.classList.contains('open')) {
      closeSheet('sheet-route');
      return;
    }
    // Sheet is closed - exit mode and clear route
    routeMode = false;
    btn.classList.remove('active');
    clearRoute();
    window._map.getCanvas().style.cursor = '';
  } else {
    // Enter route mode - show onboarding mini-bar instead of full sheet
    deactivateAllModes();
    routeMode = true;
    btn.classList.add('active');
    clearRoute();
    window._map.getCanvas().style.cursor = 'crosshair';
    showRouteOnboardingMini();
  }
  updateMapModeClass();
}

function clearRoute() {
  hideMiniRouteSheet();
  waypointMarkers.forEach(m => m.remove());
  waypointMarkers = [];
  routeWaypoints = [];
  routeResults = [];
  activeRouteIdx = 0;
  addingWaypoint = false;
  const map = window._map;
  if (map) {
    for (let i = 0; i < 5; i++) {
      if (map.getLayer('route-line-' + i)) map.removeLayer('route-line-' + i);
      if (map.getLayer('route-line-' + i + '-outline')) map.removeLayer('route-line-' + i + '-outline');
      if (map.getSource('route-' + i)) map.removeSource('route-' + i);
    }
  }
  document.getElementById('route-status').textContent = 'Тапни точку старта на карте';
  document.getElementById('route-cards').innerHTML = '';
  document.getElementById('waypoints-list').innerHTML = '';
  if (routeMode && map) map.getCanvas().style.cursor = 'crosshair';
  hideMiniOnboard();
}

function resetRouteFromSheet() {
  clearRoute();
  closeSheet('sheet-route');
  if (routeMode) showRouteOnboardingMini();
}
window.resetRouteFromSheet = resetRouteFromSheet;

function addWaypointMode() {
  if (routeWaypoints.length >= 10) return;
  if (!routeMode) {
    routeMode = true;
    document.getElementById('tb-route').classList.add('active');
    updateMapModeClass();
  }
  addingWaypoint = true;
  window._map.getCanvas().style.cursor = 'crosshair';
  // Hide main sheet so the map is visible for tapping
  closeSheet('sheet-route');
  // Show onboarding mini-bar for "add waypoint" mode
  _showMiniOnboardWaypoint();
}

function _showMiniOnboardWaypoint() {
  // Reuse mini-onboard UI with "add waypoint" hint
  const idx = routeWaypoints.length; // next waypoint index
  const label = String(idx);
  const color = '#0066ff';
  const hint = 'Тапни на карте — добавить точку';

  document.getElementById('mini-onboard').style.display = 'flex';
  document.getElementById('mini-dot').style.display = 'none';
  document.getElementById('mini-label').style.display = 'none';
  document.getElementById('mini-stats').style.display = 'none';
  document.getElementById('mini-wheel').style.display = 'none';
  const arrows = document.querySelector('.mini-route-arrows');
  if (arrows) arrows.style.display = 'none';
  const addBtn = document.getElementById('mini-add-btn');
  if (addBtn) addBtn.style.display = 'none';

  document.getElementById('mini-onboard-pin').innerHTML = waypointPinSvg(label, color);
  document.getElementById('mini-onboard-hint').textContent = hint;

  document.getElementById('sheet-route-mini').classList.add('visible');
  const ctrl = document.getElementById('map-controls-r');
  if (ctrl) ctrl.style.bottom = '148px';

  // Search button
  const searchBtn = document.getElementById('mini-onboard-search-btn');
  searchBtn.onclick = () => toggleMiniOnboardSearch('waypoint');

  // Показать кнопку отмены
  const cancelBtn = document.getElementById('mini-onboard-cancel-btn');
  if (cancelBtn) { cancelBtn.style.display = 'inline-flex'; cancelBtn.onclick = cancelAddWaypoint; }
}

// ─── Маркеры точек ─────────────────────────────────────────────────
function createWaypointMarkerEl(index, total) {
  const el = document.createElement('div');
  el.className = 'route-waypoint-marker';
  el.style.animation = 'none';

  let bg, label;
  if (index === 0) {
    bg = '#2EA043'; label = 'S';
  } else if (index === total - 1) {
    bg = '#FF3B1F'; label = 'F';
  } else {
    bg = '#0066ff'; label = String(index);
  }

  if (label === 'F') {
    const uid = Math.random().toString(36).slice(2);
    el.innerHTML = `<svg width="28" height="36" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <pattern id="chk-${uid}" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
          <rect width="4" height="4" fill="black"/>
          <rect x="4" y="0" width="4" height="4" fill="white"/>
          <rect x="0" y="4" width="4" height="4" fill="white"/>
          <rect x="4" y="4" width="4" height="4" fill="black"/>
        </pattern>
      </defs>
      <path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="url(#chk-${uid})" stroke="white" stroke-width="1.5"/>
      <text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="11" font-weight="700" fill="white" stroke="black" stroke-width="0.5">F</text>
    </svg>`;
  } else {
    el.innerHTML = `<svg width="28" height="36" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="${bg}" stroke="white" stroke-width="1.5"/>
      <text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="${label.length > 1 ? '9' : '11'}" font-weight="700" fill="white">${label}</text>
    </svg>`;
  }

  return el;
}

function rebuildWaypointMarkers() {
  waypointMarkers.forEach(m => m.remove());
  waypointMarkers = [];
  const map = window._map;
  routeWaypoints.forEach((wp, i) => {
    const el = createWaypointMarkerEl(i, routeWaypoints.length);
    const marker = new maplibregl.Marker({ element: el, anchor: 'bottom', draggable: true })
      .setLngLat([wp.lon, wp.lat])
      .addTo(map);
    (function(idx) {
      marker.on('dragend', () => {
        const lngLat = marker.getLngLat();
        routeWaypoints[idx] = { lon: lngLat.lng, lat: lngLat.lat };
        renderWaypointsList();
        debounceBuildRoute();
      });
    })(i);
    waypointMarkers.push(marker);
  });
}

// ─── Reverse Geocoding ───────────────────────────────────────────
const geocodeCache = {};

async function reverseGeocode(lat, lon) {
  const key = `${lat.toFixed(4)},${lon.toFixed(4)}`;
  if (geocodeCache[key]) return geocodeCache[key];
  try {
    const resp = await fetch(
      `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&accept-language=ru`,
      { headers: { 'Accept-Language': 'ru' } }
    );
    const data = await resp.json();
    const a = data.address || {};
    const name = a.village || a.hamlet || a.town || a.city || a.suburb || a.road || a.county || a.state || `${lat.toFixed(3)}, ${lon.toFixed(3)}`;
    geocodeCache[key] = name;
    return name;
  } catch(e) {
    return `${lat.toFixed(3)}, ${lon.toFixed(3)}`;
  }
}

function waypointPinSvg(label, color) {
  const fs = label.length > 1 ? '7' : '9';

  // Finish flag — checkered pattern
  if (label === 'F') {
    const uid = Math.random().toString(36).slice(2);
    return `<svg width="20" height="26" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <pattern id="checker-${uid}" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
          <rect width="4" height="4" fill="black"/>
          <rect x="4" y="0" width="4" height="4" fill="white"/>
          <rect x="0" y="4" width="4" height="4" fill="white"/>
          <rect x="4" y="4" width="4" height="4" fill="black"/>
        </pattern>
        <clipPath id="pin-clip-${uid}">
          <path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z"/>
        </clipPath>
      </defs>
      <path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="url(#checker-${uid})" stroke="white" stroke-width="1.5"/>
      <text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="9" font-weight="700" fill="white" stroke="black" stroke-width="0.5">${label}</text>
    </svg>`;
  }

  return `<svg width="20" height="26" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="${color}" stroke="white" stroke-width="1.5"/>
    <text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="${fs}" font-weight="700" fill="white">${label}</text>
  </svg>`;
}

function haversineM(a, b) {
  const R = 6371000;
  const dLat = (b.lat - a.lat) * Math.PI / 180;
  const dLon = (b.lon - a.lon) * Math.PI / 180;
  const s = Math.sin(dLat/2)**2 + Math.cos(a.lat*Math.PI/180) * Math.cos(b.lat*Math.PI/180) * Math.sin(dLon/2)**2;
  return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1-s));
}

// ET-005: единица и разделитель определяются units.js (ADR-0001, риск R4).
function formatSegmentDist(m) {
  return Units.formatDistance(m);
}

// Returns array of route-distance segments (meters) for each waypoint.
// segDists[0] = 0, segDists[i] = distance along route geometry from wp[i-1] to wp[i].
// First waypoint snaps to geometry[0], last to geometry[n-1] — guarantees sum == full route length.
function getRouteSegmentDistances() {
  const route = routeResults[activeRouteIdx];
  if (!route || !route.geometry || !route.geometry.coordinates) return null;

  const coords = route.geometry.coordinates; // [[lon, lat], ...]
  const n = coords.length;
  if (n < 2 || routeWaypoints.length < 2) return null;

  // Convert geometry coords to {lat, lon} for haversineM
  const geoPts = coords.map(([lon, lat]) => ({ lat, lon }));

  // Snap each waypoint to nearest geometry index
  const snapIdx = routeWaypoints.map(wp => {
    let bestIdx = 0, bestDist = Infinity;
    for (let j = 0; j < n; j++) {
      const d = haversineM(wp, geoPts[j]);
      if (d < bestDist) { bestDist = d; bestIdx = j; }
    }
    return bestIdx;
  });

  // Force first waypoint → geometry start, last waypoint → geometry end
  // This ensures segments sum exactly to the full route geometry length
  snapIdx[0] = 0;
  snapIdx[snapIdx.length - 1] = n - 1;

  // For each segment i→i+1, sum haversine along geometry points
  const segDists = [0];
  for (let i = 1; i < routeWaypoints.length; i++) {
    const from = snapIdx[i - 1];
    const to   = snapIdx[i];
    if (from === to) {
      segDists.push(haversineM(routeWaypoints[i - 1], routeWaypoints[i]));
      continue;
    }
    const step = to > from ? 1 : -1;
    let dist = 0;
    for (let j = from; j !== to; j += step) {
      dist += haversineM(geoPts[j], geoPts[j + step]);
    }
    segDists.push(dist);
  }

  // Scale so sum of segments == route.distance_m exactly
  const rawTotal = segDists.slice(1).reduce((a, b) => a + b, 0);
  if (rawTotal > 0 && route.distance_m > 0) {
    const scale = route.distance_m / rawTotal;
    for (let i = 1; i < segDists.length; i++) segDists[i] = Math.round(segDists[i] * scale);
  }

  return segDists;
}

async function renderWaypointsList() {
  const list = document.getElementById('waypoints-list');

  // ── Onboarding: no waypoints yet ──────────────────────────────
  if (!routeWaypoints.length) {
    list.innerHTML = `
      <div class="wl-onboarding">
        <div class="wl-onboard-field" id="wl-onboard-start">
          <div class="wl-pin">${waypointPinSvg('S', '#2EA043')}</div>
          <div style="flex:1">
            <input class="wl-onboard-input" id="wl-onboard-input-start"
                   type="text" placeholder="Откуда? Введи название..."
                   autocomplete="off" autocorrect="off">
            <div class="wl-search-results" id="wl-onboard-results-start"></div>
          </div>
        </div>
        <div class="wl-onboard-hint">или тапни на карте</div>
      </div>`;
    _initOnboardSearch('start');
    _initWaypointDragHandles(list);
    return;
  }

  // ── Onboarding: only start added, need finish ──────────────────
  // (handled below after normal list render)

  const gripSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>`;

  const segDists = (routeResults.length > 0 && activeRouteIdx >= 0)
    ? getRouteSegmentDistances()
    : null;

  let html = routeWaypoints.map((wp, i) => {
    const isStart = i === 0;
    const isEnd = i === routeWaypoints.length - 1;
    const label = isStart ? 'S' : isEnd ? 'F' : String(i);
    const color = isStart ? '#2EA043' : isEnd ? '#FF3B1F' : '#0066ff';
    const coordText = `${wp.lat.toFixed(3)}, ${wp.lon.toFixed(3)}`;
    const distStr = i > 0 && segDists ? formatSegmentDist(segDists[i]) :
                    i > 0 ? formatSegmentDist(haversineM(routeWaypoints[i-1], wp)) : '';
    return `<div class="wl-item" id="wl-item-${i}" data-idx="${i}">
      <div class="wl-pin">${waypointPinSvg(label, color)}</div>
      <div class="wl-info">
        <span class="wl-label" id="wl-label-${i}">${coordText}</span>
        ${distStr ? `<span class="wl-dist">${distStr}</span>` : ''}
      </div>
      <button class="wl-search-btn" onclick="openWaypointSearch(${i})" title="Поиск">
        <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
      </button>
      <div class="wl-drag-handle" data-idx="${i}">${gripSvg}</div>
      <button class="wl-remove" onclick="removeWaypoint(${i})" title="Удалить">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
      </button>
    </div>
    <div class="wl-search-panel" id="wl-search-panel-${i}" style="display:none">
      <input class="wl-search-input" id="wl-search-input-${i}" type="text" placeholder="Поиск места..." autocomplete="off" autocorrect="off">
      <div class="wl-search-results" id="wl-search-results-${i}"></div>
    </div>`;
  }).join('');

  // Кнопка «Добавить точку» в стиле wl-item
  if (routeWaypoints.length < 10) {
    html += `<div class="wl-item wl-add" onclick="addWaypointMode()">
      <div class="wl-pin">${waypointPinSvg('+', 'var(--text3)')}</div>
      <span class="wl-label">Добавить точку</span>
    </div>`;
  }

  // Onboarding finish field: only start added, no finish yet
  if (routeWaypoints.length === 1) {
    html += `
      <div class="wl-onboard-field" id="wl-onboard-finish">
        <div class="wl-pin">${waypointPinSvg('F', '#FF3B1F')}</div>
        <div style="flex:1">
          <input class="wl-onboard-input" id="wl-onboard-input-finish"
                 type="text" placeholder="Куда? Введи название..."
                 autocomplete="off" autocorrect="off">
          <div class="wl-search-results" id="wl-onboard-results-finish"></div>
        </div>
      </div>
      <div class="wl-onboard-hint">или тапни на карте</div>`;
  }

  list.innerHTML = html;

  // Init finish onboard search if only 1 waypoint
  if (routeWaypoints.length === 1) {
    _initOnboardSearch('finish');
  }

  // Async geocode
  routeWaypoints.forEach(async (wp, i) => {
    const name = await reverseGeocode(wp.lat, wp.lon);
    const el = document.getElementById(`wl-label-${i}`);
    if (el) el.textContent = name;
  });

  // Touch drag-and-drop (mobile only)
  _initWaypointDragHandles(list);
}

// ─── Onboard search helpers ────────────────────────────────────────
function _initOnboardSearch(type) {
  const input = document.getElementById(`wl-onboard-input-${type}`);
  const resultsEl = document.getElementById(`wl-onboard-results-${type}`);
  if (!input) return;

  let timeout = null;
  input.addEventListener('input', () => {
    clearTimeout(timeout);
    const q = input.value.trim();
    if (q.length < 2) { resultsEl.innerHTML = ''; return; }
    timeout = setTimeout(() => _doOnboardSearch(type, q, resultsEl), 400);
  });

  // Autofocus only for start field (finish field appears inline)
  if (type === 'start') {
    setTimeout(() => input.focus(), 100);
  }
}

async function _doOnboardSearch(type, query, resultsEl) {
  resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--text3)">Поиск...</span></div>';
  try {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5&countrycodes=ru&accept-language=ru`;
    const resp = await fetch(url);
    const data = await resp.json();
    if (!data.length) {
      resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--text3)">Ничего не найдено</span></div>';
      return;
    }
    resultsEl.innerHTML = data.map(item => {
      const parts = (item.display_name || '').split(', ');
      const name = parts[0];
      const sub = parts.slice(1, 3).join(', ');
      return `<div class="wl-search-result-item" onclick="_selectOnboardResult('${type}', ${item.lat}, ${item.lon}, '${name.replace(/'/g, "\\'")}')">  
        <div class="wl-search-result-name">${name}</div>
        ${sub ? `<div class="wl-search-result-sub">${sub}</div>` : ''}
      </div>`;
    }).join('');
  } catch(e) {
    resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--red)">Ошибка</span></div>';
  }
}

function _selectOnboardResult(type, lat, lon, name) {
  const wp = { lat: parseFloat(lat), lon: parseFloat(lon) };
  if (type === 'start') {
    routeWaypoints.unshift(wp);
  } else {
    routeWaypoints.push(wp);
  }
  rebuildWaypointMarkers();
  renderWaypointsList();
  window._map.flyTo({ center: [parseFloat(lon), parseFloat(lat)], zoom: 12, duration: 600 });
  if (routeWaypoints.length >= 2) debounceBuildRoute();
  updateMiniRouteCard();
}

function _initWaypointDragHandles(list) {
  let dragIdx = -1;
  let startY = 0;
  let dragging = false;
  let lastOverEl = null;
  let lastOverPos = null;

  function getItemEls() {
    return Array.from(list.querySelectorAll('.wl-item[data-idx]'));
  }

  function clearHighlights() {
    getItemEls().forEach(el => {
      el.classList.remove('drag-over-top', 'drag-over-bottom', 'dragging');
    });
  }

  function getDropTarget(clientY) {
    const items = getItemEls();
    for (const el of items) {
      const idx = parseInt(el.dataset.idx, 10);
      if (idx === dragIdx) continue;
      const rect = el.getBoundingClientRect();
      if (clientY >= rect.top && clientY <= rect.bottom) {
        const mid = rect.top + rect.height / 2;
        return { el, idx, pos: clientY < mid ? 'top' : 'bottom' };
      }
    }
    return null;
  }

  function startDrag(clientY, idx) {
    dragIdx = idx;
    startY = clientY;
    dragging = false;
    lastOverEl = null;
    lastOverPos = null;
    const dragEl = document.getElementById(`wl-item-${idx}`);
    if (dragEl) dragEl.classList.add('dragging');
  }

  function moveDrag(clientY) {
    if (dragIdx < 0) return;
    const dy = Math.abs(clientY - startY);
    if (dy > 5) dragging = true;
    if (!dragging) return;
    clearHighlights();
    const target = getDropTarget(clientY);
    if (target) {
      lastOverEl = target.el;
      lastOverPos = target.pos;
      target.el.classList.add(target.pos === 'top' ? 'drag-over-top' : 'drag-over-bottom');
    } else {
      lastOverEl = null;
      lastOverPos = null;
    }
  }

  function endDrag(finalClientY) {
    if (dragIdx < 0) return;
    clearHighlights();
    const dy = Math.abs(finalClientY - startY);

    if (dragging && dy > 30 && lastOverEl !== null) {
      const dropIdx = parseInt(lastOverEl.dataset.idx, 10);
      let insertAt = lastOverPos === 'top' ? dropIdx : dropIdx + 1;
      const moved = routeWaypoints.splice(dragIdx, 1)[0];
      if (insertAt > dragIdx) insertAt--;
      routeWaypoints.splice(insertAt, 0, moved);
      rebuildWaypointMarkers();
      renderWaypointsList();
      if (routeWaypoints.length >= 2) debounceBuildRoute();
      updateMiniRouteCard();
    }

    dragIdx = -1;
    dragging = false;
    lastOverEl = null;
    lastOverPos = null;
  }

  // Touch (mobile)
  list.addEventListener('touchstart', (e) => {
    const handle = e.target.closest('.wl-drag-handle');
    if (!handle) return;
    startDrag(e.touches[0].clientY, parseInt(handle.dataset.idx, 10));
  }, { passive: true });

  list.addEventListener('touchmove', (e) => {
    if (dragIdx < 0) return;
    moveDrag(e.touches[0].clientY);
    e.preventDefault();
  }, { passive: false });

  list.addEventListener('touchend', (e) => {
    endDrag(e.changedTouches[0].clientY);
  }, { passive: true });

  // Mouse (desktop)
  list.addEventListener('mousedown', (e) => {
    const handle = e.target.closest('.wl-drag-handle');
    if (!handle) return;
    if (e.target.closest('.wl-add')) return; // не перехватывать кнопку добавления
    e.preventDefault();
    startDrag(e.clientY, parseInt(handle.dataset.idx, 10));
    document.addEventListener('mousemove', _onDragMouse);
    document.addEventListener('mouseup', _onDropMouse);
  });

  function _onDragMouse(e) {
    if (dragIdx < 0) return;
    moveDrag(e.clientY);
  }

  function _onDropMouse(e) {
    endDrag(e.clientY);
    document.removeEventListener('mousemove', _onDragMouse);
    document.removeEventListener('mouseup', _onDropMouse);
  }
}

function removeWaypoint(idx) {
  routeWaypoints.splice(idx, 1);
  rebuildWaypointMarkers();
  renderWaypointsList();
  if (routeWaypoints.length >= 2) {
    debounceBuildRoute();
  } else {
    const map = window._map;
    for (let i = 0; i < 5; i++) {
      if (map.getLayer('route-line-' + i)) map.removeLayer('route-line-' + i);
      if (map.getLayer('route-line-' + i + '-outline')) map.removeLayer('route-line-' + i + '-outline');
      if (map.getSource('route-' + i)) map.removeSource('route-' + i);
    }
    routeResults = [];
    document.getElementById('route-cards').innerHTML = '';
    document.getElementById('route-status').textContent =
      routeWaypoints.length === 0 ? 'Тапни точку старта на карте' :
      routeWaypoints.length === 1 ? 'Тапни точку финиша' : '';
  }
}

// ─── Построение маршрута ───────────────────────────────────────────
function debounceBuildRoute() {
  clearTimeout(buildDebounceTimer);
  buildDebounceTimer = setTimeout(buildRoute, 300);
}

async function buildRoute() {
  if (routeWaypoints.length < 2) return;
  const map = window._map;
  const basePath = getBasePath();

  // Show mini-bar with spinning wheel
  showMiniRouteLoading();
  showRouteLoading();
  // Close main sheet if open
  closeSheet('sheet-route');

  document.getElementById('route-status').textContent = 'Строю маршрут...';
  showSkeleton('route-cards', 3);

  try {
    const resp = await fetch(basePath + '/api/route', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ waypoints: routeWaypoints, alternatives: 5 }),
    });
    if (!resp.ok) throw new Error('Маршрут не найден');
    const data = await resp.json();
    routeResults = data.routes || [];
    if (!routeResults.length) throw new Error('Маршрут не найден');

    drawRouteResults(routeResults, 0);
    renderWaypointsList(); // update segment distances now that route is built

    document.getElementById('route-status').textContent = `${routeResults.length} маршрут(ов)`;
    // Show mini-bar with result - do NOT open main sheet
    hideMiniRouteLoading();
    showMiniRouteSheet();
  } catch(e) {
    hideMiniRouteLoading();
    document.getElementById('route-status').textContent = '❌ ' + e.message;
    document.getElementById('route-cards').innerHTML = '';
    const statsEl = document.getElementById('mini-stats');
    if (statsEl) statsEl.textContent = '❌ ' + e.message;
  }
}

function drawRouteResults(routes, activeIdx) {
  const map = window._map;
  activeRouteIdx = activeIdx;
  const wasBuilt = routeResults.length > 0; // track rebuild vs first build
  routeResults = routes;

  // Clear old layers
  for (let i = 0; i < 5; i++) {
    try { if (map.getLayer('route-line-' + i)) map.removeLayer('route-line-' + i); } catch(e) {}
    try { if (map.getLayer('route-line-' + i + '-outline')) map.removeLayer('route-line-' + i + '-outline'); } catch(e) {}
    try { if (map.getSource('route-' + i)) map.removeSource('route-' + i); } catch(e) {}
  }

  routes.forEach((route, i) => {
    const color = ROUTE_COLORS[i] || '#888888';
    const isActive = i === activeIdx;
    map.addSource('route-' + i, {
      type: 'geojson',
      data: { type: 'Feature', geometry: route.geometry, properties: {} }
    });
    map.addLayer({
      id: 'route-line-' + i + '-outline',
      type: 'line', source: 'route-' + i,
      paint: {
        'line-color': '#ffffff',
        'line-width': isActive ? 7 : 4,
        'line-opacity': isActive ? 0.6 : 0,
      },
      layout: { 'line-cap': 'round', 'line-join': 'round' }
    });
    map.addLayer({
      id: 'route-line-' + i,
      type: 'line', source: 'route-' + i,
      paint: {
        'line-color': color,
        'line-width': isActive ? 5 : 3,
        'line-opacity': isActive ? 0.95 : 0.5,
      },
      layout: { 'line-cap': 'round', 'line-join': 'round' }
    });

    map.on('click', 'route-line-' + i, (e) => {
      if (e.stopPropagation) e.stopPropagation();
      selectRoute(i);
    });
    map.on('mouseenter', 'route-line-' + i, () => {
      map.getCanvas().style.cursor = 'pointer';
    });
    map.on('mouseleave', 'route-line-' + i, () => {
      map.getCanvas().style.cursor = routeMode ? 'crosshair' : '';
    });
  });

  renderRouteCards(routes);

  // Auto-zoom to active route after drawing
  const activeRoute = routes[activeIdx] || routes[0];
  if (activeRoute && activeRoute.geometry && activeRoute.geometry.coordinates) {
    const coords = activeRoute.geometry.coordinates;
    if (coords.length > 1) {
      const bounds = coords.reduce(
        (b, c) => b.extend(c),
        new maplibregl.LngLatBounds(coords[0], coords[0])
      );
      map.fitBounds(bounds, {
        padding: { top: 80, bottom: 160, left: 20, right: 20 },
        duration: 1200,
        maxZoom: 14
      });
    }
  }

  // Update mini sheet if visible
  const miniEl = document.getElementById('sheet-route-mini');
  if (miniEl && miniEl.classList.contains('visible')) showMiniRouteSheet();

  // Auto-minimize sheet on rebuild (not on first build)
  if (wasBuilt) {
    const sheet = document.getElementById('sheet-route');
    if (sheet && sheet.classList.contains('open')) {
      minimizeSheet('sheet-route');
    }
  }
}

function selectRoute(idx) {
  activeRouteIdx = idx;
  const map = window._map;
  routeResults.forEach((_, i) => {
    const isActive = i === idx;
    try {
      if (map.getLayer('route-line-' + i)) {
        map.setPaintProperty('route-line-' + i, 'line-width', isActive ? 5 : 3);
        map.setPaintProperty('route-line-' + i, 'line-opacity', isActive ? 0.95 : 0.5);
      }
      if (map.getLayer('route-line-' + i + '-outline')) {
        map.setPaintProperty('route-line-' + i + '-outline', 'line-width', isActive ? 7 : 4);
        map.setPaintProperty('route-line-' + i + '-outline', 'line-opacity', isActive ? 0.6 : 0);
      }
    } catch(e) {}
  });
  document.querySelectorAll('.route-card').forEach((card, i) => {
    card.classList.toggle('active', i === idx);
  });
  renderWaypointsList();
}

// ─── Карточки маршрутов ───────────────────────────────────────────
function renderRouteCards(routes) {
  const container = document.getElementById('route-cards');
  container.innerHTML = routes.map((route, i) => {
    const color = ROUTE_COLORS[i] || '#888888';
    const timeStr = formatDuration(route.duration_s);
    const isActive = i === activeRouteIdx;
    const s = route.stats || {};
    const dirtPct = s.dirt_total_pct || 0;
    const asphPct = s.asphalt_pct || 0;

    return `<div class="route-card${isActive ? ' active' : ''}" onclick="selectRoute(${i})">
      <div class="rc-header">
        <span class="rc-dot" style="background:${color}"></span>
        <span class="rc-title">Вариант ${i + 1}</span>
        <span class="rc-meta">${Units.formatDistance(route.distance_m)} · ${timeStr}</span>
      </div>
      <div class="rc-bar-wrap">
        <div class="rc-bar">
          <div class="rc-bar-dirt" style="width:${dirtPct}%"></div>
          <div class="rc-bar-asphalt" style="width:${asphPct}%"></div>
        </div>
      </div>
      <div class="rc-bar-label">${dirtPct}% грунт${asphPct ? ` · ${asphPct}% асфальт` : ''}</div>
    </div>`;
  }).join('');
}

// ─── GPX экспорт ───────────────────────────────────────────────────
function escapeXml(str) {
  return (str || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

function generateGPX() {
  const route = routeResults[activeRouteIdx];
  if (!route) return '';
  const now = new Date();
  const dateStr = now.toISOString().slice(0, 10);
  // ET-005: экспорт GPX остаётся метрическим — стандарт GPX и риск R6
  // исключают конвертацию выгружаемых данных в мили.
  const distKm = (route.distance_m / 1000).toFixed(1);
  const dirtPct = route.stats ? route.stats.dirt_total_pct : '?';

  const wpts = routeWaypoints.map((wp, i) => {
    const name = i === 0 ? 'Старт' : i === routeWaypoints.length - 1 ? 'Финиш' : `Точка ${i}`;
    return `  <wpt lat="${wp.lat}" lon="${wp.lon}"><name>${escapeXml(name)}</name></wpt>`;
  });
  const markers = loadMarkers();
  markers.forEach(m => {
    wpts.push(`  <wpt lat="${m.lat}" lon="${m.lon}"><name>${escapeXml(m.name)}</name><sym>${escapeXml(m.icon)}</sym></wpt>`);
  });

  const trkpts = route.geometry.coordinates.map(([lon, lat]) =>
    `      <trkpt lat="${lat}" lon="${lon}"/>`
  ).join('\n');

  return `<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Enduro Trails" xmlns="http://www.topografix.com/GPX/1/1">
  <metadata>
    <name>Enduro route ${dateStr}</name>
    <desc>${distKm} км · ${dirtPct}% грунт</desc>
    <time>${now.toISOString()}</time>
  </metadata>
${wpts.join('\n')}
  <trk>
    <name>Enduro route ${dateStr}</name>
    <trkseg>
${trkpts}
    </trkseg>
  </trk>
</gpx>`;
}

function downloadGPX() {
  const gpx = generateGPX();
  if (!gpx) return;
  const now = new Date();
  const timeStr = now.toISOString().replace(/[-:]/g, '').slice(0, 15);
  const filename = `enduro-${timeStr}.gpx`;
  const blob = new Blob([gpx], { type: 'application/gpx+xml' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

// ─── Флажки / именованные метки ────────────────────────────────────
const MARKER_ICONS = ['🚩', '⛺', '🔧', '⛽', '💧', '📍'];
const MARKERS_KEY = 'enduro_markers';
let markerMode = false;
let namedMarkerObjects = {};

function loadMarkers() {
  try { return JSON.parse(localStorage.getItem(MARKERS_KEY) || '[]'); } catch(e) { return []; }
}
function saveMarkers(markers) {
  try { localStorage.setItem(MARKERS_KEY, JSON.stringify(markers)); } catch(e) {}
}

function toggleMarkerMode() {
  markerMode = !markerMode;
  const btn = document.getElementById('tb-marker');
  if (markerMode) {
    deactivateAllModes();
    markerMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
  } else {
    btn.classList.remove('active');
    window._map.getCanvas().style.cursor = '';
  }
  updateMapModeClass();
}

function addMarker(lngLat) {
  const markers = loadMarkers();
  if (markers.length >= 50) { alert('Достигнут лимит 50 меток'); return; }

  const grid = document.getElementById('marker-type-grid');
  // Show marker dialog
  openMarkerDialog(lngLat);
}

function openMarkerDialog(lngLat) {
  const dialog = document.getElementById('marker-dialog');
  const grid = document.getElementById('marker-type-grid');
  grid.innerHTML = MARKER_ICONS.map((ic, i) =>
    `<button class="marker-type-btn" onclick="selectMarkerType(${i}, ${lngLat.lat}, ${lngLat.lng})">
      <span class="mt-icon">${ic}</span>
      <span class="mt-label">${['Флаг','Лагерь','Ремонт','Заправка','Вода','Точка'][i]}</span>
    </button>`
  ).join('');
  dialog.classList.add('open');
}

function closeMarkerDialog() {
  document.getElementById('marker-dialog').classList.remove('open');
}

function selectMarkerType(idx, lat, lng) {
  closeMarkerDialog();
  const markers = loadMarkers();
  const icon = MARKER_ICONS[idx] || MARKER_ICONS[0];
  const autoName = `Метка ${markers.length + 1}`;
  const marker = { id: Date.now(), name: autoName, icon, lat, lon: lng };
  markers.push(marker);
  saveMarkers(markers);
  drawNamedMarker(marker);
}

function drawNamedMarker(markerData) {
  const map = window._map;
  if (!map) return;
  const el = document.createElement('div');
  el.className = 'named-marker-el';
  el.textContent = markerData.icon;
  el.title = markerData.name;

  const popup = new maplibregl.Popup({ offset: 25, closeButton: true })
    .setHTML(`
      <div class="popup-title">${escapeXml(markerData.name)}</div>
      <div class="popup-row"><span class="popup-key">Координаты</span><span class="popup-val">${markerData.lat.toFixed(5)}, ${markerData.lon.toFixed(5)}</span></div>
      <div style="margin-top:8px;display:flex;gap:6px;flex-wrap:wrap;">
        <button onclick="useMarkerAsA(${markerData.id})" style="flex:1;padding:3px 6px;background:var(--success);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;">→ Точка A</button>
        <button onclick="useMarkerAsB(${markerData.id})" style="flex:1;padding:3px 6px;background:var(--red);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;">→ Точка B</button>
        <button onclick="removeMarker(${markerData.id})" style="flex:1;padding:3px 6px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;cursor:pointer;font-size:11px;">🗑 Удалить</button>
      </div>
    `);

  const mlMarker = new maplibregl.Marker({ element: el, anchor: 'bottom' })
    .setLngLat([markerData.lon, markerData.lat])
    .setPopup(popup)
    .addTo(map);

  namedMarkerObjects[markerData.id] = mlMarker;
}

function renderMarkers() {
  // Clear existing marker objects to prevent duplicates
  Object.keys(namedMarkerObjects).forEach(id => {
    const obj = namedMarkerObjects[id];
    if (obj) {
      const popup = obj.getPopup();
      if (popup) popup.remove();
      obj.remove();
    }
  });
  namedMarkerObjects = {};
  // Re-draw from localStorage
  const markers = loadMarkers();
  markers.forEach(m => drawNamedMarker(m));
}

function removeMarker(id) {
  if (namedMarkerObjects[id]) {
    const popup = namedMarkerObjects[id].getPopup();
    if (popup) popup.remove();
    namedMarkerObjects[id].remove();
    delete namedMarkerObjects[id];
  }
  const markers = loadMarkers().filter(m => m.id !== id);
  saveMarkers(markers);
}

function useMarkerAsA(id) {
  const markers = loadMarkers();
  const m = markers.find(x => x.id === id);
  if (!m) return;
  if (!routeMode) toggleRouteMode();
  if (routeWaypoints.length === 0) routeWaypoints.push({ lon: m.lon, lat: m.lat });
  else routeWaypoints[0] = { lon: m.lon, lat: m.lat };
  rebuildWaypointMarkers(); renderWaypointsList();
  if (routeWaypoints.length >= 2) debounceBuildRoute();
  if (namedMarkerObjects[id]) namedMarkerObjects[id].getPopup().remove();
}

function useMarkerAsB(id) {
  const markers = loadMarkers();
  const m = markers.find(x => x.id === id);
  if (!m) return;
  if (!routeMode) toggleRouteMode();
  if (routeWaypoints.length === 0) { routeWaypoints.push({ lon: m.lon, lat: m.lat }); routeWaypoints.push({ lon: m.lon, lat: m.lat }); }
  else if (routeWaypoints.length === 1) routeWaypoints.push({ lon: m.lon, lat: m.lat });
  else routeWaypoints[routeWaypoints.length - 1] = { lon: m.lon, lat: m.lat };
  rebuildWaypointMarkers(); renderWaypointsList();
  if (routeWaypoints.length >= 2) debounceBuildRoute();
  if (namedMarkerObjects[id]) namedMarkerObjects[id].getPopup().remove();
}

// ─── Map init ──────────────────────────────────────────────────────
async function initMap() {
  const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || '';
  const tileBase = window.location.origin + basePath;
  const style = await fetch(basePath + '/style.json').then(r => r.json());
  style.sources['trails-tiles'].tiles = [`${tileBase}/api/tiles/{z}/{x}/{y}.mvt`];

  const map = new maplibregl.Map({
    container: 'map',
    style: style,
    center: [40.5, 55.5],
    zoom: 7,
    minZoom: 4,
    maxZoom: 18,
    hash: true,
  });
  window._map = map;

  map.addControl(new maplibregl.NavigationControl(), 'top-left');
  map.addControl(new maplibregl.FullscreenControl(), 'top-left');
  // Custom scale + zoom indicator (one line, top-right)
  const scaleZoomBar = document.createElement('div');
  scaleZoomBar.id = 'scale-zoom-bar';
  scaleZoomBar.innerHTML = '<div class="szb-scale"><span class="szb-label">30 km</span></div><div class="szb-zoom">z7</div>';
  document.getElementById('map').appendChild(scaleZoomBar);

  function updateScaleZoom() {
    const zoom = Math.round(map.getZoom());
    const lat = map.getCenter().lat;
    const metersPerPixel = 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, map.getZoom());
    
    const targetPx = 80;
    const rawMeters = metersPerPixel * targetPx;

    // ET-005 (риск R3): масштабная линейка учитывает выбранную единицу.
    // niceMeters всегда остаётся в метрах — по нему считается ширина в px.
    let distance, unit, niceMeters;
    if (window.Units && Units.getUnit() === 'mi') {
      const rawMiles = (rawMeters / 1000) * Units.KM_TO_MI;
      if (rawMiles >= 1) {
        distance = rawMiles >= 100 ? Math.round(rawMiles / 50) * 50 :
                   rawMiles >= 10  ? Math.round(rawMiles / 5) * 5 :
                                     Math.round(rawMiles);
      } else {
        distance = Math.max(0.1, Math.round(rawMiles * 10) / 10);
      }
      unit = 'mi';
      niceMeters = (distance / Units.KM_TO_MI) * 1000;
    } else if (rawMeters >= 1000) {
      const km = rawMeters / 1000;
      distance = km >= 100 ? Math.round(km / 50) * 50 :
                 km >= 10 ? Math.round(km / 5) * 5 :
                 km >= 1 ? Math.round(km) : Math.round(km * 10) / 10;
      unit = 'km';
      niceMeters = distance * 1000;
    } else {
      distance = rawMeters >= 100 ? Math.round(rawMeters / 50) * 50 :
                 rawMeters >= 10 ? Math.round(rawMeters / 5) * 5 :
                 Math.round(rawMeters);
      unit = 'm';
      niceMeters = distance;
    }
    
    const actualPx = Math.round(niceMeters / metersPerPixel);
    const clampedPx = Math.max(40, Math.min(150, actualPx));
    
    const scaleEl = scaleZoomBar.querySelector('.szb-scale');
    const labelEl = scaleZoomBar.querySelector('.szb-label');
    const zoomEl = scaleZoomBar.querySelector('.szb-zoom');
    
    scaleEl.style.width = clampedPx + 'px';
    labelEl.textContent = distance + ' ' + unit;
    zoomEl.textContent = 'z' + zoom;
  }

  // ET-005: оркестратор onUnitChange() обновляет линейку при смене единицы.
  window._updateScaleZoom = updateScaleZoom;
  updateScaleZoom();
  map.on('zoom', updateScaleZoom);
  map.on('move', updateScaleZoom);

  map.on('load', () => {
    checkDataAvailability();
    initRouteClicks(map);
    initRulerClicks(map);
    renderMarkers();
    // Apply theme on load
    applyTheme();
    // Start auto-theme interval
    themeAutoInterval = setInterval(() => {
      if (themeMode === 'auto') applyAutoTheme();
    }, 60000);
  });

  map.on('style.load', () => {
    onMapStyleLoad();
  });

  map.on('error', (e) => {
    console.error('Map error:', e.error?.message || e);
  });

  // Popup for trail features
  const popup = new maplibregl.Popup({
    closeButton: true,
    closeOnClick: false,
    maxWidth: '300px',
  });

  // ET-005: расстояние во всплывающих подсказках — через units.js (ADR-0001).
  function formatLength(m) {
    if (!m) return '-';
    return Units.formatDistance(m);
  }

  function poiTypeLabel(t) {
    const labels = {
      'natural=peak': '⛰ Вершина',
      'natural=water': '💧 Вода',
      'tourism=viewpoint': '👁 Смотровая',
      'historic=ruins': '🏚 Руины',
      'natural=cave_entrance': '🕳 Пещера',
      'ford=yes': '🌊 Брод',
    };
    return labels[t] || t;
  }

  ['trails-track', 'trails-path-bridleway', 'trails-asphalt'].forEach(layerId => {
    map.on('click', layerId, (e) => {
      if (routeMode || rulerMode || markerMode) return;
      const props = e.features[0].properties;
      const html = `
        <div class="popup-title">${props.name || 'Без названия'}</div>
        <div class="popup-row"><span class="popup-key">Тип</span><span class="popup-val">${props.highway || '-'}</span></div>
        <div class="popup-row"><span class="popup-key">Покрытие</span><span class="popup-val">${props.surface || '-'}</span></div>
        <div class="popup-row"><span class="popup-key">Категория</span><span class="popup-val">${props.tracktype || '-'}</span></div>
        <div class="popup-row"><span class="popup-key">Длина</span><span class="popup-val">${formatLength(props.length_m)}</span></div>
        ${props.mtb_scale ? `<div class="popup-row"><span class="popup-key">MTB scale</span><span class="popup-val">${props.mtb_scale}</span></div>` : ''}
      `;
      popup.setLngLat(e.lngLat).setHTML(html).addTo(map);
    });
    map.on('mouseenter', layerId, () => {
      if (!routeMode && !rulerMode && !markerMode) map.getCanvas().style.cursor = 'pointer';
    });
    map.on('mouseleave', layerId, () => {
      if (!routeMode && !rulerMode && !markerMode) map.getCanvas().style.cursor = '';
    });
  });

  map.on('click', 'poi-circles', (e) => {
    if (routeMode || rulerMode || markerMode) return;
    const props = e.features[0].properties;
    const html = `
      <div class="popup-title">${props.name || 'Без названия'}</div>
      <div class="popup-row"><span class="popup-key">Тип</span><span class="popup-val">${poiTypeLabel(props.poi_type)}</span></div>
    `;
    popup.setLngLat(e.lngLat).setHTML(html).addTo(map);
  });
  map.on('mouseenter', 'poi-circles', () => {
    if (!routeMode && !rulerMode && !markerMode) map.getCanvas().style.cursor = 'pointer';
  });
  map.on('mouseleave', 'poi-circles', () => {
    if (!routeMode && !rulerMode && !markerMode) map.getCanvas().style.cursor = '';
  });

  map.on('click', (e) => {
    if (routeMode || rulerMode || markerMode) return;
    const features = map.queryRenderedFeatures(e.point, {
      layers: ['trails-track', 'trails-path-bridleway', 'trails-asphalt', 'poi-circles'],
    });
    if (!features.length) popup.remove();
  });
}

async function checkDataAvailability() {
  try {
    const basePath = getBasePath();
    const resp = await fetch(basePath + '/api/health');
    const data = await resp.json();
    if (!data.db_exists) {
      document.getElementById('no-data-warning').classList.add('visible');
    }
  } catch (e) {
    console.warn('Health check failed:', e);
  }
}

// ─── Клики на карте ────────────────────────────────────────────────
function initRouteClicks(map) {
  map.on('click', (e) => {
    const { lng, lat } = e.lngLat;

    if (reconMode) { doRecon(lng, lat); return; }
    if (linkMode) { addLinkPoint(lng, lat); return; }
    if (scenicMode) {
      scenicStart = { lon: lng, lat: lat };
      document.getElementById('scenic-status').textContent = `📍 Старт: ${lat.toFixed(4)}, ${lng.toFixed(4)}`;
      if (scenicStartMarker) scenicStartMarker.remove();
      const el = document.createElement('div');
      el.className = '';
      el.style.cssText = 'width:16px;height:16px;background:var(--accent);border:2px solid #fff;border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,0.3);';
      scenicStartMarker = new maplibregl.Marker({ element: el, anchor: 'center' }).setLngLat([lng, lat]).addTo(map);
      document.getElementById('btn-build-scenic').style.display = '';
      return;
    }
    if (markerMode) {
      addMarker(e.lngLat);
      toggleMarkerMode();
      return;
    }
    if (!routeMode) return;

    if (addingWaypoint) {
      addingWaypoint = false;
      map.getCanvas().style.cursor = 'crosshair';
      if (routeWaypoints.length >= 2) {
        routeWaypoints.splice(routeWaypoints.length - 1, 0, { lon: lng, lat: lat });
      } else {
        routeWaypoints.push({ lon: lng, lat: lat });
      }
      hideMiniOnboard();
      rebuildWaypointMarkers(); renderWaypointsList();
      if (routeWaypoints.length >= 2) debounceBuildRoute();
      updateMiniRouteCard();
      return;
    }

    if (routeWaypoints.length === 0) {
      routeWaypoints.push({ lon: lng, lat: lat });
      rebuildWaypointMarkers(); renderWaypointsList();
      document.getElementById('route-status').textContent = 'Тапни точку финиша';
      showRouteOnboardingMini(); // switch to finish prompt
    } else if (routeWaypoints.length === 1) {
      routeWaypoints.push({ lon: lng, lat: lat });
      rebuildWaypointMarkers(); renderWaypointsList();
      hideMiniOnboard();
      buildRoute();
    }
  });
}

// ─── Поиск (Nominatim) ─────────────────────────────────────────────
let searchTimeout = null;

// ─── Waypoint inline search ────────────────────────────────────────
let wpSearchTimeout = null;

function openWaypointSearch(idx) {
  // Close all other open panels
  document.querySelectorAll('.wl-search-panel').forEach(p => {
    if (p.id !== `wl-search-panel-${idx}`) p.style.display = 'none';
  });
  const panel = document.getElementById(`wl-search-panel-${idx}`);
  if (!panel) return;
  const isOpen = panel.style.display !== 'none';
  panel.style.display = isOpen ? 'none' : 'block';
  if (!isOpen) {
    const input = document.getElementById(`wl-search-input-${idx}`);
    if (input) {
      input.value = '';
      input.focus();
      input.addEventListener('input', () => {
        clearTimeout(wpSearchTimeout);
        const q = input.value.trim();
        if (q.length < 2) {
          document.getElementById(`wl-search-results-${idx}`).innerHTML = '';
          return;
        }
        wpSearchTimeout = setTimeout(() => doWaypointSearch(idx, q), 400);
      });
    }
  }
}

async function doWaypointSearch(idx, query) {
  const resultsEl = document.getElementById(`wl-search-results-${idx}`);
  if (!resultsEl) return;
  resultsEl.innerHTML = '<div class="wl-search-result-item"><span class="wl-search-result-name" style="color:var(--text3)">Поиск...</span></div>';
  try {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5&countrycodes=ru&accept-language=ru`;
    const resp = await fetch(url);
    const data = await resp.json();
    if (!data.length) {
      resultsEl.innerHTML = '<div class="wl-search-result-item"><span class="wl-search-result-name" style="color:var(--text3)">Ничего не найдено</span></div>';
      return;
    }
    resultsEl.innerHTML = data.map(item => {
      const parts = (item.display_name || '').split(', ');
      const name = parts[0];
      const sub = parts.slice(1, 3).join(', ');
      return `<div class="wl-search-result-item" onclick="selectWaypointSearchResult(${idx}, ${item.lat}, ${item.lon}, '${name.replace(/'/g, "\\'")}')">
        <div class="wl-search-result-name">${name}</div>
        ${sub ? `<div class="wl-search-result-sub">${sub}</div>` : ''}
      </div>`;
    }).join('');
  } catch(e) {
    resultsEl.innerHTML = '<div class="wl-search-result-item"><span class="wl-search-result-name" style="color:var(--red)">Ошибка поиска</span></div>';
  }
}

function selectWaypointSearchResult(idx, lat, lon, name) {
  routeWaypoints[idx] = { lat: parseFloat(lat), lon: parseFloat(lon) };
  const panel = document.getElementById(`wl-search-panel-${idx}`);
  if (panel) panel.style.display = 'none';
  rebuildWaypointMarkers();
  renderWaypointsList();
  window._map.flyTo({ center: [parseFloat(lon), parseFloat(lat)], zoom: 13, duration: 600 });
  if (routeWaypoints.length >= 2) debounceBuildRoute();
  updateMiniRouteCard();
}

function initSearch() {
  const input = document.getElementById('search-input');
  const results = document.getElementById('search-results');
  input.addEventListener('input', () => {
    clearTimeout(searchTimeout);
    const q = input.value.trim();
    if (q.length < 2) { results.style.display = 'none'; return; }
    searchTimeout = setTimeout(() => doSearch(q), 400);
  });
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { results.style.display = 'none'; input.blur(); }
  });
  document.addEventListener('click', (e) => {
    if (!e.target.closest('#search-bar') && !e.target.closest('#search-results')) {
      results.style.display = 'none';
    }
  });
}

async function doSearch(query) {
  const results = document.getElementById('search-results');
  results.innerHTML = '<div class="search-result-item"><span class="sri-name" style="color:var(--text3)">Поиск...</span></div>';
  results.style.display = 'block';
  try {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=6&countrycodes=ru&accept-language=ru`;
    const resp = await fetch(url, { headers: { 'Accept-Language': 'ru' } });
    const data = await resp.json();
    if (!data.length) {
      results.innerHTML = '<div class="search-result-item"><span class="sri-name" style="color:var(--text3)">Ничего не найдено</span></div>';
      return;
    }
    results.innerHTML = data.map((item) => {
      const name = item.display_name.split(',')[0];
      const detail = item.display_name.split(',').slice(1, 3).join(',').trim();
      return `<div class="search-result-item" onclick="selectSearchResult(${item.lat}, ${item.lon}, '${name.replace(/'/g, "\\'")}')">
        <div class="sri-name">${name}</div>
        <div class="sri-sub">${detail}</div>
      </div>`;
    }).join('');
  } catch(e) {
    results.innerHTML = '<div class="search-result-item"><span class="sri-name" style="color:var(--red)">Ошибка поиска</span></div>';
  }
}

function selectSearchResult(lat, lon, name) {
  window._map.flyTo({ center: [lon, lat], zoom: 13, duration: 800 });
  document.getElementById('search-results').style.display = 'none';
  document.getElementById('search-input').value = name;
}

// ─── Линейка ───────────────────────────────────────────────────────
let rulerMode = false;
let rulerPoints = [];
let rulerMarkers = [];
let rulerTotal = 0;

function toggleRuler() {
  const btn = document.getElementById('tb-ruler');

  if (rulerMode) {
    // Режим активен → выйти из режима, скрыть линейку (точки сохраняются в rulerPoints)
    rulerMode = false;
    btn.classList.remove('active');
    window._map.getCanvas().style.cursor = '';
    // Убрать маркеры с карты + удалить DOM-элементы
    rulerMarkers.forEach(m => m.remove());
    rulerMarkers = [];
    const map = window._map;
    try { if (map.getLayer('ruler-line')) map.removeLayer('ruler-line'); } catch(e) {}
    try { if (map.getSource('ruler')) map.removeSource('ruler'); } catch(e) {}
    document.getElementById('ruler-info').classList.remove('visible');
    updateMapModeClass();

  } else if (rulerPoints.length > 0) {
    // Линейка скрыта, точки есть → восстановить и войти в режим рисования
    deactivateAllModes();
    rulerMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
    const pts = [...rulerPoints];
    rulerPoints = [];
    rulerTotal = 0;
    rulerMarkers.forEach(m => m.remove()); // удалить старые маркеры с карты
    rulerMarkers = [];
    pts.forEach(pt => addRulerPoint({ lng: pt[0], lat: pt[1] }));
    document.getElementById('ruler-info').classList.add('visible');
    updateMapModeClass();

  } else {
    // Нет линейки → войти в режим рисования
    deactivateAllModes();
    rulerMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
    showRulerToast();
    updateMapModeClass();
  }
}

function deleteRuler() {
  rulerMode = false;
  const btn = document.getElementById('tb-ruler');
  if (btn) btn.classList.remove('active');
  window._map.getCanvas().style.cursor = '';
  clearRuler();
  document.getElementById('ruler-info').classList.remove('visible');
  updateMapModeClass();
}

// Fix 4: Toast hint helper
function showRulerToast() {
  const toast = document.getElementById('ruler-toast');
  if (!toast) return;
  toast.classList.add('visible');
  setTimeout(() => toast.classList.remove('visible'), 3000);
}

// Exit ruler mode without clearing points/markers ("Завершить")
function exitRulerMode() {
  if (!rulerMode) return;
  rulerMode = false;
  const btn = document.getElementById('tb-ruler');
  btn.classList.remove('active');
  window._map.getCanvas().style.cursor = '';
  document.getElementById('ruler-info').classList.remove('visible');
  updateMapModeClass();
}

function clearRuler() {
  rulerPoints = [];
  rulerTotal = 0;
  rulerMarkers.forEach(m => m.remove());
  rulerMarkers = [];
  const map = window._map;
  try { if (map.getLayer('ruler-line')) map.removeLayer('ruler-line'); } catch(e) {}
  try { if (map.getSource('ruler')) map.removeSource('ruler'); } catch(e) {}
}

function haversineKm(a, b) {
  const R = 6371;
  const dLat = (b[1] - a[1]) * Math.PI / 180;
  const dLon = (b[0] - a[0]) * Math.PI / 180;
  const s = Math.sin(dLat/2)**2 + Math.cos(a[1]*Math.PI/180) * Math.cos(b[1]*Math.PI/180) * Math.sin(dLon/2)**2;
  return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1-s));
}

function updateRulerLine() {
  const map = window._map;
  const geojson = { type: 'Feature', geometry: { type: 'LineString', coordinates: rulerPoints } };
  if (map.getSource('ruler')) {
    map.getSource('ruler').setData(geojson);
  } else {
    map.addSource('ruler', { type: 'geojson', data: geojson });
    map.addLayer({
      id: 'ruler-line', type: 'line', source: 'ruler',
      paint: { 'line-color': '#0088ff', 'line-width': 2, 'line-dasharray': [4, 2], 'line-opacity': 0.9 }
    });
  }
  // Update ruler info display
  // ET-005: rulerTotal хранится в км — переводим в метры для units.js.
  const dist = Units.formatDistance(rulerTotal * 1000);
  document.getElementById('ruler-dist').textContent = dist;
}

function removeRulerPoint(idx) {
  const map = window._map;
  // Remove marker from map
  rulerMarkers[idx].remove();
  rulerMarkers.splice(idx, 1);
  rulerPoints.splice(idx, 1);
  // Recalculate total and update all labels
  updateRulerLabels();
  updateRulerLine();
}

function updateRulerLabels() {
  // Recalculate rulerTotal from scratch and update label elements on each marker
  rulerTotal = 0;
  for (let i = 0; i < rulerMarkers.length; i++) {
    const markerEl = rulerMarkers[i].getElement();
    const dot = markerEl.querySelector('.ruler-dot');
    const label = markerEl.querySelector('.ruler-label');
    const btn = markerEl.querySelector('.ruler-remove-btn');
    const labelText = label ? label.querySelector('span') : null;
    
    // Fix 5: Update dot color for first point
    if (dot) {
      const dotColor = i === 0 ? '#2EA043' : '#0088ff';
      dot.style.background = dotColor;
    }
    
    if (i === 0) {
      if (labelText) labelText.textContent = 'Старт';
    } else {
      const segDist = haversineKm(rulerPoints[i - 1], rulerPoints[i]);
      rulerTotal += segDist;
      if (labelText) {
        // ET-005: segDist в км — переводим в метры для units.js.
        labelText.textContent = Units.formatDistance(segDist * 1000);
      }
    }
    // Update remove button index
    if (btn) {
      btn.onclick = (e) => { e.stopPropagation(); removeRulerPoint(i); };
    }
  }
  // Update total display
  // ET-005: rulerTotal хранится в км — переводим в метры для units.js.
  const dist = Units.formatDistance(rulerTotal * 1000);
  document.getElementById('ruler-dist').textContent = dist;
}

function addRulerPoint(lngLat) {
  const map = window._map;
  const pt = [lngLat.lng, lngLat.lat];
  const idx = rulerPoints.length;
  rulerPoints.push(pt);

  let segDist = 0;
  if (idx > 0) {
    segDist = haversineKm(rulerPoints[idx - 1], pt);
    rulerTotal += segDist;
  }

  // Bug 3: hide toast on ANY tap, not just the first
  const toast = document.getElementById('ruler-toast');
  if (toast) toast.classList.remove('visible');

  // Bug 4: show ruler-info panel after first point is added
  if (idx === 0) {
    document.getElementById('ruler-info').classList.add('visible');
  }

  // Wrapper element for dot + label row
  // Bug 6: wrapper is flex-column; label is absolute below dot so anchor:'center' hits the dot
  const wrapper = document.createElement('div');
  wrapper.style.cssText = 'position:relative;display:flex;flex-direction:column;align-items:center;';

  // Dot - first point green
  const dot = document.createElement('div');
  dot.className = 'ruler-dot';
  const dotColor = idx === 0 ? '#2EA043' : '#0088ff';
  dot.style.cssText = `width:10px;height:10px;background:${dotColor};border:2px solid #fff;border-radius:50%;box-shadow:0 0 4px rgba(0,0,0,0.3);display:block;flex-shrink:0;`;

  // Label row: [distance text][× button] — absolutely positioned below dot
  const label = document.createElement('div');
  label.className = 'ruler-label';
  label.style.cssText = 'position:absolute;top:100%;margin-top:3px;display:flex;align-items:center;gap:4px;background:rgba(20,20,20,0.75);color:#fff;font-size:10px;padding:2px 6px;border-radius:3px;white-space:nowrap;';

  const labelText = document.createElement('span');
  if (idx === 0) {
    labelText.textContent = 'Старт';
  } else {
    // ET-005: segDist в км — переводим в метры для units.js.
    labelText.textContent = Units.formatDistance(segDist * 1000);
  }

  // Bug 5: use button element for better tap target and semantics
  const btn = document.createElement('button');
  btn.type = 'button';
  btn.className = 'ruler-remove-btn';
  btn.textContent = '×';
  btn.style.cssText = 'background:none;border:none;cursor:pointer;font-size:16px;line-height:1;opacity:0.85;padding:4px 8px;min-width:32px;min-height:32px;display:flex;align-items:center;justify-content:center;margin:-2px -6px -2px 0;color:#fff;';
  btn.onclick = (e) => { e.stopPropagation(); removeRulerPoint(idx); };

  label.appendChild(labelText);
  label.appendChild(btn);

  wrapper.appendChild(dot);
  wrapper.appendChild(label);

  // Bug 2: tap on marker wrapper resumes ruler mode
  wrapper.addEventListener('click', (e) => {
    e.stopPropagation();
    if (!rulerMode && rulerPoints.length > 0) {
      rulerMode = true;
      document.getElementById('tb-ruler').classList.add('active');
      window._map.getCanvas().style.cursor = 'crosshair';
      updateMapModeClass();
    }
    document.getElementById('ruler-info').classList.add('visible');
  });

  const dotMarker = new maplibregl.Marker({ element: wrapper, anchor: 'center' })
    .setLngLat([lngLat.lng, lngLat.lat])
    .addTo(map);
  rulerMarkers.push(dotMarker);
  updateRulerLine();
}

function initRulerClicks(map) {
  map.on('click', (e) => {
    if (!rulerMode) return;
    addRulerPoint(e.lngLat);
  });
  map.on('dblclick', (e) => {
    if (!rulerMode) return;
    e.preventDefault();
    exitRulerMode();
  });
  // Fix 2 & 6: tap on ruler line shows panel AND resumes ruler mode
  map.on('click', 'ruler-line', (e) => {
    e.originalEvent.stopPropagation();
    if (rulerPoints.length > 0) {
      // Fix 6: Resume ruler mode
      if (!rulerMode) {
        rulerMode = true;
        const btn = document.getElementById('tb-ruler');
        btn.classList.add('active');
        window._map.getCanvas().style.cursor = 'crosshair';
        updateMapModeClass();
      }
      document.getElementById('ruler-info').classList.add('visible');
    }
  });
}

// ─── Фаза 4: Разведка ─────────────────────────────────────────────
let reconMode = false;
let reconCenter = null;
let reconRadius = 20;

function toggleReconMode() {
  const btn = document.getElementById('tb-recon');
  if (reconMode) {
    // Exit recon mode
    reconMode = false;
    btn.classList.remove('active');
    closeSheet('sheet-recon');
    window._map.getCanvas().style.cursor = '';
    clearRecon(); // recon data is transient - safe to clear
  } else {
    deactivateAllModes();
    reconMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
    openSheet('sheet-recon');
  }
  updateMapModeClass();
}

function makeCircleGeoJSON(lon, lat, radiusKm) {
  const coords = [];
  for (let i = 0; i <= 64; i++) {
    const a = (2 * Math.PI * i) / 64;
    const dlat = (radiusKm / 111) * Math.cos(a);
    const dlon = (radiusKm / (111 * Math.cos(lat * Math.PI / 180))) * Math.sin(a);
    coords.push([lon + dlon, lat + dlat]);
  }
  return { type: 'Feature', geometry: { type: 'Polygon', coordinates: [coords] }, properties: {} };
}

async function doRecon(lon, lat) {
  reconCenter = [lon, lat];
  const map = window._map;

  const circle = makeCircleGeoJSON(lon, lat, reconRadius);
  if (map.getSource('recon-circle')) {
    map.getSource('recon-circle').setData(circle);
  } else {
    map.addSource('recon-circle', { type: 'geojson', data: circle });
    map.addLayer({
      id: 'recon-circle-fill', type: 'fill', source: 'recon-circle',
      paint: { 'fill-color': '#ff6600', 'fill-opacity': 0.08 }
    });
    map.addLayer({
      id: 'recon-circle-stroke', type: 'line', source: 'recon-circle',
      paint: { 'line-color': '#ff6600', 'line-width': 2, 'line-opacity': 0.5 }
    });
  }

  const basePath = getBasePath();
  const resultsDiv = document.getElementById('recon-results');
  resultsDiv.style.display = 'block';

  try {
    const resp = await fetch(`${basePath}/api/recon`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ lon, lat, radius_km: reconRadius })
    });
    const data = await resp.json();
    const t = data.trails || {};
    const p = data.poi || {};

    document.getElementById('r-total-km').textContent = t.total_km || 0;
    document.getElementById('r-lev12-km').textContent = t.lev12_km || 0;
    document.getElementById('r-lev345-km').textContent = t.lev345_km || 0;
    document.getElementById('r-path-km').textContent = t.path_km || 0;

    const poiList = document.getElementById('r-poi-list');
    const poiTypes = [
      { key: 'natural=water', icon: '💧', label: 'Озёра' },
      { key: 'tourism=viewpoint', icon: '👁', label: 'Смотровая' },
      { key: 'ford=yes', icon: '🌊', label: 'Броды' },
      { key: 'historic=ruins', icon: '🏚', label: 'Руины' },
    ];
    poiList.innerHTML = poiTypes.map(pt =>
      `<div class="poi-row">
        <span class="poi-row-label"><span class="poi-icon">${pt.icon}</span> ${pt.label}</span>
        <span class="poi-row-count">${p[pt.key] || 0}</span>
      </div>`
    ).join('');

  } catch(e) {
    document.getElementById('r-total-km').textContent = '-';
  }
}

function setReconRadius(km) {
  reconRadius = km;
  document.querySelectorAll('.seg-btn[data-km]').forEach(b => {
    b.classList.toggle('active', +b.dataset.km === km);
  });
  if (reconCenter) doRecon(reconCenter[0], reconCenter[1]);
}

function clearRecon() {
  const map = window._map;
  try { if (map.getLayer('recon-circle-fill')) map.removeLayer('recon-circle-fill'); } catch(e) {}
  try { if (map.getLayer('recon-circle-stroke')) map.removeLayer('recon-circle-stroke'); } catch(e) {}
  try { if (map.getSource('recon-circle')) map.removeSource('recon-circle'); } catch(e) {}
  closeSheet('sheet-recon');
  reconCenter = null;
}

// ─── Фаза 4: Связка ────────────────────────────────────────────────
let linkMode = false;
let linkPoints = [];
let linkMarkers = [];
// ET-005: последние построенные связки кэшируются, чтобы оркестратор
// onUnitChange() мог перерисовать карточки без повторного запроса к API.
let linkRoutes = [];

function toggleLinkMode() {
  const btn = document.getElementById('tb-link');
  if (linkMode) {
    // Exit link mode
    linkMode = false;
    btn.classList.remove('active');
    closeSheet('sheet-link');
    window._map.getCanvas().style.cursor = '';
    clearLink();
  } else {
    deactivateAllModes();
    linkMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
    openSheet('sheet-link');
    document.getElementById('link-status').textContent = '1️⃣ Тапни конец первого трека';
    document.getElementById('link-cards').innerHTML = '';
    linkPoints = [];
    linkMarkers.forEach(m => m.remove());
    linkMarkers = [];
  }
  updateMapModeClass();
}

function addLinkPoint(lng, lat) {
  const map = window._map;
  linkPoints.push({ lon: lng, lat: lat });
  const idx = linkPoints.length;
  const el = document.createElement('div');
  el.className = '';
  el.style.cssText = 'width:16px;height:16px;background:var(--accent);border:2px solid #fff;border-radius:50%;box-shadow:0 1px 4px rgba(0,0,0,0.3);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff;';
  el.textContent = idx;
  const marker = new maplibregl.Marker({ element: el, anchor: 'center', draggable: true }).setLngLat([lng, lat]).addTo(map);
  linkMarkers.push(marker);

  if (idx === 1) {
    document.getElementById('link-pt-1').classList.remove('empty');
    document.getElementById('link-pt-1').querySelector('.link-pt-label').textContent = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
    document.getElementById('link-status').textContent = '2️⃣ Тапни начало второго трека';
  } else if (idx >= 2) {
    document.getElementById('link-pt-2').classList.remove('empty');
    document.getElementById('link-pt-2').querySelector('.link-pt-label').textContent = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
    showSkeleton('link-cards', 2);
    buildLinkRoute();
  }
}

async function buildLinkRoute() {
  const map = window._map;
  document.getElementById('link-status').textContent = '⏳ Ищу связку...';
  const basePath = getBasePath();
  try {
    const resp = await fetch(`${basePath}/api/route`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ waypoints: linkPoints, alternatives: 3 })
    });
    if (!resp.ok) throw new Error('Не найдена');
    const data = await resp.json();
    if (data.routes && data.routes.length > 0) {
      renderLinkCards(data.routes);
      document.getElementById('link-status').textContent = '✅ Связка найдена';
    } else {
      document.getElementById('link-status').textContent = '❌ Грунтовая связка не найдена';
    }
  } catch(e) {
    document.getElementById('link-status').textContent = '❌ ' + e.message;
  }
}

function renderLinkCards(routes) {
  const map = window._map;
  linkRoutes = routes; // ET-005: кэш для перерисовки при смене единицы
  const colors = ['#0066ff', '#00aa44', '#9933cc'];
  const cardsEl = document.getElementById('link-cards');
  cardsEl.innerHTML = '';

  routes.forEach((r, i) => {
    const geojson = { type: 'Feature', geometry: r.geometry, properties: {} };
    const sid = `link-src-${i}`;
    const lid = `link-line-${i}`;
    try { if (map.getSource(sid)) map.removeSource(sid); } catch(e) {}
    try { if (map.getLayer(lid)) map.removeLayer(lid); } catch(e) {}
    map.addSource(sid, { type: 'geojson', data: geojson });
    map.addLayer({
      id: lid, type: 'line', source: sid,
      paint: {
        'line-color': colors[i % colors.length],
        'line-width': i === 0 ? 5 : 3,
        'line-opacity': i === 0 ? 0.9 : 0.5,
      },
      layout: { 'line-cap': 'round', 'line-join': 'round' }
    });

    const time = formatDuration(r.duration_s);
    const dirt = r.stats?.dirt_total_pct || '?';
    const col = colors[i % colors.length];
    const card = document.createElement('div');
    card.className = 'route-card' + (i === 0 ? ' active' : '');
    card.innerHTML = `
      <div class="rc-header">
        <span class="rc-dot" style="background:${col}"></span>
        <span class="rc-title">Вариант ${i+1}</span>
        <span class="rc-km">${Units.formatDistance(r.distance_m, { precision: 0 })}</span>
        <span class="rc-time">${time}</span>
      </div>
      <div style="font-size:11px;color:var(--text2)">${dirt}% грунт</div>
    `;
    card.onclick = () => selectLinkRoute(i);
    cardsEl.appendChild(card);
  });
}

function selectLinkRoute(idx) {
  const map = window._map;
  document.querySelectorAll('#link-cards .route-card').forEach((c, i) => c.classList.toggle('active', i === idx));
  for (let i = 0; i < 3; i++) {
    const lid = `link-line-${i}`;
    try {
      if (map.getLayer(lid)) {
        map.setPaintProperty(lid, 'line-width', i === idx ? 5 : 3);
        map.setPaintProperty(lid, 'line-opacity', i === idx ? 0.9 : 0.5);
      }
    } catch(e) {}
  }
}

function clearLink() {
  linkPoints = [];
  linkMarkers.forEach(m => m.remove());
  linkMarkers = [];
  const map = window._map;
  for (let i = 0; i < 5; i++) {
    try { if (map.getLayer(`link-line-${i}`)) map.removeLayer(`link-line-${i}`); } catch(e) {}
    try { if (map.getSource(`link-src-${i}`)) map.removeSource(`link-src-${i}`); } catch(e) {}
  }
  closeSheet('sheet-link');
  document.getElementById('link-cards').innerHTML = '';
  // Reset link point UI
  const pt1 = document.getElementById('link-pt-1');
  const pt2 = document.getElementById('link-pt-2');
  if (pt1) { pt1.classList.add('empty'); pt1.querySelector('.link-pt-label').textContent = 'Конец первого трека'; }
  if (pt2) { pt2.classList.add('empty'); pt2.querySelector('.link-pt-label').textContent = 'Начало второго трека'; }
}

// ─── Фаза 4: Красивый маршрут ──────────────────────────────────────
let scenicMode = false;
let scenicStart = null;
let scenicStartMarker = null;
let scenicTargetKm = 100;
let scenicRoutes = [];
let activeScenicIdx = 0;

function toggleScenicMode() {
  const btn = document.getElementById('tb-scenic');
  if (scenicMode) {
    // Exit scenic mode
    scenicMode = false;
    btn.classList.remove('active');
    closeSheet('sheet-scenic');
    window._map.getCanvas().style.cursor = '';
    clearScenic();
  } else {
    deactivateAllModes();
    scenicMode = true;
    btn.classList.add('active');
    window._map.getCanvas().style.cursor = 'crosshair';
    openSheet('sheet-scenic');
    document.getElementById('scenic-status').textContent = 'Тапни точку старта на карте';
    document.getElementById('btn-build-scenic').style.display = 'none';
  }
  updateMapModeClass();
}

function setScenicKm(km) {
  scenicTargetKm = km;
  document.querySelectorAll('#sheet-scenic .seg-btn[data-km]').forEach(b => {
    b.classList.toggle('active', +b.dataset.km === km);
  });
  const inp = document.getElementById('scenic-custom-km');
  if (inp) inp.value = km;
}

async function buildScenicRoute() {
  if (!scenicStart) return;
  const map = window._map;
  document.getElementById('scenic-status').textContent = '⏳ Строю красивый маршрут...';
  showSkeleton('scenic-cards', 2);
  const btn = document.getElementById('btn-build-scenic');
  btn.disabled = true;

  const basePath = getBasePath();
  try {
    const resp = await fetch(`${basePath}/api/scenic`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ lon: scenicStart.lon, lat: scenicStart.lat, target_km: scenicTargetKm })
    });
    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}));
      throw new Error(err.detail || 'Ошибка');
    }
    const data = await resp.json();
    scenicRoutes = data.routes || [];
    if (scenicRoutes.length === 0) throw new Error('Маршрут не найден');

    drawScenicRoutes(scenicRoutes, 0);

    document.getElementById('scenic-status').textContent = `✅ ${scenicRoutes.length} маршрут(ов)`;
  } catch(e) {
    document.getElementById('scenic-status').textContent = '❌ ' + e.message;
    document.getElementById('scenic-cards').innerHTML = '';
  }
  btn.disabled = false;
}

function drawScenicRoutes(routes, activeIdx) {
  const map = window._map;
  scenicRoutes = routes;
  activeScenicIdx = activeIdx;

  // Clear old
  for (let i = 0; i < 10; i++) {
    try { if (map.getLayer(`scenic-line-${i}`)) map.removeLayer(`scenic-line-${i}`); } catch(e) {}
    try { if (map.getSource(`scenic-src-${i}`)) map.removeSource(`scenic-src-${i}`); } catch(e) {}
  }

  const colors = ['#0066ff', '#00aa44', '#9933cc'];
  routes.forEach((r, i) => {
    const geojson = { type: 'Feature', geometry: r.geometry, properties: {} };
    const sid = `scenic-src-${i}`;
    const lid = `scenic-line-${i}`;
    map.addSource(sid, { type: 'geojson', data: geojson });
    map.addLayer({
      id: lid, type: 'line', source: sid,
      paint: {
        'line-color': colors[i % colors.length],
        'line-width': i === activeIdx ? 5 : 3,
        'line-opacity': i === activeIdx ? 0.9 : 0.5,
      },
      layout: { 'line-cap': 'round', 'line-join': 'round' }
    });
  });

  const cardsEl = document.getElementById('scenic-cards');
  if (cardsEl) {
    cardsEl.innerHTML = routes.map((r, i) => {
      const col = colors[i % colors.length];
      const time = formatDuration(r.duration_s);
      const dirt = r.stats?.dirt_total_pct || '?';
      const pois = (r.scenic_pois || []).map(p => {
        const SCENIC_LABELS = {'natural=water':'💧 Озёро','tourism=viewpoint':'👁 Смотровая','historic=ruins':'🏚 Руины','natural=peak':'🔺 Вершина','natural=cave_entrance':'🕳 Пещера','ford=yes':'🌊 Брод'};
        const label = SCENIC_LABELS[p.type] || '📍 ' + p.type;
        const name = p.name ? ` - ${p.name}` : '';
        return `<div class="scenic-poi-item">${label}${name}</div>`;
      }).join('');
      return `<div class="route-card ${i===activeIdx?'active':''}" onclick="selectScenicRoute(${i})">
        <div class="rc-header">
          <span class="rc-dot" style="background:${col}"></span>
          <span class="rc-title">${r.name || 'Вариант '+(i+1)}</span>
          <span class="rc-km">${Units.formatDistance(r.distance_m, { precision: 0 })}</span>
          <span class="rc-time">${time}</span>
        </div>
        <div style="font-size:11px;color:var(--text2)">${dirt}% грунт · score=${r.scenic_score||0}</div>
        ${pois ? '<div style="margin-top:4px">'+pois+'</div>' : ''}
      </div>`;
    }).join('');
  }
}

function selectScenicRoute(idx) {
  activeScenicIdx = idx;
  const map = window._map;
  scenicRoutes.forEach((_, i) => {
    const lid = `scenic-line-${i}`;
    try {
      if (map.getLayer(lid)) {
        map.setPaintProperty(lid, 'line-width', i === idx ? 5 : 3);
        map.setPaintProperty(lid, 'line-opacity', i === idx ? 0.9 : 0.5);
      }
    } catch(e) {}
  });
  document.querySelectorAll('#scenic-cards .route-card').forEach((c, i) => {
    c.classList.toggle('active', i === idx);
  });
}

function clearScenic() {
  const map = window._map;
  for (let i = 0; i < 10; i++) {
    try { if (map.getLayer(`scenic-line-${i}`)) map.removeLayer(`scenic-line-${i}`); } catch(e) {}
    try { if (map.getSource(`scenic-src-${i}`)) map.removeSource(`scenic-src-${i}`); } catch(e) {}
  }
  if (scenicStartMarker) { scenicStartMarker.remove(); scenicStartMarker = null; }
  scenicStart = null;
  scenicRoutes = [];
  closeSheet('sheet-scenic');
  const cardsEl = document.getElementById('scenic-cards');
  if (cardsEl) cardsEl.innerHTML = '';
}

// ─── Init on page load ─────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
  initMap();
  initSheetSwipe();
  // Apply saved theme immediately (before map loads)
  applyTheme();
  // ET-005: восстановить выбор единиц измерения (AC-3) и подключить
  // единый оркестратор пересчёта расстояний (ADR-0001 п.6).
  syncUnitToggleUI();
  document.addEventListener('unitchange', onUnitChange);
});

// ─── Mini Route Bar ──────────────────────────────────────────────────

function miniAddWaypoint() {
  // Enter waypoint-adding mode without opening full sheet
  if (!routeMode) {
    routeMode = true;
    document.getElementById('tb-route').classList.add('active');
    updateMapModeClass();
  }
  addWaypointMode();
  // Show hint on mini-bar
  const statsEl = document.getElementById('mini-stats');
  if (statsEl) statsEl.textContent = 'Тапни на карте для добавления точки';
}

window.cancelAddWaypoint = cancelAddWaypoint;
function cancelAddWaypoint() {
  addingWaypoint = false;
  window._map.getCanvas().style.cursor = routeMode ? 'crosshair' : '';
  hideMiniOnboard();
  // Просто показать мини-бар без открытия листа
  if (routeResults.length > 0) {
    // Показать мини-бар с результатом маршрута (без открытия листа)
    updateMiniRouteCard();
    document.getElementById('sheet-route-mini').classList.add('visible');
    const ctrl = document.getElementById('map-controls-r');
    if (ctrl) ctrl.style.bottom = '148px';
    initMiniRouteInteraction();
  } else if (routeWaypoints.length > 0) {
    showRouteOnboardingMini();
  } else {
    hideMiniRouteSheet();
  }
}

function showRouteOnboardingMini() {
  const cancelBtn = document.getElementById('mini-onboard-cancel-btn');
  if (cancelBtn) cancelBtn.style.display = 'none';

  if (routeWaypoints.length >= 2) {
    hideMiniOnboard();
    showMiniRouteSheet();
    return;
  }
  const isStart = routeWaypoints.length === 0;
  const label = isStart ? 'S' : 'F';
  const color = isStart ? '#2EA043' : '#FF3B1F';
  const hint = isStart ? 'Тапни на карте — старт' : 'Тапни на карте — финиш';

  // Show onboarding div, hide normal mini-bar content
  document.getElementById('mini-onboard').style.display = 'flex';
  document.getElementById('mini-dot').style.display = 'none';
  document.getElementById('mini-label').style.display = 'none';
  document.getElementById('mini-stats').style.display = 'none';
  document.getElementById('mini-wheel').style.display = 'none';
  const arrows = document.querySelector('.mini-route-arrows');
  if (arrows) arrows.style.display = 'none';
  const addBtn = document.getElementById('mini-add-btn');
  if (addBtn) addBtn.style.display = 'none';

  // Set pin and hint
  document.getElementById('mini-onboard-pin').innerHTML = waypointPinSvg(label, color);
  document.getElementById('mini-onboard-hint').textContent = hint;

  // Show mini-bar and raise map controls
  document.getElementById('sheet-route-mini').classList.add('visible');
  const ctrl = document.getElementById('map-controls-r');
  if (ctrl) ctrl.style.bottom = '148px';

  // Search button handler
  const searchBtn = document.getElementById('mini-onboard-search-btn');
  searchBtn.onclick = () => toggleMiniOnboardSearch(isStart ? 'start' : 'finish');
}

function hideMiniOnboard() {
  document.getElementById('mini-onboard').style.display = 'none';
  document.getElementById('mini-onboard-search-panel').style.display = 'none';
  const cancelBtn = document.getElementById('mini-onboard-cancel-btn');
  if (cancelBtn) cancelBtn.style.display = 'none';
  // Restore normal mini-bar elements
  document.getElementById('mini-dot').style.display = '';
  document.getElementById('mini-label').style.display = '';
  document.getElementById('mini-stats').style.display = '';
  document.getElementById('mini-wheel').style.display = '';
  const arrows = document.querySelector('.mini-route-arrows');
  if (arrows) arrows.style.display = '';
  const addBtn = document.getElementById('mini-add-btn');
  if (addBtn) addBtn.style.display = '';
}

function toggleMiniOnboardSearch(type) {
  const panel = document.getElementById('mini-onboard-search-panel');
  const isVisible = panel.style.display !== 'none';
  if (isVisible) {
    panel.style.display = 'none';
    return;
  }
  panel.style.display = 'block';
  const input = document.getElementById('mini-onboard-search-input');
  input.value = '';
  document.getElementById('mini-onboard-search-results').innerHTML = '';
  setTimeout(() => input.focus(), 50);

  let timeout = null;
  input.oninput = () => {
    clearTimeout(timeout);
    const q = input.value.trim();
    const resultsEl = document.getElementById('mini-onboard-search-results');
    if (q.length < 2) { resultsEl.innerHTML = ''; return; }
    timeout = setTimeout(() => _doMiniOnboardSearch(type, q, resultsEl), 400);
  };
}

async function _doMiniOnboardSearch(type, query, resultsEl) {
  resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--text3)">Поиск...</span></div>';
  try {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5&accept-language=ru`;
    const resp = await fetch(url);
    const data = await resp.json();
    if (!data.length) {
      resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--text3)">Ничего не найдено</span></div>';
      return;
    }
    resultsEl.innerHTML = data.map(item => {
      const parts = (item.display_name || '').split(', ');
      const name = parts[0];
      const sub = parts.slice(1, 3).join(', ');
      return `<div class="wl-search-result-item" onclick="_selectMiniOnboardResult('${type}', ${item.lat}, ${item.lon}, '${name.replace(/'/g, "\\'")}')">
        <div class="wl-search-result-name">${name}</div>
        ${sub ? `<div class="wl-search-result-sub">${sub}</div>` : ''}
      </div>`;
    }).join('');
  } catch(e) {
    resultsEl.innerHTML = '<div class="wl-search-result-item"><span style="color:var(--red)">Ошибка</span></div>';
  }
}

function _selectMiniOnboardResult(type, lat, lon, name) {
  const wp = { lat: parseFloat(lat), lon: parseFloat(lon) };
  if (type === 'start') {
    routeWaypoints.unshift(wp);
  } else if (type === 'finish') {
    routeWaypoints.push(wp);
  } else if (type === 'waypoint') {
    // Insert before last waypoint (same as addingWaypoint tap logic)
    if (routeWaypoints.length >= 2) {
      routeWaypoints.splice(routeWaypoints.length - 1, 0, wp);
    } else {
      routeWaypoints.push(wp);
    }
    addingWaypoint = false;
  }
  document.getElementById('mini-onboard-search-panel').style.display = 'none';
  rebuildWaypointMarkers();
  window._map.flyTo({ center: [parseFloat(lon), parseFloat(lat)], zoom: 12, duration: 600 });

  if (routeWaypoints.length >= 2) {
    hideMiniOnboard();
    debounceBuildRoute();
  } else {
    showRouteOnboardingMini(); // switch to finish
  }
  updateMiniRouteCard();
}

function showMiniRouteSheet() {
  if (!routeResults || routeResults.length === 0) return;
  updateMiniRouteCard();
  document.getElementById('sheet-route-mini').classList.add('visible');
  // Поднять кнопки карты над мини-баром (64px высота + 72px bottom + 8px отступ)
  const ctrl = document.getElementById('map-controls-r');
  if (ctrl) ctrl.style.bottom = '148px';
  initMiniRouteInteraction();
}

function hideMiniRouteSheet() {
  const el = document.getElementById('sheet-route-mini');
  if (el) el.classList.remove('visible');
  // Вернуть кнопки на место
  const ctrl = document.getElementById('map-controls-r');
  if (ctrl) ctrl.style.bottom = '';
}

function updateMiniRouteCard() {
  const r = routeResults[activeRouteIdx];
  if (!r) return;
  const dirt = r.stats?.dirt_total_pct ?? '-';
  document.getElementById('mini-dot').style.background = ROUTE_COLORS[activeRouteIdx % ROUTE_COLORS.length];
  document.getElementById('mini-label').textContent = `Вариант ${activeRouteIdx + 1} из ${routeResults.length}`;
  document.getElementById('mini-stats').textContent = `${Units.formatDistance(r.distance_m)} · ${dirt}% грунт`;
  document.getElementById('mini-prev').style.opacity = activeRouteIdx > 0 ? '1' : '0.3';
  document.getElementById('mini-next').style.opacity = activeRouteIdx < routeResults.length - 1 ? '1' : '0.3';
}

function selectMiniRoute(idx) {
  if (idx < 0 || idx >= routeResults.length) return;
  activeRouteIdx = idx;
  const map = window._map;
  for (let i = 0; i < routeResults.length; i++) {
    const op = i === idx ? 1 : 0.35;
    try {
      if (map.getLayer('route-line-' + i)) map.setPaintProperty('route-line-' + i, 'line-opacity', op);
      if (map.getLayer('route-line-' + i + '-outline')) map.setPaintProperty('route-line-' + i + '-outline', 'line-opacity', i === idx ? 0.6 : 0);
    } catch(e) {}
  }
  updateMiniRouteCard();
  renderWaypointsList();
}

// ─── Route Loading Indicators ────────────────────────────────────
function showRouteLoading() {
  const el = document.getElementById('route-cards');
  if (el) el.innerHTML = `<div class="route-loading">
    <div class="route-spinner"></div>
    <span style="color:var(--text3);font-size:13px">Строю маршрут...</span>
  </div>`;
}

function showMiniRouteLoading() {
  const wheel = document.getElementById('mini-wheel');
  const statsEl = document.getElementById('mini-stats');
  if (wheel) wheel.classList.add('spinning');
  if (statsEl) statsEl.textContent = 'Строю маршрут...';
  document.getElementById('sheet-route-mini').classList.add('visible');
}

function hideMiniRouteLoading() {
  const wheel = document.getElementById('mini-wheel');
  if (wheel) wheel.classList.remove('spinning');
}

function initMiniRouteInteraction() {
  const mini = document.getElementById('sheet-route-mini');
  if (!mini) return;

  // Replace element to drop all old listeners
  const newMini = mini.cloneNode(true);
  mini.parentNode.replaceChild(newMini, mini);

  // Re-bind arrow buttons
  document.getElementById('mini-prev').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx - 1); };
  document.getElementById('mini-next').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx + 1); };
  const addBtn = document.getElementById('mini-add-btn');
  if (addBtn) addBtn.onclick = (e) => { e.stopPropagation(); miniAddWaypoint(); };

  let startX = 0, startY = 0;
  newMini.addEventListener('touchstart', e => {
    startX = e.touches[0].clientX;
    startY = e.touches[0].clientY;
  }, { passive: true });

  newMini.addEventListener('touchend', e => {
    const dx = e.changedTouches[0].clientX - startX;
    const dy = e.changedTouches[0].clientY - startY;
    if (Math.abs(dy) > Math.abs(dx)) {
      if (dy < -40) { hideMiniRouteSheet(); openSheet('sheet-route'); selectRoute(activeRouteIdx); }
    } else {
      if (dx < -40) selectMiniRoute(activeRouteIdx + 1);
      if (dx > 40)  selectMiniRoute(activeRouteIdx - 1);
    }
  });

  newMini.addEventListener('click', e => {
    if (e.target.classList.contains('mini-arrow')) return;
    hideMiniRouteSheet();
    openSheet('sheet-route');
    selectRoute(activeRouteIdx);
  });
}

// ═══════════════════════════════════════════
// TERRAIN LAYERS (Phase 5.4)
// ═══════════════════════════════════════════

const TERRAIN_BASE_URL = window.location.pathname.replace(/\/[^/]*$/, '') + '/terrain';

// ET-013: zoom-aware paint для слоёв рельефа (ADR-017).
// Цель — компенсировать «потерю выразительности» перепадов на z9-z11.
// Pre-z9 — hillshade не показывается (UI-минзум). На z9-z11 — максимальный
// контраст и opacity, чтобы тени читались как на z8. К z12-z14 — возврат
// к исходным значениям (тогда у пользователя есть другие способы
// читать рельеф: подложка, грунтовки, POI).
const HILLSHADE_PAINT = {
  'raster-opacity': [
    'interpolate', ['linear'], ['zoom'],
    9,  0.65,
    10, 0.60,
    11, 0.55,
    12, 0.50,
    14, 0.40
  ],
  'raster-contrast': [
    'interpolate', ['linear'], ['zoom'],
    9,  0.40,
    10, 0.35,
    11, 0.30,
    12, 0.15,
    14, 0.00
  ],
  'raster-resampling': 'nearest'
};

// ET-013: TRI остаётся 0.70 на z8 (регрессия), пик 0.80-0.85 на z9-z11.
const TRI_PAINT = {
  'raster-opacity': [
    'interpolate', ['linear'], ['zoom'],
    5,  0.55,
    7,  0.65,
    8,  0.70,
    9,  0.80,
    10, 0.85,
    11, 0.85,
    12, 0.75,
    15, 0.70
  ],
  'raster-resampling': 'nearest'
};

function toggleTerrainPopup() {
  const popup = document.getElementById('terrain-popup');
  const btn = document.getElementById('terrain-toggle');
  if (!popup || !btn) return;
  
  const isVisible = popup.style.display !== 'none';
  popup.style.display = isVisible ? 'none' : 'block';
  
  // Position popup to the left of the button
  if (!isVisible) {
    const rect = btn.getBoundingClientRect();
    popup.style.right = (window.innerWidth - rect.left + 8) + 'px';
    // Position: align bottom of popup with bottom of button, ensure fits in viewport
    const popupHeight = popup.offsetHeight;
    const desiredTop = rect.bottom - popupHeight;
    const minTop = 8;
    popup.style.top = Math.max(minTop, desiredTop) + 'px';
    updateHillshadeAvailability();
    setTimeout(() => {
      document.addEventListener('click', closeTerrainOnOutside);
    }, 10);
  } else {
    document.removeEventListener('click', closeTerrainOnOutside);
  }
  
  btn.classList.toggle('active', !isVisible);
}

function closeTerrainOnOutside(e) {
  const popup = document.getElementById('terrain-popup');
  const btn = document.getElementById('terrain-toggle');
  if (!popup.contains(e.target) && e.target !== btn && !btn.contains(e.target)) {
    popup.style.display = 'none';
    btn.classList.remove('active');
    document.removeEventListener('click', closeTerrainOnOutside);
  }
}

function onTerrainCheckbox() {
  const map = window._map;
  if (!map) return;
  
  const hillshadeChecked = document.getElementById('terrain-hillshade-cb').checked;
  const triChecked = document.getElementById('terrain-tri-cb').checked;
  
  // Save state
  localStorage.setItem('terrain-hillshade', hillshadeChecked ? '1' : '0');
  localStorage.setItem('terrain-tri', triChecked ? '1' : '0');
  
  // Update button active state
  const btn = document.getElementById('terrain-toggle');
  btn.classList.toggle('active', hillshadeChecked || triChecked);
  
  // Apply layers
  // ET-013: hillshade теперь доступен с z9; paint zoom-aware (см. HILLSHADE_PAINT / TRI_PAINT).
  applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, HILLSHADE_PAINT, 9, 15);
  applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png', triChecked, TRI_PAINT, 5, 15);
}


function onTrailsCheckbox() {
  const map = window._map;
  if (!map) return;

  const trackChecked = document.getElementById('trails-track-cb').checked;
  const pathChecked = document.getElementById('trails-path-cb').checked;

  // Save state
  localStorage.setItem('trails-track', trackChecked ? '1' : '0');
  localStorage.setItem('trails-path', pathChecked ? '1' : '0');

  // Toggle layer visibility
  if (map.getLayer('trails-track')) {
    map.setLayoutProperty('trails-track', 'visibility', trackChecked ? 'visible' : 'none');
  }
  if (map.getLayer('trails-path-bridleway')) {
    map.setLayoutProperty('trails-path-bridleway', 'visibility', pathChecked ? 'visible' : 'none');
  }
  // ET-007 P1-6: синхронизируем halo-underlay-слои с состоянием
  // чекбоксов, чтобы на спутнике не оставалось «фантома» halo при
  // выключенной грунтовке/тропе. Безопасно к ранней инициализации:
  // _applyTrailHaloVisibility определена ниже в том же файле (ET-007
  // base layer block). См. ADR-004 §9, TRZ §5.7.
  if (typeof _applyTrailHaloVisibility === 'function' &&
      typeof getStoredBaseLayer === 'function') {
    _applyTrailHaloVisibility(map, getStoredBaseLayer());
  }
}

function restoreTrailsState() {
  const trackState = localStorage.getItem('trails-track');
  const pathState = localStorage.getItem('trails-path');

  // Default: both checked (visible)
  const trackOn = trackState === null || trackState === '1';
  const pathOn = pathState === null || pathState === '1';

  const trackCb = document.getElementById('trails-track-cb');
  const pathCb = document.getElementById('trails-path-cb');

  if (trackCb) trackCb.checked = trackOn;
  if (pathCb) pathCb.checked = pathOn;

  const map = window._map;
  if (map) {
    if (map.getLayer('trails-track')) {
      map.setLayoutProperty('trails-track', 'visibility', trackOn ? 'visible' : 'none');
    }
    if (map.getLayer('trails-path-bridleway')) {
      map.setLayoutProperty('trails-path-bridleway', 'visibility', pathOn ? 'visible' : 'none');
    }
    // ET-007 P1-6: тот же контракт, что в onTrailsCheckbox (см. выше).
    if (typeof _applyTrailHaloVisibility === 'function' &&
        typeof getStoredBaseLayer === 'function') {
      _applyTrailHaloVisibility(map, getStoredBaseLayer());
    }
  }
}

// >>> ET-002 POI visibility block (do not remove markers — used by unit tests) >>>
// Видимость POI (слои poi-circles, poi-labels) управляется чекбоксом
// «POI» в попапе рельефа. Состояние хранится в localStorage под ключом
// 'poi-visible' ('1'/'0'). Источник истины в рантайме — layerState.poi.
// См. docs/work-items/ET-002/06-adr/adr-0001-poi-visibility-client-side.md
/**
 * Применяет видимость группы слоёв POI и синхронизирует layerState.poi.
 *
 * Единый приватный хелпер: переиспользуется чекбоксом попапа
 * (onPoiCheckbox) и восстановлением состояния при загрузке/смене стиля
 * (restorePoiState). Не пишет в localStorage — персистентность остаётся
 * ответственностью обработчика чекбокса.
 *
 * @param {boolean} visible - true — показать POI, false — скрыть.
 */
function applyPoiVisibility(visible) {
  layerState.poi = visible;
  const map = window._map;
  if (!map) return;
  const visibility = visible ? 'visible' : 'none';
  layerGroups.poi.forEach(id => {
    if (map.getLayer(id)) {
      map.setLayoutProperty(id, 'visibility', visibility);
    }
  });
}

/**
 * Обработчик чекбокса «POI» в попапе рельефа (атрибут onchange).
 *
 * Сохраняет выбор в localStorage ('poi-visible': '1' видимы | '0' скрыты)
 * и применяет видимость слоёв POI через applyPoiVisibility().
 */
function onPoiCheckbox() {
  const checked = document.getElementById('poi-visible-cb').checked;
  localStorage.setItem('poi-visible', checked ? '1' : '0');
  applyPoiVisibility(checked);
}

/**
 * Восстанавливает видимость POI при загрузке страницы и после смены
 * стиля карты (переключение темы).
 *
 * По умолчанию (ключ 'poi-visible' отсутствует или равен '1') POI
 * видимы; '0' — скрыты. Синхронизирует чекбокс, layerState.poi и
 * фактическую видимость слоёв.
 */
function restorePoiState() {
  const stored = localStorage.getItem('poi-visible');
  const poiOn = stored === null || stored === '1';
  const cb = document.getElementById('poi-visible-cb');
  if (cb) cb.checked = poiOn;
  applyPoiVisibility(poiOn);
}
// <<< ET-002 POI visibility block <<<

// >>> ET-007 base layer toggle block (do not remove markers — used by unit tests) >>>
// Переключатель базовой подложки карты «Схема» / «Спутник» в попапе слоёв.
// Реализация: ленивое создание спутникового raster-source/layer при первом
// включении «Спутника»; восстановление выбора из localStorage и
// rebuildMapOverlays() после смены темы. POI / trails halo переключаются
// через visibility у декларативных underlay-слоёв (`*-halo-satellite`) и
// setPaintProperty у POI labels/circles. См.
// docs/work-items/ET-007/06-adr/ADR-004-satellite-base-layer.md.

/**
 * Параметры спутникового источника и слоя (ADR-004 §4.1, TRZ §4.1).
 * URL без API-ключа, HTTPS обязателен, атрибуция Esri.
 */
const SATELLITE_SOURCE_ID = 'satellite-raster';
const SATELLITE_LAYER_ID  = 'satellite-base';
const SATELLITE_TILE_URL  =
  'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
const SATELLITE_ATTRIBUTION =
  'Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community';

/**
 * Halo-underlay-слои, видимые только в режиме «Спутник» (ADR-004 §5,
 * вариант H-B). Объявлены в style.json / style-dark.json с
 * visibility: none; здесь только переключаем видимость.
 */
const SATELLITE_HALO_LAYER_IDS = [
  'trails-track-halo-satellite',
  'trails-path-bridleway-halo-satellite',
];

/**
 * Пары (base-layer, halo-underlay) для синхронизации halo с
 * пользовательскими чекбоксами «Грунтовки» / «Тропы»
 * (ADR-004 §9, TRZ §5.7). Источник истины: halo видим ⇔
 * (текущая база === 'satellite') AND (соответствующий чекбокс ON).
 */
const TRAIL_HALO_PAIRS = [
  { base: 'trails-track',           halo: 'trails-track-halo-satellite' },
  { base: 'trails-path-bridleway',  halo: 'trails-path-bridleway-halo-satellite' },
];

/**
 * Сохранённое значение `layerState.basemap` на время активного
 * режима «Спутник» (ADR-004 §8, TRZ §5.6). `null` означает «сейчас
 * на схеме, восстанавливать нечего». При входе в «Спутник» сохраняем
 * сюда `layerState.basemap`, при выходе — восстанавливаем и
 * обнуляем. Это сохраняет выбор пользователя по «Базовая карта»
 * через ход «Схема → Спутник → Схема» без рассинхрона с
 * `layerState.basemap`.
 */
let _savedBasemapState = null;

/**
 * Возвращает выбранную пользователем подложку из localStorage.
 *
 * Любое значение, кроме известных (`'schematic'` / `'satellite'`),
 * трактуется как дефолт `'schematic'` (TRZ §4.3, U-04). Безопасно к
 * приватному режиму браузера: при ошибке доступа к localStorage
 * возвращает дефолт.
 * @returns {('schematic'|'satellite')}
 */
function getStoredBaseLayer() {
  try {
    const v = window.localStorage.getItem('map-base-layer');
    return v === 'satellite' ? 'satellite' : 'schematic';
  } catch (_) {
    return 'schematic';
  }
}

/**
 * Обработчик сегментированного переключателя «Подложка» (атрибут
 * onclick кнопок «Схема» / «Спутник»).
 *
 * Идемпотентен: повторный вызов с уже активным значением — no-op
 * (U-05): не пишет в localStorage и не трогает стиль карты.
 * @param {('schematic'|'satellite')} base - выбранная подложка.
 */
function onBaseLayerToggle(base) {
  if (base !== 'schematic' && base !== 'satellite') return;
  const current = getStoredBaseLayer();
  if (current === base) return;
  try {
    window.localStorage.setItem('map-base-layer', base);
  } catch (_) { /* private mode — фича остаётся per-session */ }
  applyBaseLayer(base);
  syncBaseLayerUI(base);
}

/**
 * Применяет выбранную подложку к карте (TRZ §5.2, ADR-004 §3, §5).
 *
 * Для `'satellite'`: лениво создаёт source/layer (если их ещё нет),
 * вставляет слой ниже первого terrain/trails/POI-слоя, скрывает
 * `osm-base`, включает halo-underlay-слои у trails, выставляет
 * тёмный halo у POI и тёмный background, чтобы белый фон не
 * «бликовал» под медленно подгружающимися плитками.
 *
 * Для `'schematic'`: возвращает все динамически изменённые свойства
 * к значениям, объявленным в текущем `style.json` / `style-dark.json`.
 * @param {('schematic'|'satellite')} base
 */
function applyBaseLayer(base) {
  const map = window._map;
  if (!map) return;
  if (base === 'satellite') {
    if (!map.getSource(SATELLITE_SOURCE_ID)) {
      map.addSource(SATELLITE_SOURCE_ID, {
        type: 'raster',
        tiles: [SATELLITE_TILE_URL],
        tileSize: 256,
        minzoom: 0,
        maxzoom: 19,
        attribution: SATELLITE_ATTRIBUTION,
      });
    }
    if (!map.getLayer(SATELLITE_LAYER_ID)) {
      const before = _firstOverlayLayerId(map);
      map.addLayer({
        id: SATELLITE_LAYER_ID,
        type: 'raster',
        source: SATELLITE_SOURCE_ID,
        paint: { 'raster-opacity': 1.0, 'raster-resampling': 'linear' },
        layout: { visibility: 'none' },
      }, before);
    }
    if (map.getLayer(SATELLITE_LAYER_ID)) {
      map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', 'visible');
    }
    // ET-007 P1-5 / ADR-004 §8: запоминаем layerState.basemap и
    // принудительно скрываем osm-base. layerState.basemap не меняем —
    // это пользовательский выбор «Базовая карта», его восстановим при
    // возврате на «Схему».
    if (_savedBasemapState === null && typeof layerState !== 'undefined') {
      _savedBasemapState = layerState.basemap;
    }
    if (map.getLayer('osm-base')) {
      map.setLayoutProperty('osm-base', 'visibility', 'none');
    }
    // CSS-hook: скрыть кнопку #btn-basemap пока активен спутник
    // (гибридный режим out of scope — BRD §3). Defensive: mock-DOM в
    // unit-тестах может не иметь classList.add/remove.
    _setBodyClass('satellite-active', true);
    // ET-007 P1-6: halo синхронизирован с состоянием чекбоксов
    // «Грунтовки» / «Тропы», а не безусловно включён.
    _applyTrailHaloVisibility(map, 'satellite');
    // ET-008: halo публичных треков на спутнике
    if (typeof applyGpsHaloVisibility === 'function') {
      applyGpsHaloVisibility(map);
    }
    _applyPoiSatellitePaint(map, true);
    _applyBackgroundForSatellite(map, true);
  } else {
    if (map.getLayer(SATELLITE_LAYER_ID)) {
      map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', 'none');
    }
    // ET-007 P1-5: восстановить выбор пользователя по «Базовой карте»
    // (если он раньше выключал osm-base — оставить выключенным).
    if (map.getLayer('osm-base')) {
      const wantOsm = _savedBasemapState !== false; // default visible
      map.setLayoutProperty('osm-base', 'visibility', wantOsm ? 'visible' : 'none');
    }
    _savedBasemapState = null;
    _setBodyClass('satellite-active', false);
    // На «Схеме» halo всегда скрыт независимо от чекбоксов.
    _applyTrailHaloVisibility(map, 'schematic');
    // ET-008: halo публичных треков выключить
    if (typeof applyGpsHaloVisibility === 'function') {
      applyGpsHaloVisibility(map);
    }
    _applyPoiSatellitePaint(map, false);
    _applyBackgroundForSatellite(map, false);
  }
}

/**
 * Восстанавливает выбор подложки из localStorage и применяет его к
 * карте (TRZ §5.3).
 *
 * Вызывается:
 *   - в `rebuildMapOverlays()` (первым — TRZ §5.5) после смены темы;
 *   - в IIFE-инициализаторе ниже на старте приложения.
 *
 * Идемпотентна: дублирующий вызов с тем же сохранённым значением — no-op.
 */
function restoreBaseLayerState() {
  const base = getStoredBaseLayer();
  syncBaseLayerUI(base);
  applyBaseLayer(base);
}

/**
 * Синхронизирует визуальное состояние кнопок переключателя подложки
 * с переданным значением (TRZ §5.4).
 * @param {('schematic'|'satellite')} base
 */
function syncBaseLayerUI(base) {
  const schBtn = document.getElementById('base-btn-schematic');
  const satBtn = document.getElementById('base-btn-satellite');
  if (schBtn) schBtn.classList.toggle('active', base === 'schematic');
  if (satBtn) satBtn.classList.toggle('active', base === 'satellite');
}

// ── Приватные хелперы (ADR-004 §5) ─────────────────────────────────

/**
 * Defensive переключатель класса на document.body. Реальный браузерный
 * `classList` имеет `add`/`remove`/`toggle`, но в unit-тестах
 * (tests/unit/base_layer.test.js) мок-DOM собран минимально и содержит
 * только `contains`. Использует `toggle(name, on)` если доступен,
 * иначе деградирует в no-op (тестовая среда — побочные эффекты на body
 * не важны).
 */
function _setBodyClass(name, on) {
  if (typeof document === 'undefined' || !document.body) return;
  const cl = document.body.classList;
  if (!cl) return;
  if (typeof cl.toggle === 'function') { cl.toggle(name, !!on); return; }
  if (on && typeof cl.add === 'function') { cl.add(name); return; }
  if (!on && typeof cl.remove === 'function') { cl.remove(name); return; }
}


/**
 * Возвращает id первого «верхнего» слоя (terrain/trails/POI),
 * чтобы спутник был добавлен ПОД ним и terrain/trails/POI/маршрут
 * остались видны поверх спутника без вычисления beforeId для каждого
 * слоя в отдельности (ADR-004 §O-A).
 */
function _firstOverlayLayerId(map) {
  const style = map.getStyle && map.getStyle();
  if (!style || !style.layers) return undefined;
  const first = style.layers.find((l) =>
    l.id.startsWith('terrain-') ||
    l.id.startsWith('trails-') ||
    l.id.startsWith('poi-')
  );
  return first ? first.id : undefined;
}

/**
 * Применяет видимость halo-underlay-слоёв у trails по правилу
 * «halo видим ⇔ (base === 'satellite') AND (соответствующий чекбокс ON)»
 * (TRZ §5.7, ADR-004 §9, 12-review.md P1-6).
 *
 * Состояние чекбоксов читается из DOM (`#trails-track-cb`,
 * `#trails-path-cb`). Если узлов нет (тесты под jsdom без HTML или
 * ранний вызов до отрисовки попапа) — пары считаются ON (`true`),
 * это совпадает с дефолтом `restoreTrailsState()`.
 *
 * @param {object} map - инстанс MapLibre.
 * @param {('schematic'|'satellite')} base - текущая база.
 */
function _applyTrailHaloVisibility(map, base) {
  const trackCb = (typeof document !== 'undefined') &&
    document.getElementById && document.getElementById('trails-track-cb');
  const pathCb  = (typeof document !== 'undefined') &&
    document.getElementById && document.getElementById('trails-path-cb');
  const trackOn = trackCb ? !!trackCb.checked : true;
  const pathOn  = pathCb  ? !!pathCb.checked  : true;
  const onByBase = base === 'satellite';
  const pairs = [
    { halo: 'trails-track-halo-satellite',          checked: trackOn },
    { halo: 'trails-path-bridleway-halo-satellite', checked: pathOn  },
  ];
  pairs.forEach((p) => {
    if (!map.getLayer(p.halo)) return;
    const visibility = (onByBase && p.checked) ? 'visible' : 'none';
    map.setLayoutProperty(p.halo, 'visibility', visibility);
  });
}

// Обратная совместимость для существующих unit-тестов, которые могли
// ссылаться на _toggleSatelliteHalo до P1-6 рефакторинга. Делегирует
// на новую функцию с правильным base. См. tests/unit/base_layer.test.js.
function _toggleSatelliteHalo(map, enabled) {
  _applyTrailHaloVisibility(map, enabled ? 'satellite' : 'schematic');
}

/**
 * Применяет правки paint к POI labels/circles в зависимости от
 * активной подложки (ADR-004 §5, Data §5.1–5.2).
 *
 * На «Спутнике» — белый текст с чёрным halo у подписей и белая
 * обводка у кружков, чтобы POI оставались читаемыми поверх тёмных
 * снимков. На «Схеме» — возврат к baseline-значениям текущей темы
 * из `style.json` / `style-dark.json` (см. Data §5.1).
 *
 * Менять обе пары (`text-color` + `text-halo-*`) обязательно: иначе
 * baseline-текст светлой темы `#333333` поверх чёрного halo не
 * читается (см. 12-review.md P1-2).
 */
function _applyPoiSatellitePaint(map, satellite) {
  const dark = (typeof document !== 'undefined') &&
    document.body && document.body.classList &&
    document.body.classList.contains('theme-dark');
  if (map.getLayer('poi-labels')) {
    if (satellite) {
      // Satellite — единые значения для обеих тем (Data §5.2).
      map.setPaintProperty('poi-labels', 'text-color',      '#ffffff');
      map.setPaintProperty('poi-labels', 'text-halo-color', '#000000');
      map.setPaintProperty('poi-labels', 'text-halo-width', 2);
    } else {
      // Schematic — baseline текущей темы (Data §5.1).
      map.setPaintProperty('poi-labels', 'text-color',      dark ? '#e0e0e0' : '#333333');
      map.setPaintProperty('poi-labels', 'text-halo-color', dark ? '#1a1a2e' : '#ffffff');
      map.setPaintProperty('poi-labels', 'text-halo-width', dark ? 2 : 1.5);
    }
  }
  if (map.getLayer('poi-circles')) {
    if (satellite) {
      map.setPaintProperty('poi-circles', 'circle-stroke-color', '#ffffff');
      map.setPaintProperty('poi-circles', 'circle-stroke-width', 2);
    } else {
      map.setPaintProperty('poi-circles', 'circle-stroke-color', dark ? '#333333' : '#ffffff');
      map.setPaintProperty('poi-circles', 'circle-stroke-width', 1.5);
    }
  }
}

/**
 * Меняет цвет background-слоя под спутником на единый тёмно-серый
 * `#2a2a2a` (обе темы) — TRZ §1 REQ-F-03, ADR-004 §6. На «Схеме» —
 * возврат к baseline текущей темы из Data §5 (`#f0ede6` light /
 * `#1a1a2e` dark; именно `#1a1a2e`, как в `style-dark.json:28`, а
 * не `#1a1a1a` из более раннего черновика — см. 12-review.md P1-4).
 */
function _applyBackgroundForSatellite(map, satellite) {
  if (!map.getLayer('background')) return;
  if (satellite) {
    // Единая константа для обеих тем (ADR-004 §6).
    map.setPaintProperty('background', 'background-color', '#2a2a2a');
  } else {
    const dark = (typeof document !== 'undefined') &&
      document.body && document.body.classList &&
      document.body.classList.contains('theme-dark');
    map.setPaintProperty('background', 'background-color', dark ? '#1a1a2e' : '#f0ede6');
  }
}
// <<< ET-007 base layer toggle block <<<

// >>> ET-005 unit toggle block >>>
// Переключатель единиц измерения расстояний (км/мили) в попапе рельефа.
// Выбор единицы, его персистентность и форматирование вынесены в
// src/web/units.js (ADR-0001). Здесь — UI-обработчик попапа и единый
// оркестратор пересчёта видимых расстояний.
// См. docs/work-items/ET-005/06-adr/adr-0001-unit-toggle-client-side.md

/**
 * Обработчик сегментированного переключателя единиц в попапе рельефа
 * (атрибут onclick кнопок «км» / «мили»).
 *
 * Делегирует смену единицы модулю Units. Пересчёт всех видимых
 * расстояний выполняет оркестратор onUnitChange() по событию
 * 'unitchange'; здесь дополнительно синхронизируется вид кнопок.
 * @param {('km'|'mi')} unit - выбранная единица измерения.
 */
function onUnitToggle(unit) {
  Units.setUnit(unit);
  syncUnitToggleUI();
}

/**
 * Синхронизирует визуальное состояние кнопок «км» / «мили» с текущей
 * выбранной единицей измерения.
 *
 * Вызывается при инициализации страницы (восстановление выбора из
 * localStorage — AC-3) и после каждого переключения.
 */
function syncUnitToggleUI() {
  const unit = Units.getUnit();
  const kmBtn = document.getElementById('unit-btn-km');
  const miBtn = document.getElementById('unit-btn-mi');
  if (kmBtn) kmBtn.classList.toggle('active', unit === 'km');
  if (miBtn) miBtn.classList.toggle('active', unit === 'mi');
}

/**
 * Единый оркестратор пересчёта расстояний при смене единицы измерения
 * (ADR-0001 п.6). Подписан на событие 'unitchange' ровно один раз —
 * вместо россыпи подписок по компонентам пере-вызывает функции
 * отрисовки всех видимых поверхностей с расстояниями.
 *
 * Внутреннее состояние остаётся метрическим: конвертация выполняется
 * исключительно в Units.formatDistance().
 */
function onUnitChange() {
  // Карточки основного маршрута, лист точек и мини-карточка.
  if (routeResults.length > 0) {
    renderRouteCards(routeResults);
    updateMiniRouteCard();
  }
  if (routeWaypoints.length > 0) {
    renderWaypointsList();
  }
  // Линейка: updateRulerLabels() обновляет и подписи отрезков, и итог.
  if (rulerMarkers.length > 0) {
    updateRulerLabels();
  }
  // Карточки связки и «красивого» маршрута — только при активном режиме,
  // чтобы перерисовка не возвращала на карту скрытые слои.
  if (linkMode && linkRoutes.length > 0) {
    renderLinkCards(linkRoutes);
  }
  if (scenicMode && scenicRoutes.length > 0) {
    drawScenicRoutes(scenicRoutes, activeScenicIdx);
  }
  // Масштабная линейка карты (риск R3).
  if (typeof window._updateScaleZoom === 'function') {
    window._updateScaleZoom();
  }
}
// <<< ET-005 unit toggle block <<<

/**
 * ET-013: обратно-совместимое расширение для поддержки zoom-aware paint.
 *
 * @param {string} id - id слоя.
 * @param {string} tileUrl - URL-шаблон тайлов.
 * @param {boolean} enabled - показывать ли слой.
 * @param {number|object} opacityOrPaint - либо число (старый контракт,
 *   станет 'raster-opacity' + linear-resampling), либо объект paint-properties
 *   целиком (должен содержать как минимум 'raster-opacity').
 * @param {number} minzoom
 * @param {number} maxzoom
 */
function applyTerrainLayer(id, tileUrl, enabled, opacityOrPaint, minzoom, maxzoom) {
  const map = window._map;
  if (!map) return;

  const sourceId = id + '-source';

  // ET-013: нормализация paint — число (старый контракт) или объект.
  const paint = (typeof opacityOrPaint === 'number')
    ? { 'raster-opacity': opacityOrPaint, 'raster-resampling': 'linear' }
    : opacityOrPaint;

  if (enabled) {
    // Add source if not exists
    if (!map.getSource(sourceId)) {
      map.addSource(sourceId, {
        type: 'raster',
        tiles: [tileUrl],
        tileSize: 256,
        scheme: 'tms',
        minzoom: minzoom,
        maxzoom: maxzoom
      });
    }
    // Add layer if not exists
    if (!map.getLayer(id)) {
      // Insert before first road/trail layer for correct z-order
      const firstTrailLayer = map.getStyle().layers.find(l =>
        l.id.startsWith('trails-') || l.id.startsWith('poi-')
      );
      map.addLayer({
        id: id,
        type: 'raster',
        source: sourceId,
        paint: paint,
        minzoom: minzoom,
        maxzoom: maxzoom
      }, firstTrailLayer ? firstTrailLayer.id : undefined);
    }
  } else {
    // Remove layer and source
    if (map.getLayer(id)) map.removeLayer(id);
    if (map.getSource(sourceId)) map.removeSource(sourceId);
  }
}

function updateHillshadeAvailability() {
  const map = window._map;
  if (!map) return;
  
  const zoom = map.getZoom();
  const cb = document.getElementById('terrain-hillshade-cb');
  const hint = document.getElementById('terrain-hillshade-hint');
  const label = cb ? cb.closest('.terrain-checkbox') : null;
  
  if (zoom < 9) {     // ET-013: на z9 hillshade уже доступен
    if (cb) cb.disabled = true;
    if (label) label.classList.add('disabled');
    if (hint) hint.style.display = 'inline';
  } else {
    if (cb) cb.disabled = false;
    if (label) label.classList.remove('disabled');
    if (hint) hint.style.display = 'none';
  }
}

function restoreTerrainState() {
  const hillshade = localStorage.getItem('terrain-hillshade') === '1';
  const tri = localStorage.getItem('terrain-tri') === '1';
  
  const hillshadeCb = document.getElementById('terrain-hillshade-cb');
  const triCb = document.getElementById('terrain-tri-cb');
  
  if (hillshadeCb) hillshadeCb.checked = hillshade;
  if (triCb) triCb.checked = tri;
  
  if (hillshade || tri) {
    onTerrainCheckbox();
  }
  
  // Update button active state
  const btn = document.getElementById('terrain-toggle');
  if (btn) btn.classList.toggle('active', hillshade || tri);
}

// Hook into map load and zoom changes
(function initTerrain() {
  const map = window._map;
  if (map) {
    map.on('zoomend', updateHillshadeAvailability);
    map.on('style.load', () => {
      // Re-apply terrain after style change (theme switch)
      setTimeout(restoreTerrainState, 100);
    });
    // Initial state
    restoreBaseLayerState();
    restoreTerrainState();
      restoreTrailsState();
      restorePoiState();
  } else {
    // Map not ready yet, wait
    const interval = setInterval(() => {
      if (window._map) {
        clearInterval(interval);
        window._map.on('zoomend', updateHillshadeAvailability);
        window._map.on('style.load', () => {
          setTimeout(restoreTerrainState, 100);
        });
        updateHillshadeAvailability();
        restoreBaseLayerState();
        restoreTerrainState();
    restoreTrailsState();
    restorePoiState();
      }
    }, 500);
  }
})();

// ─── Standalone Search Mode ──────────────────────────────────────
let searchModeActive = false;
let standaloneSearchTimeout = null;

function toggleSearchMode() {
  searchModeActive = !searchModeActive;
  const panel = document.getElementById('search-panel');
  const btn = document.getElementById('tb-search');
  
  if (searchModeActive) {
    panel.style.display = 'block';
    btn.classList.add('active');
    const input = document.getElementById('standalone-search-input');
    input.value = '';
    document.getElementById('standalone-search-results').innerHTML = '';
    setTimeout(() => input.focus(), 100);
    
    input.oninput = () => {
      clearTimeout(standaloneSearchTimeout);
      const q = input.value.trim();
      if (q.length < 2) {
        document.getElementById('standalone-search-results').innerHTML = '';
        return;
      }
      standaloneSearchTimeout = setTimeout(() => doStandaloneSearch(q), 400);
    };
    
    input.onkeydown = (e) => {
      if (e.key === 'Escape') toggleSearchMode();
    };
  } else {
    panel.style.display = 'none';
    btn.classList.remove('active');
  }
}

async function doStandaloneSearch(query) {
  const resultsEl = document.getElementById('standalone-search-results');
  resultsEl.innerHTML = '<div class="search-result-item"><span style="color:var(--text3)">Поиск...</span></div>';
  
  try {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=6&countrycodes=ru&accept-language=ru`;
    const resp = await fetch(url);
    const data = await resp.json();
    
    if (!data.length) {
      resultsEl.innerHTML = '<div class="search-result-item"><span style="color:var(--text3)">Ничего не найдено</span></div>';
      return;
    }
    
    resultsEl.innerHTML = data.map(item => {
      const parts = item.display_name.split(',');
      const name = parts[0].trim();
      const sub = parts.slice(1, 3).join(',').trim();
      return `<div class="search-result-item" onclick="standaloneSelectResult(${item.lat}, ${item.lon}, '${name.replace(/'/g, "\\\'")}')">
        <div class="search-result-name">${name}</div>
        ${sub ? `<div class="search-result-sub">${sub}</div>` : ''}
      </div>`;
    }).join('');
  } catch(e) {
    resultsEl.innerHTML = '<div class="search-result-item"><span style="color:var(--red)">Ошибка поиска</span></div>';
  }
}

function standaloneSelectResult(lat, lon, name) {
  toggleSearchMode();
  window._map.flyTo({ center: [parseFloat(lon), parseFloat(lat)], zoom: 13, duration: 800 });
}
