// Positions map — geocodes household members' addresses (from Reporting_Conso Pos_* columns)
// and plots them on a real France map (d3-geo + world-atlas topojson), styled to the theme.
const { useState, useEffect, useRef, useMemo } = React;

const PEOPLE = [
  { key: 'posSteph',  label: 'Steph',  color: 'var(--accent)' },
  { key: 'posHerve',  label: 'Hervé',  color: 'var(--green)' },
  { key: 'posJeanne', label: 'Jeanne', color: 'var(--sun)' },
  { key: 'posMaelle', label: 'Maëlle', color: 'var(--grid-red)' },
];

function geocodeCacheGet(addr) {
  try {
    const c = JSON.parse(localStorage.getItem('geocode-cache') || '{}');
    return c[addr];
  } catch { return null; }
}
function geocodeCacheSet(addr, val) {
  try {
    const c = JSON.parse(localStorage.getItem('geocode-cache') || '{}');
    c[addr] = val;
    localStorage.setItem('geocode-cache', JSON.stringify(c));
  } catch {}
}
async function geocode(addr) {
  const cached = geocodeCacheGet(addr);
  if (cached) return cached;
  try {
    const url = 'https://nominatim.openstreetmap.org/search?format=json&limit=1&countrycodes=fr&q=' + encodeURIComponent(addr);
    const r = await fetch(url, { headers: { 'Accept-Language': 'fr' } });
    const j = await r.json();
    if (j && j[0]) {
      const val = { lat: parseFloat(j[0].lat), lon: parseFloat(j[0].lon) };
      geocodeCacheSet(addr, val);
      return val;
    }
  } catch (e) { console.warn('[geocode] failed for', addr, e.message); }
  return null;
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms));

function PositionsMap({ rows, height = 220 }) {
  const ref = useRef(null);
  const [w, setW] = useState(400);
  const [franceGeo, setFranceGeo] = useState(null);
  const [points, setPoints] = useState(null); // { label, color, lat, lon, addr }[]

  useEffect(() => {
    const r = new ResizeObserver(es => setW(es[0].contentRect.width));
    if (ref.current) r.observe(ref.current);
    return () => r.disconnect();
  }, []);

  // Load world topology once, extract France (+ neighbors for context)
  useEffect(() => {
    fetch('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json')
      .then(r => r.json())
      .then(topo => {
        const countries = topojson.feature(topo, topo.objects.countries);
        const france = countries.features.find(f => f.properties.name === 'France');
        const neighbors = countries.features.filter(f =>
          ['Spain','Italy','Germany','Belgium','Switzerland','United Kingdom','Luxembourg','Andorra','Monaco'].includes(f.properties.name));
        setFranceGeo({ france, neighbors, all: countries });
      })
      .catch(e => console.warn('[map] topology load failed', e.message));
  }, []);

  // Latest known address per person within the given rows (selected period), fallback to most recent ever
  const latestAddr = useMemo(() => {
    const out = {};
    for (const p of PEOPLE) {
      for (let i = rows.length - 1; i >= 0; i--) {
        if (rows[i][p.key]) { out[p.key] = rows[i][p.key]; break; }
      }
    }
    return out;
  }, [rows]);

  // Geocode each unique address (cached)
  useEffect(() => {
    let cancelled = false;
    (async () => {
      const uniqueAddrs = [...new Set(Object.values(latestAddr).filter(Boolean))];
      const geocoded = {};
      for (const addr of uniqueAddrs) {
        const wasCached = !!geocodeCacheGet(addr);
        geocoded[addr] = await geocode(addr);
        if (!wasCached) await sleep(1100); // respect Nominatim's 1 req/sec usage policy
      }
      if (cancelled) return;
      const pts = PEOPLE.filter(p => latestAddr[p.key] && geocoded[latestAddr[p.key]]).map(p => ({
        label: p.label, color: p.color, addr: latestAddr[p.key],
        lat: geocoded[latestAddr[p.key]].lat, lon: geocoded[latestAddr[p.key]].lon,
      }));
      setPoints(pts);
    })();
    return () => { cancelled = true; };
  }, [latestAddr]);

  if (!franceGeo) {
    return <div className="pos-map-loading dim small">Chargement de la carte…</div>;
  }

  const W = w, H = height;
  let projection;
  if (points && points.length > 0) {
    let minLon = Infinity, maxLon = -Infinity, minLat = Infinity, maxLat = -Infinity;
    for (const p of points) {
      minLon = Math.min(minLon, p.lon); maxLon = Math.max(maxLon, p.lon);
      minLat = Math.min(minLat, p.lat); maxLat = Math.max(maxLat, p.lat);
    }
    const spanLon = maxLon - minLon, spanLat = maxLat - minLat;
    const padLon = Math.max(spanLon * 0.5, 0.1);
    const padLat = Math.max(spanLat * 0.5, 0.1);
    const bboxFeature = {
      type: 'Feature',
      geometry: { type: 'Polygon', coordinates: [[
        [minLon - padLon, minLat - padLat], [maxLon + padLon, minLat - padLat],
        [maxLon + padLon, maxLat + padLat], [minLon - padLon, maxLat + padLat],
        [minLon - padLon, minLat - padLat],
      ]] },
    };
    projection = d3.geoMercator().fitSize([W, H], bboxFeature);
  } else {
    projection = d3.geoMercator().fitSize([W, H], franceGeo.france || franceGeo.all);
  }
  const path = d3.geoPath(projection);

  return (
    <div ref={ref} className="pos-map">
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H}>
        {franceGeo.neighbors.map((f, i) => (
          <path key={'n'+i} d={path(f)} fill="var(--bg-elev)" stroke="var(--line)" strokeWidth=".5" />
        ))}
        {franceGeo.france && <path d={path(franceGeo.france)} fill="var(--bg-soft)" stroke="var(--green-deep)" strokeWidth="1" />}
        {points && points.map((p, i) => {
          const [x, y] = projection([p.lon, p.lat]);
          return (
            <g key={i} transform={`translate(${x},${y})`}>
              <circle r="5" fill={p.color} stroke="var(--bg-card)" strokeWidth="1.5" />
              <circle r="9" fill="none" stroke={p.color} strokeOpacity=".4">
                <animate attributeName="r" values="5;12;5" dur="2.4s" repeatCount="indefinite" />
                <animate attributeName="opacity" values=".6;0;.6" dur="2.4s" repeatCount="indefinite" />
              </circle>
              <text x="10" y="4" fontSize="10" fontFamily="Geist Mono" fill="var(--ink)">{p.label}</text>
            </g>
          );
        })}
      </svg>
      {points && points.length === 0 && (
        <div className="pos-map-empty dim small">Aucune position connue sur la période</div>
      )}
    </div>
  );
}

window.PositionsMap = PositionsMap;
