/* real-map.jsx — real Sofia basemap (Leaflet + CARTO tiles) with real route geometry. */

const SOFIA_HOME = { center: [42.6852, 23.3235], zoom: 14 };

const TILE = {
  light_minimal:  'light_nolabels',
  light_standard: 'light_all',
  light_detailed: 'rastertiles/voyager',
  dark_minimal:   'dark_nolabels',
  dark_standard:  'dark_all',
  dark_detailed:  'dark_all',
};

function cssVars(el) {
  const cs = getComputedStyle(el);
  return (n) => cs.getPropertyValue('--' + n).trim();
}

// slice a route into colored segments following a pattern of {frac, ...} entries (either
// {frac, level} from routePattern or {frac, cat} from routeCatPattern). Each output segment
// carries the original entry's fields so the caller can resolve the color.
function segByPattern(coords, pattern) {
  const n = coords.length;
  if (!pattern || pattern.length <= 1) return [{ pts: coords, level: 'safe', ...(pattern && pattern[0]) }];
  const out = []; let i = 0, acc = 0;
  pattern.forEach((p, k) => {
    acc += p.frac;
    const end = (k === pattern.length - 1) ? n - 1 : Math.max(i + 1, Math.round(acc * (n - 1)));
    out.push({ pts: coords.slice(i, end + 1), ...p });
    i = end;
  });
  return out;
}

// CSS color var for an infrastructure category — the SAME color the filter legend uses, so the
// drawn route matches the markers. 'street' (plain road) gets a neutral grey.
function catColorVar(cat) {
  if (!cat || cat === 'street') return 'route-street';
  const c = window.VR_COMMUNITY && window.VR_COMMUNITY.cats && window.VR_COMMUNITY.cats[cat];
  return (c && c.color) || 'route-street';
}

const pinSVG = (color) =>
  `<svg width="32" height="42" viewBox="0 0 32 42" xmlns="http://www.w3.org/2000/svg">
     <path d="M16 41C16 41 30 25 30 15A14 14 0 1 0 2 15C2 25 16 41 16 41Z" fill="${color}"
       stroke="#fff" stroke-width="2.5"/>
     <circle cx="16" cy="15" r="5.5" fill="#fff"/>
   </svg>`;

// small white glyphs embedded inside community markers (stroke = white)
const CMARK_GLYPH = {
  shield: '<path d="M12 3l7 2.4v5.2c0 4.4-3 7.6-7 9.4-4-1.8-7-5-7-9.4V5.4L12 3z"/><path d="M8.8 12.2l2.3 2.3 4-4.4"/>',
  warn:   '<path d="M12 4.5l8.5 14.5H3.5L12 4.5z"/><path d="M12 10v4M12 16.6v.2"/>',
  car:    '<path d="M4 16v-3l2-5h12l2 5v3M6 16h12"/><circle cx="7.5" cy="16.5" r="1.4"/><circle cx="16.5" cy="16.5" r="1.4"/>',
  bike:   '<circle cx="6" cy="16.5" r="3"/><circle cx="18" cy="16.5" r="3"/><path d="M9 16.5l3-6 3.5 6M9.2 7.6h2.8l2.4 4M13 7.6h2.4"/>',
  bulb:   '<path d="M9.5 18h5M10.2 21h3.6"/><path d="M12 3a6 6 0 00-3.6 10.8c.5.4.6.9.6 1.5v.2h6v-.2c0-.6.1-1.1.6-1.5A6 6 0 0012 3z"/>',
  park:   '<path d="M9 19V6h4a3.3 3.3 0 010 6.6H9"/>',
  drop:   '<path d="M12 4s5.5 6 5.5 9.8a5.5 5.5 0 01-11 0C6.5 10 12 4 12 4z"/>',
  wrench: '<path d="M15.5 5.2a3.6 3.6 0 00-4.7 4.7l-5.9 5.9 1.5 1.5 5.9-5.9a3.6 3.6 0 004.7-4.7l-2.2 2.2-1.8-.4-.4-1.8 2.2-2.2z"/>',
  // VELOSOFIZE imported POI glyphs
  train:  '<rect x="6" y="4" width="12" height="12" rx="3"/><path d="M6 11h12"/><path d="M9 20l1.8-3M15 20l-1.8-3"/>',
  cross:  '<circle cx="12" cy="5" r="1.5"/><path d="M12 8v6M12 14l-3 6M12 14l3 6M8.5 10.5h7"/>',
  pin:    '<path d="M12 21s6-5.6 6-10a6 6 0 10-12 0c0 4.4 6 10 6 10z"/><circle cx="12" cy="11" r="2"/>',
};
// shared so the legend chips + detail-sheet header (screens.jsx CatChip) render the
// exact same glyphs as the map markers.
window.VR_CMARK_GLYPH = CMARK_GLYPH;

