// LeafletMap.jsx — real geographic map with Leaflet + Supabase data
// Uses getSupabase() from Data.jsx (loaded earlier)

const LINE_COLORS = {
  A: '#0078B1', B: '#A72126', C: '#09517D', D: '#006515',
  E: '#52267D', H: '#D4A200', U: '#FF8C3A', P: '#FFC85C',
};

const MAP_ALIASES = {
  'Plaza Miserere':     'Plaza De Miserere',
  'L. N. Alem':         'Leandro N. Alem',
  'Carlos Pellegrini':  'C. Pellegrini',
  'Medrano':            'Almagro - Medrano',
  'J. M. de Rosas':     'Juan Manuel De Rosas - Villa Urquiza',
  'General San Martín': 'San Martin',
  'Scalabrini Ortiz':   'R.Scalabrini Ortiz',
  'José M. Moreno':     'Jose Maria Moreno',
};

function normStation(s) {
  return s
    .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/\bav\.\s*/g, 'avenida ')
    .replace(/\s*-\s*.+$/, '')
    .trim();
}

const BA_CENTER    = [-34.615, -58.443];
const BA_ZOOM      = 13;
const BA_ZOOM_MINI = 12;

const TILE_LIGHT = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
const TILE_DARK  = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
const TILE_ATTR  = '© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/">CARTO</a>';

function getTheme() {
  return document.documentElement.getAttribute('data-theme') || 'light';
}

// ── Interpolation ─────────────────────────────────────────────────────────────
// Each "train" = one forecast record: {nextStop, prevStop, line_code, direction_id}
// nextStop: {lat, lng, arrival_epoch, departure_epoch}   ← where it's heading
// prevStop: same shape, or null if this is the first stop on the line
//
// We estimate that the train departed prevStop ~60 s before arriving at nextStop.
// This gives smooth movement across the 5-second animation interval.
const TRANSIT_ESTIMATE = 60; // seconds between adjacent stops (estimate)

function interpolateTrainPosition(now, train) {
  const { nextStop, prevStop } = train;

  // ① Dwelling at next stop
  if (now >= nextStop.arrival_epoch && now < nextStop.departure_epoch) {
    return { lat: nextStop.lat, lng: nextStop.lng, atStation: true };
  }

  // ② Already departed – hold at stop until next 30 s refresh brings new data
  if (now >= nextStop.departure_epoch) {
    return { lat: nextStop.lat, lng: nextStop.lng, atStation: false };
  }

  // ③ En route: interpolate from prevStop toward nextStop
  const fromLat = prevStop ? prevStop.lat : nextStop.lat;
  const fromLng = prevStop ? prevStop.lng : nextStop.lng;
  const depPrev = nextStop.arrival_epoch - TRANSIT_ESTIMATE;

  if (now >= depPrev) {
    const t = Math.min((now - depPrev) / TRANSIT_ESTIMATE, 1);
    return {
      lat: fromLat + (nextStop.lat - fromLat) * t,
      lng: fromLng + (nextStop.lng - fromLng) * t,
      atStation: false,
    };
  }

  // ④ Before even departing prevStop – show at prevStop
  return { lat: fromLat, lng: fromLng, atStation: true };
}