// memoize marker HTML — the same (color, glyph, sel) combo is reused across hundreds of markers,
// so building the string once and caching it avoids per-marker template work on every redraw.
const _cmarkCache = {};
function cmarkHTML(color, glyph, sel) {
  const key = color + '|' + glyph + '|' + (sel ? 1 : 0);
  let html = _cmarkCache[key];
  if (html) return html;
  const size = sel ? 38 : 30;
  html = `<div class="vr-cmark${sel ? ' sel' : ''}" style="--c:${color};width:${size}px;height:${size}px">`
    + `<span class="vr-cmark-pulse"></span>`
    + `<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${CMARK_GLYPH[glyph] || ''}</svg>`
    + `</div>`;
  _cmarkCache[key] = html;
  return html;
}

function RealMap({ theme = 'light', detail = 'standard', view = 'home', routeMode = 'none', route = null, selected = 'safe', community = false, commFilter = null, commSelId = null, onPickComm = null, kmzData = null, interactive = true, registerControls = true, commVersion = 0, pickPoint = false, onPickPoint = null }) {
  const elRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const tileRef = React.useRef(null);
  const drawRef = React.useRef([]);
  const commRef = React.useRef([]);
  const kmzRef = React.useRef([]);
  const kmzCanvasRef = React.useRef(null);
  const pickRef = React.useRef(null);
  const userRef = React.useRef(null);
  // selection registries: id → { setSel(on) } for already-rendered features, so picking a
  // marker re-styles just the two affected features instead of rebuilding whole layers.
  const commItemsRef = React.useRef(new Map());   // community points + segments
  const kmzLineItemsRef = React.useRef(new Map()); // VELOSOFIZE route lines
  const kmzPtItemsRef = React.useRef(new Map());   // VELOSOFIZE POI markers (re-culled on move)
  const selAppliedRef = React.useRef(null);        // currently-highlighted id
  const commSelIdRef = React.useRef(commSelId);    // live selected id for move/zoom callbacks
  commSelIdRef.current = commSelId;
  const applySel = (id, on) => {
    if (id == null) return;
    const it = commItemsRef.current.get(id) || kmzLineItemsRef.current.get(id) || kmzPtItemsRef.current.get(id);
    if (it) it.setSel(on);
  };

  // init map once
  React.useEffect(() => {
    const map = L.map(elRef.current, {
      zoomControl: false, attributionControl: true,
      dragging: interactive, scrollWheelZoom: interactive, doubleClickZoom: interactive, touchZoom: interactive,
      boxZoom: false, keyboard: false, tap: interactive, inertia: true, zoomSnap: 0.25,
      fadeAnimation: false, zoomAnimation: false, markerZoomAnimation: false,
    });
    mapRef.current = map;
    if (registerControls) window.__vrMap = map; // debug hook for the main map instance
    if (registerControls) window.__vrMapZoom = (d) => { const m = mapRef.current; if (m) m.setZoom(m.getZoom() + d); };
    map.setView(SOFIA_HOME.center, SOFIA_HOME.zoom);
    if (registerControls) window.__vrMapRecenter = () => { const m = mapRef.current; if (m) m.flyTo(SOFIA_HOME.center, SOFIA_HOME.zoom, { duration: 0.6 }); };
    // "you are here" marker — a blue pulsing dot in its own ref, so the route/community/kmz
    // effects (which only clear their own refs) never remove it.
    if (registerControls) window.__vrShowUserLocation = (lat, lng, fly) => {
      const m = mapRef.current; if (!m || lat == null || lng == null) return;
      const ll = L.latLng(lat, lng);
      if (userRef.current) userRef.current.setLatLng(ll);
      else userRef.current = L.marker(ll, {
        icon: L.divIcon({ html: '<div class="vr-userloc"><span class="vr-userloc-pulse"></span><span class="vr-userloc-dot"></span></div>', className: 'vr-marker', iconSize: [30, 30], iconAnchor: [15, 15] }),
        interactive: false, zIndexOffset: 900,
      }).addTo(m);
      if (fly) m.flyTo(ll, Math.max(m.getZoom(), 16), { duration: 0.6 });
    };
    if (registerControls) window.__vrClearUserLocation = () => { const m = mapRef.current; if (m && userRef.current) { m.removeLayer(userRef.current); userRef.current = null; } };
    // directional "puck" used during active navigation — a heading arrow when we know which way
    // the rider faces, falling back to the plain pulsing dot when heading is unknown. Shares
    // userRef with the dot above so the nav line/marker effects never clear it.
    if (registerControls) window.__vrShowUserPuck = (lat, lng, heading) => {
      const m = mapRef.current; if (!m || lat == null || lng == null) return;
      const ll = L.latLng(lat, lng);
      const html = (heading == null || isNaN(heading))
        ? '<div class="vr-userloc"><span class="vr-userloc-pulse"></span><span class="vr-userloc-dot"></span></div>'
        : `<div class="vr-userpuck" style="transform:rotate(${heading}deg)"><svg viewBox="0 0 24 24" width="30" height="30"><path d="M12 2 L20.5 21 L12 16 L3.5 21 Z" fill="#2f76d8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/></svg></div>`;
      const icon = L.divIcon({ html, className: 'vr-marker', iconSize: [30, 30], iconAnchor: [15, 15] });
      if (userRef.current) userRef.current.setLatLng(ll).setIcon(icon);
      else userRef.current = L.marker(ll, { icon, interactive: false, zIndexOffset: 900 }).addTo(m);
    };
    const inv = () => map.invalidateSize();
    setTimeout(inv, 60); setTimeout(inv, 400);
    window.addEventListener('resize', inv);
    return () => { window.removeEventListener('resize', inv); map.remove(); mapRef.current = null; };
  }, []);

  // tiles by theme + detail
  React.useEffect(() => {
    const map = mapRef.current; if (!map) return;
    if (tileRef.current) map.removeLayer(tileRef.current);
    const dark = theme !== 'light';
    const key = (dark ? 'dark_' : 'light_') + detail;
    const path = TILE[key] || (dark ? 'dark_all' : 'light_all');
    tileRef.current = L.tileLayer(`https://{s}.basemaps.cartocdn.com/${path}/{z}/{x}/{y}{r}.png`, {
      subdomains: 'abcd', maxZoom: 20, detectRetina: true, crossOrigin: true,
      attribution: '&copy; OpenStreetMap &middot; CARTO',
    }).addTo(map);
    tileRef.current.bringToBack();
  }, [theme, detail]);

  // route + markers + view
  React.useEffect(() => {
    const map = mapRef.current; if (!map) return;
    map.invalidateSize();
    drawRef.current.forEach((l) => map.removeLayer(l));
    drawRef.current = [];
    const add = (l) => { l.addTo(map); drawRef.current.push(l); };
    const V = cssVars(elRef.current);

    if (routeMode === 'none' || !route) {
      map.setView(SOFIA_HOME.center, SOFIA_HOME.zoom, { animate: false });
      return;
    }

    const sel = route.routes.find((r) => r.id === selected) || route.routes[0];

    // color the route by infrastructure category (bike lane / shared / comfort / street / …) —
    // same colors as the filter legend — so the rider sees what to expect. Falls back to the
    // safety pattern, then a single brand line, if the cat classification isn't ready yet.
    const colorSegs = (coords, baseWeight) => {
      const segs = sel.catPattern ? segByPattern(coords, sel.catPattern) : segByPattern(coords, sel.pattern);
      segs.forEach((s) => {
        const color = sel.catPattern ? V(catColorVar(s.cat)) : V(s.level);
        add(L.polyline(s.pts, { color, weight: baseWeight, opacity: 1, lineCap: 'round', lineJoin: 'round', interactive: false }));
      });
    };

    if (routeMode === 'plan') {
      // faint alternatives
      route.routes.forEach((r) => {
        if (r.id !== sel.id) add(L.polyline(r.coords, {
          color: theme === 'light' ? '#9b988f' : '#6b7280', weight: 5,
          opacity: 0.55, dashArray: '1 10', lineCap: 'round', interactive: false,
        }));
      });
      // white casing + category-colored segments
      add(L.polyline(sel.coords, { color: theme === 'light' ? '#fff' : '#0c1118', weight: 12, opacity: 0.95, lineCap: 'round', lineJoin: 'round', interactive: false }));
      colorSegs(sel.coords, 7.5);
    } else { // active — soft halo + category-colored segments
      add(L.polyline(sel.coords, { color: V('navline'), weight: 14, opacity: 0.18, lineCap: 'round', lineJoin: 'round', interactive: false }));
      add(L.polyline(sel.coords, { color: theme === 'light' ? '#fff' : '#0c1118', weight: 11, opacity: 0.9, lineCap: 'round', lineJoin: 'round', interactive: false }));
      colorSegs(sel.coords, 7.5);
    }

    // destination marker
    add(L.marker(route.dest, {
      icon: L.divIcon({ html: pinSVG(V('danger')), className: 'vr-marker', iconSize: [32, 42], iconAnchor: [16, 41] }),
      interactive: false,
    }));
    // start marker — only in PLAN mode. During active nav the rider is shown by the live puck
    // (__vrShowUserPuck), so a static start pin here is a duplicate "stuck at start" marker.
    if (routeMode !== 'active') {
      const navc = V('navline');
      add(L.marker(route.start, {
        icon: L.divIcon({ html: `<div class="vr-dot" style="background:${navc}"><span style="background:${navc}"></span></div>`, className: 'vr-marker', iconSize: [22, 22], iconAnchor: [11, 11] }),
        interactive: false,
      }));
    }

    // view
    if (view === 'route') {
      map.fitBounds(L.latLngBounds(sel.coords), { paddingTopLeft: [36, 150], paddingBottomRight: [36, 470], animate: false });
    } else if (view === 'nav') {
      const idx = Math.max(0, Math.floor(sel.coords.length * 0.12));
      map.setView(sel.coords[idx], 16.5, { animate: false });
    }
  }, [route, selected, routeMode, view, theme, detail]);

  // community crowdsourced layer (shown on home when toggled) — interactive.
  // Rebuilt only on its real inputs; selection highlight is handled by the dedicated effect
  // below (toggles two markers) so picking a marker no longer rebuilds the whole layer.
  React.useEffect(() => {
    const map = mapRef.current; if (!map) return;
    commRef.current.forEach((l) => map.removeLayer(l));
    commRef.current = [];
    commItemsRef.current = new Map();
    if (!community || view !== 'home' || !window.VR_COMMUNITY) return;
    const add = (l) => { l.addTo(map); commRef.current.push(l); };
    const V = cssVars(elRef.current);
    const C = window.VR_COMMUNITY;
    const cats = C.cats;
    const on = (cat) => !commFilter || commFilter[cat] !== false;
    const pick = (item) => onPickComm && onPickComm(item);
    const selId = commSelIdRef.current;

    // segments (safe / danger lines)
    C.segments.forEach((s) => {
      if (!on(s.cat)) return;
      const col = V(cats[s.cat].color);
      const pending = s.status === 'pending';
      const isSel = selId === s.id;
      // wide invisible hit-line for easy tapping
      const hit = L.polyline(s.coords, { color: '#000', weight: 22, opacity: 0, lineCap: 'round' });
      hit.on('click', () => pick(s)); add(hit);
      const line = L.polyline(s.coords, {
        color: col, weight: isSel ? 9 : 7, opacity: pending ? 0.9 : 1,
        dashArray: pending ? '2 9' : null, lineCap: 'round', lineJoin: 'round', interactive: false,
      });
      add(line);
      commItemsRef.current.set(s.id, { setSel: (sel) => line.setStyle({ weight: sel ? 9 : 7 }) });
    });

    // points (categorized markers)
    C.points.forEach((p) => {
      if (!on(p.cat)) return;
      const cat = cats[p.cat];
      const isSel = selId === p.id;
      const col = V(cat.color);
      const mk = (sel) => L.divIcon({
        html: cmarkHTML(col, cat.glyph, sel),
        className: 'vr-marker', iconSize: sel ? [38, 38] : [30, 30],
        iconAnchor: sel ? [19, 19] : [15, 15],
      });
      const m = L.marker(p.at, { icon: mk(isSel), riseOnHover: true, zIndexOffset: isSel ? 1000 : 0 });
      m.on('click', () => pick(p));
      add(m);
      commItemsRef.current.set(p.id, { setSel: (sel) => { m.setIcon(mk(sel)); m.setZIndexOffset(sel ? 1000 : 0); } });
    });
  }, [community, view, theme, commFilter, onPickComm, commVersion]);

  // selection highlight — re-style just the previously- and newly-selected features instead of
  // rebuilding the community / KMZ layers. Runs after those effects so the registries are fresh.
  React.useEffect(() => {
    const prev = selAppliedRef.current;
    if (prev === commSelId) return;
    applySel(prev, false);
    applySel(commSelId, true);
    selAppliedRef.current = commSelId;
  }, [commSelId]);

  // contribute pin-pick mode — tap the map to drop/move a marker; report off its coords.
  React.useEffect(() => {
    const map = mapRef.current; if (!map) return;
    const clear = () => { if (pickRef.current) { map.removeLayer(pickRef.current); pickRef.current = null; } };
    if (!pickPoint) { clear(); return; }
    const V = cssVars(elRef.current);
    const place = (latlng) => {
      if (pickRef.current) { pickRef.current.setLatLng(latlng); }
      else {
        pickRef.current = L.marker(latlng, { draggable: true,
          icon: L.divIcon({ html: pinSVG(V('brand')), className: 'vr-marker', iconSize: [32, 42], iconAnchor: [16, 41] }) }).addTo(map);
        pickRef.current.on('dragend', (e) => onPickPoint && onPickPoint(e.target.getLatLng()));
      }
      onPickPoint && onPickPoint(latlng);
    };
    // seed from a previously-picked point, else the map centre
    const seed = window.__vrPickedPoint || map.getCenter();
    place(L.latLng(seed.lat != null ? seed.lat : seed[0], seed.lng != null ? seed.lng : seed[1]));
    const onClick = (e) => place(e.latlng);
    map.on('click', onClick);
    return () => { map.off('click', onClick); clear(); };
  }, [pickPoint]);

  // VELOSOFIZE imported layer — curated route network + POIs from the official KMZ.
  // Routes are canvas-rendered (591 lines) on a pane below the route overlays; POI markers
  // are gated by zoom so ~730 markers don't lag at city scale. Honors the same commFilter.
  React.useEffect(() => {
    const map = mapRef.current; if (!map) return;
    kmzRef.current.forEach((l) => map.removeLayer(l));
    kmzRef.current = [];
    kmzLineItemsRef.current = new Map();
    kmzPtItemsRef.current = new Map();
    if (!kmzData || view !== 'home') return;
    if (!map.getPane('kmz')) map.createPane('kmz').style.zIndex = 350; // below overlayPane (400)
    if (!kmzCanvasRef.current) kmzCanvasRef.current = L.canvas({ pane: 'kmz', padding: 0.5 });
    const add = (l) => { l.addTo(map); kmzRef.current.push(l); };
    const V = cssVars(elRef.current);
    const cats = window.VR_COMMUNITY.cats;
    const on = (cat) => !commFilter || commFilter[cat] !== false;
    const pick = (item) => onPickComm && onPickComm(item);

    // route lines (canvas) — click opens the shared detail sheet (same as community markers).
    // Selection highlight is applied separately (kmzLineItemsRef) so picking doesn't rebuild all 591.
    kmzData.routes.forEach((r) => {
      if (!on(r.cat)) return;
      const cat = cats[r.cat] || {};
      const isSel = commSelIdRef.current === r.id;
      const line = L.polyline(r.coords, {
        renderer: kmzCanvasRef.current, color: V(cat.color || 'route-untested'),
        weight: isSel ? 6 : 3.5, opacity: isSel ? 1 : 0.85, lineCap: 'round', lineJoin: 'round',
      });
      line.on('click', () => pick(r));
      add(line);
      kmzLineItemsRef.current.set(r.id, { setSel: (sel) => line.setStyle({ weight: sel ? 6 : 3.5, opacity: sel ? 1 : 0.85 }) });
    });

    // POI markers — only when zoomed in AND within the (padded) viewport, so ~730 markers don't
    // all mount at once. Redrawn on move/zoom so panning reveals new ones.
    const POINT_ZOOM = 13;
    const drawPoints = () => {
      kmzRef.current = kmzRef.current.filter((l) => { if (l.__kmzPt) { map.removeLayer(l); return false; } return true; });
      kmzPtItemsRef.current = new Map();
      if (map.getZoom() < POINT_ZOOM) return;
      const bounds = map.getBounds().pad(0.2);
      const selId = commSelIdRef.current;
      kmzData.points.forEach((p) => {
        if (!on(p.cat)) return;
        if (!bounds.contains(L.latLng(p.at))) return;
        const cat = cats[p.cat] || {};
        const isSel = selId === p.id;
        const col = V(cat.color || 'route-untested'), glyph = cat.glyph || 'pin';
        const mk = (sel) => L.divIcon({ html: cmarkHTML(col, glyph, sel),
          className: 'vr-marker', iconSize: sel ? [38, 38] : [30, 30], iconAnchor: sel ? [19, 19] : [15, 15] });
        const m = L.marker(p.at, { icon: mk(isSel), riseOnHover: true, zIndexOffset: isSel ? 1000 : 0 });
        m.on('click', () => pick(p));
        m.__kmzPt = true;
        add(m);
        kmzPtItemsRef.current.set(p.id, { setSel: (sel) => { m.setIcon(mk(sel)); m.setZIndexOffset(sel ? 1000 : 0); } });
      });
    };
    drawPoints();
    map.on('zoomend', drawPoints);
    map.on('moveend', drawPoints);
    return () => { map.off('zoomend', drawPoints); map.off('moveend', drawPoints); };
  }, [kmzData, view, theme, commFilter, onPickComm]);

  // pan to selected item (community OR VELOSOFIZE imported), keeping it above the detail sheet
  React.useEffect(() => {
    const map = mapRef.current; if (!map || !community || view !== 'home' || !commSelId || !window.VR_COMMUNITY) return;
    const C = window.VR_COMMUNITY;
    const K = kmzData || { points: [], routes: [] };
    const item = C.points.find((p) => p.id === commSelId)
      || C.segments.find((s) => s.id === commSelId)
      || K.points.find((p) => p.id === commSelId)
      || K.routes.find((r) => r.id === commSelId);
    if (!item) return;
    const target = item.at ? L.latLng(item.at) : L.latLngBounds(item.coords).getCenter();
    const z = Math.max(map.getZoom(), 15);
    const pt = map.project(target, z).subtract([0, 150]); // shift up so sheet doesn't cover
    map.flyTo(map.unproject(pt, z), z, { duration: 0.5 });
  }, [commSelId, community, view, kmzData]);

  return <div ref={elRef} style={{ position: 'absolute', inset: 0, background: 'var(--land)' }} />;
}

Object.assign(window, { RealMap });