// ── Train marker icon ─────────────────────────────────────────────────────────
function makeTrainIcon(color, isLight) {
  return L.divIcon({
    className: '',
    html: `<div style="
      width:22px;height:22px;border-radius:50%;
      background:${color};
      border:2.5px solid white;
      box-shadow:0 2px 7px rgba(0,0,0,.38);
      display:flex;align-items:center;justify-content:center;
    ">
      <div style="
        width:7px;height:7px;border-radius:50%;
        background:#FFE500;
        box-shadow:0 0 4px rgba(255,229,0,.8);
      "></div>
    </div>`,
    iconSize:   [22, 22],
    iconAnchor: [11, 11],
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// LeafletMap component
// ─────────────────────────────────────────────────────────────────────────────
function LeafletMap({
  selectedLines   = ['A','B','C','D','E','H','U','P'],
  showTrains      = true,
  showEntrances   = false,
  mini            = false,
  onLineTap,
}) {
  const divRef           = React.useRef(null);
  const mapRef           = React.useRef(null);
  const tileLayerRef     = React.useRef(null);
  const polyRefs         = React.useRef({});
  const stationRefs      = React.useRef({});
  const trainMarkerRefs  = React.useRef({});  // trainId (trip_id_stop_id) → L.marker
  const entranceRefs     = React.useRef([]);
  const userMarkerRef    = React.useRef(null);
  const watchIdRef       = React.useRef(null);
  const scheduleRef      = React.useRef({});  // trainId → {line_code, direction_id, nextStop, prevStop}
  const fetchTimerRef    = React.useRef(null);
  const animTimerRef     = React.useRef(null);
  const themeObserverRef = React.useRef(null);

  // live refs for toggle state (readable from timer callbacks)
  const showTrainsRef    = React.useRef(showTrains);
  const showEntrancesRef = React.useRef(showEntrances);
  const selectedLinesRef = React.useRef(selectedLines);
  React.useEffect(() => { showTrainsRef.current    = showTrains;    }, [showTrains]);
  React.useEffect(() => { showEntrancesRef.current = showEntrances; }, [showEntrances]);
  React.useEffect(() => { selectedLinesRef.current = selectedLines; }, [selectedLines]);

  // ── init map once ──────────────────────────────────────────
  React.useEffect(() => {
    if (mapRef.current) return;

    const map = L.map(divRef.current, {
      center:             BA_CENTER,
      zoom:               mini ? BA_ZOOM_MINI : BA_ZOOM,
      zoomControl:        false,
      attributionControl: !mini,
      dragging:           !mini,
      scrollWheelZoom:    !mini,
      doubleClickZoom:    !mini,
      touchZoom:          !mini,
      keyboard:           !mini,
    });

    const tileUrl = getTheme() === 'dark' ? TILE_DARK : TILE_LIGHT;
    tileLayerRef.current = L.tileLayer(tileUrl, { attribution: TILE_ATTR, maxZoom: 19 }).addTo(map);

    // Watch for dark/light theme changes and swap tile layer
    themeObserverRef.current = new MutationObserver(() => {
      const url = getTheme() === 'dark' ? TILE_DARK : TILE_LIGHT;
      if (tileLayerRef.current) {
        tileLayerRef.current.setUrl(url);
      }
    });
    themeObserverRef.current.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });

    if (!mini) L.control.zoom({ position: 'bottomright' }).addTo(map);
    mapRef.current = map;

    // Static layers
    loadLines(map);
    loadEntrances(map);

    if (!mini) {
      // User location (point 3)
      if (navigator.geolocation) {
        watchIdRef.current = navigator.geolocation.watchPosition(
          pos => updateUserLocation(map, pos.coords.latitude, pos.coords.longitude),
          () => {},
          { enableHighAccuracy: true, maximumAge: 5000 }
        );
      }

      // Live train fetch/animation — disabled (representación en vivo desactivada)
      // fetchSchedule(map);
      // fetchTimerRef.current = setInterval(() => fetchSchedule(map), 30000);
      // animTimerRef.current  = setInterval(() => renderTrains(map),   5000);
    }

    return () => {
      if (fetchTimerRef.current)    clearInterval(fetchTimerRef.current);
      if (animTimerRef.current)     clearInterval(animTimerRef.current);
      if (themeObserverRef.current) themeObserverRef.current.disconnect();
      if (watchIdRef.current != null) navigator.geolocation.clearWatch(watchIdRef.current);
      map.remove();
      mapRef.current = null;
    };
  }, []);

  // ── showTrains toggle ──────────────────────────────────────
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map || mini) return;
    if (!showTrains) {
      Object.values(trainMarkerRefs.current).forEach(m => {
        if (map.hasLayer(m)) map.removeLayer(m);
      });
    } else {
      renderTrains(map);
    }
  }, [showTrains]);

  // ── showEntrances toggle ───────────────────────────────────
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    entranceRefs.current.forEach(m => {
      if (showEntrances) { if (!map.hasLayer(m)) m.addTo(map); }
      else               { if (map.hasLayer(m))  map.removeLayer(m); }
    });
  }, [showEntrances]);

  // ── selectedLines filter ───────────────────────────────────
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    Object.entries(polyRefs.current).forEach(([code, poly]) => {
      if (selectedLines.includes(code)) { if (!map.hasLayer(poly)) poly.addTo(map); }
      else                              { if (map.hasLayer(poly))  map.removeLayer(poly); }
    });
    Object.entries(stationRefs.current).forEach(([code, markers]) => {
      markers.forEach(m => {
        if (selectedLines.includes(code)) { if (!map.hasLayer(m)) m.addTo(map); }
        else                              { if (map.hasLayer(m))  map.removeLayer(m); }
      });
    });
  }, [selectedLines]);

  // ── helpers ────────────────────────────────────────────────
  async function loadLines(map) {
    try {
      const { data, error } = await getSupabase().rpc('get_stations_with_lines');
      if (error || !data) return;

      const grouped = {};
      data.forEach(row => {
        if (!grouped[row.line_code]) grouped[row.line_code] = {};
        const ll = [row.lat, row.lng];
        grouped[row.line_code][row.canonical_name]           = ll;
        grouped[row.line_code][normStation(row.canonical_name)] = ll;
      });

      LINES.forEach(lineData => {
        const code = lineData.id;
        const cmap = grouped[code];
        if (!cmap) return;

        const pairs = lineData.stations.map(name => {
          const alias = MAP_ALIASES[name];
          const ll = cmap[name]
            || (alias && cmap[alias])
            || (alias && cmap[normStation(alias)])
            || cmap[normStation(name)]
            || null;
          return ll ? { name, ll } : null;
        }).filter(Boolean);
        if (pairs.length < 2) return;

        const latlngs = pairs.map(p => p.ll);
        const color   = LINE_COLORS[code] || '#888';

        const poly = L.polyline(latlngs, { color, weight: mini ? 4 : 5, opacity: 0.85, lineJoin: 'round' });
        if (selectedLines.includes(code)) poly.addTo(map);
        if (!mini) poly.on('click', () => onLineTap && onLineTap(code));
        polyRefs.current[code] = poly;

        const markers = pairs.map(({ name, ll }) => {
          const isClosed = typeof CLOSED_TODAY !== 'undefined' && CLOSED_TODAY.includes(name);
          const m = L.circleMarker(ll, {
            radius: mini ? 3 : 5, color, weight: 2,
            fillColor: isClosed ? '#FF6B6B' : 'white', fillOpacity: 1,
            opacity: isClosed ? 0.7 : 1,
          });
          if (!mini) m.bindTooltip(name, { direction: 'top', offset: [0, -6], className: 'leaflet-station-tip' });
          if (selectedLines.includes(code)) m.addTo(map);
          return m;
        });
        stationRefs.current[code] = markers;
      });
    } catch (e) { console.warn('LeafletMap: loadLines error', e); }
  }

  async function loadEntrances(map) {
    try {
      const { data, error } = await getSupabase().rpc('get_entrances_geo');
      if (error || !data) return;
      data.forEach(en => {
        if (!en.lat || !en.lng) return;
        const color   = LINE_COLORS[en.line_code] || '#888';
        const isLight = en.line_code === 'H' || en.line_code === 'P';
        const icon = L.divIcon({
          className: '',
          html: `<div style="width:8px;height:8px;background:${color};border:1.5px solid ${isLight ? 'rgba(0,0,0,.35)' : 'rgba(255,255,255,.7)'};border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.3);"></div>`,
          iconSize: [8, 8], iconAnchor: [4, 4],
        });
        const m = L.marker([en.lat, en.lng], { icon, zIndexOffset: 100 });
        if (!mini) {
          const parts = [];
          if (en.destino_boca) parts.push(en.destino_boca);
          if (en.is_accessible) parts.push('♿ Accesible');
          if (en.ascensor) parts.push('Ascensor');
          if (en.escalera_mecanica) parts.push('Escalera mecánica');
          if (en.closes_on_weekend) parts.push('Cierra fines de semana');
          m.bindTooltip(
            `<b>${en.station_name}</b> · L${en.line_code}<br>${parts.join(' · ') || 'Boca de acceso'}`,
            { direction: 'top', offset: [0, -6], className: 'leaflet-entrance-tip' }
          );
        }
        if (showEntrancesRef.current) m.addTo(map);
        entranceRefs.current.push(m);
      });
    } catch (e) { console.warn('LeafletMap: loadEntrances error', e); }
  }

  // Fetch schedule from DB (called every 30 s).
  // The RPC now returns ONE ROW PER PHYSICAL TRAIN (= one row per stop on the line),
  // because the BA API encodes multiple simultaneous trains under the same trip_id.
  async function fetchSchedule(map) {
    try {
      const { data, error } = await getSupabase().rpc('get_trips_with_schedule');
      if (error || !data) { console.warn('LeafletMap: fetchSchedule', error); return; }

      // 1. Group stops by trip_id and sort in physical travel order
      const byTrip = {};
      data.forEach(row => {
        if (!byTrip[row.trip_id]) byTrip[row.trip_id] = [];
        byTrip[row.trip_id].push(row);
      });

      // 2. For each trip build a map: trainId → {line_code, direction_id, nextStop, prevStop}
      //    trainId = `${trip_id}_${stop_id}` — one unique train per stop
      const trains = {};

      Object.entries(byTrip).forEach(([tripId, stops]) => {
        // is_ascending is the same for all rows of a trip; use the first row
        const isAsc = stops[0]?.is_ascending !== false;

        // Sort ascending by stop_num; for descending trips the "previous" stop
        // in travel direction is the one with the HIGHER index in this sorted array
        const sorted = [...stops].sort((a, b) => a.stop_num - b.stop_num);

        sorted.forEach((stop, idx) => {
          // prevStop in physical travel direction:
          //   ascending  trip (B11): came from lower stop_num  → sorted[idx-1]
          //   descending trip (B01): came from higher stop_num → sorted[idx+1]
          const prevStop = isAsc
            ? (idx > 0              ? sorted[idx - 1] : null)
            : (idx < sorted.length - 1 ? sorted[idx + 1] : null);

          trains[`${tripId}_${stop.stop_id}`] = {
            line_code:    stop.line_code,
            direction_id: stop.direction_id,
            nextStop: {
              lat:             stop.lat,
              lng:             stop.lng,
              arrival_epoch:   stop.arrival_epoch,
              departure_epoch: stop.departure_epoch,
              stop_id:         stop.stop_id,
            },
            // prevStop only needs coords; epoch is used just for dwell check (unused here)
            prevStop: prevStop ? { lat: prevStop.lat, lng: prevStop.lng } : null,
          };
        });
      });

      scheduleRef.current = trains;
      renderTrains(map);
    } catch (e) { console.warn('LeafletMap: fetchSchedule error', e); }
  }

  // Recompute positions every 5 s using client-side interpolation.
  // scheduleRef now holds ONE entry per physical train (trip_id + stop_id).
  function renderTrains(map) {
    if (!showTrainsRef.current) return;
    const now    = Date.now() / 1000;
    const trains = scheduleRef.current;
    const lines  = selectedLinesRef.current;

    Object.entries(trains).forEach(([trainId, train]) => {
      if (!lines.includes(train.line_code)) {
        const m = trainMarkerRefs.current[trainId];
        if (m && map.hasLayer(m)) map.removeLayer(m);
        return;
      }

      const pos = interpolateTrainPosition(now, train);
      if (!pos) return;

      const color = LINE_COLORS[train.line_code] || '#888';
      const ll    = [pos.lat, pos.lng];

      if (trainMarkerRefs.current[trainId]) {
        trainMarkerRefs.current[trainId].setLatLng(ll);
        if (!map.hasLayer(trainMarkerRefs.current[trainId])) {
          trainMarkerRefs.current[trainId].addTo(map);
        }
      } else {
        const icon = makeTrainIcon(color);
        const m    = L.marker(ll, { icon, zIndexOffset: 400 });
        const dir  = train.direction_id === 0 ? '→' : '←';
        m.bindTooltip(`Línea ${train.line_code} ${dir}`, {
          direction: 'top', offset: [0, -13], className: 'leaflet-train-tip',
        });
        m.addTo(map);
        trainMarkerRefs.current[trainId] = m;
      }
    });

    // Remove stale markers (trains no longer in schedule)
    Object.keys(trainMarkerRefs.current).forEach(trainId => {
      if (!trains[trainId]) {
        const m = trainMarkerRefs.current[trainId];
        if (map.hasLayer(m)) map.removeLayer(m);
        delete trainMarkerRefs.current[trainId];
      }
    });
  }

  // User location — pulsing blue dot (point 3)
  function updateUserLocation(map, lat, lng) {
    if (userMarkerRef.current) {
      userMarkerRef.current.setLatLng([lat, lng]);
      return;
    }
    const icon = L.divIcon({
      className: '',
      html: `<div style="position:relative;width:20px;height:20px">
        <div style="
          position:absolute;inset:-10px;border-radius:50%;
          background:rgba(0,120,177,.18);
          animation:pulse 1.8s ease-in-out infinite;
        "></div>
        <div style="
          position:absolute;inset:0;border-radius:50%;
          background:#0078B1;border:3px solid white;
          box-shadow:0 2px 6px rgba(0,0,0,.3);
        "></div>
      </div>`,
      iconSize:   [20, 20],
      iconAnchor: [10, 10],
    });
    userMarkerRef.current = L.marker([lat, lng], { icon, zIndexOffset: 1000 }).addTo(map);
    userMarkerRef.current.bindTooltip('Tu ubicación', { permanent: false, direction: 'top' });
  }

  return <div ref={divRef} style={{ width: '100%', height: '100%' }} />;
}

Object.assign(window, { LeafletMap });
