// GAZETTEER · places kept. Uses Natural Earth 110m land data rendered via equirectangular.
// Loads D3 + topojson + world-atlas from CDN and projects it in-browser.

const GAZ_LON0 = -12, GAZ_LON1 = 52;
const GAZ_LAT0 = 60,  GAZ_LAT1 = 18;
const GAZ_W = 1000;
const GAZ_H = Math.round(((GAZ_LAT0 - GAZ_LAT1) / (GAZ_LON1 - GAZ_LON0)) * GAZ_W);

function gazProj(lat, lon) {
  return {
    x: ((lon - GAZ_LON0) / (GAZ_LON1 - GAZ_LON0)) * GAZ_W,
    y: ((GAZ_LAT0 - lat) / (GAZ_LAT0 - GAZ_LAT1)) * GAZ_H,
  };
}

function loadScript(src) {
  return new Promise((res, rej) => {
    if (document.querySelector(`script[data-gaz-src="${src}"]`)) return res();
    const s = document.createElement('script');
    s.src = src;
    s.async = true;
    s.dataset.gazSrc = src;
    s.onload = () => res();
    s.onerror = rej;
    document.head.appendChild(s);
  });
}

async function loadMapLibs() {
  await loadScript('https://cdn.jsdelivr.net/npm/d3-geo@3.1.0/dist/d3-geo.min.js');
  await loadScript('https://cdn.jsdelivr.net/npm/topojson-client@3.1.0/dist/topojson-client.min.js');
  const res = await fetch('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/land-110m.json');
  if (!res.ok) throw new Error('world-atlas load failed');
  return res.json();
}

function GazetteerMapSVG() {
  const [geo, setGeo] = React.useState(null);
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    let alive = true;
    loadMapLibs()
      .then(topo => {
        if (!alive) return;
        const fc = window.topojson.feature(topo, topo.objects.land);
        // FeatureCollection → iterate all features, each with Polygon or MultiPolygon geometry
        const features = fc.features || [fc];
        // Custom equirectangular projector matching our extent
        const project = ([lon, lat]) => {
          const x = ((lon - GAZ_LON0) / (GAZ_LON1 - GAZ_LON0)) * GAZ_W;
          const y = ((GAZ_LAT0 - lat) / (GAZ_LAT0 - GAZ_LAT1)) * GAZ_H;
          return [x, y];
        };
        // Build SVG path from GeoJSON MultiPolygon/Polygon
        const pathFor = (feats) => {
          let d = '';
          for (const f of feats) {
            const geom = f.geometry;
            if (!geom) continue;
            const polygons = geom.type === 'Polygon' ? [geom.coordinates] : geom.coordinates;
            for (const poly of polygons) {
              for (const ring of poly) {
                // Cheap viewport test on this ring
                let insideAny = false;
                for (const [lon, lat] of ring) {
                  if (lon > GAZ_LON0 - 20 && lon < GAZ_LON1 + 20 && lat > GAZ_LAT1 - 10 && lat < GAZ_LAT0 + 10) {
                    insideAny = true;
                    break;
                  }
                }
                if (!insideAny) continue;
                ring.forEach((pt, i) => {
                  const [x, y] = project(pt);
                  d += (i === 0 ? 'M' : 'L') + x.toFixed(1) + ',' + y.toFixed(1);
                });
                d += 'Z';
              }
            }
          }
          return d;
        };
        setGeo(pathFor(features));
      })
      .catch(e => setErr(String(e)));
    return () => { alive = false; };
  }, []);

  if (err) {
    return <text x={GAZ_W/2} y={GAZ_H/2} textAnchor="middle" fill="#141110" fontSize="14" fontFamily="JetBrains Mono, monospace" opacity=".5">MAP DATA FAILED TO LOAD</text>;
  }
  if (!geo) {
    return <text x={GAZ_W/2} y={GAZ_H/2} textAnchor="middle" fill="#141110" fontSize="14" fontFamily="JetBrains Mono, monospace" opacity=".5">⋯ drawing coastlines ⋯</text>;
  }
  return (
    <>
      <path d={geo} fill="#141110" fillOpacity=".18"/>
      <path d={geo} fill="none" stroke="#141110" strokeWidth="1.1" strokeLinejoin="round" strokeLinecap="round" opacity=".85"/>
    </>
  );
}

const GAZ_SEAS = [
  { t: 'MEDITERRANEAN SEA', lat: 35.0, lon: 19.0, size: 15, italic: true },
  { t: 'NORTH SEA',         lat: 56.5, lon:  3.0, size: 12, italic: true },
  { t: 'BALTIC SEA',        lat: 58.5, lon: 20.0, size: 11, italic: true },
  { t: 'ATLANTIC',          lat: 45.0, lon: -8.5, size: 14, italic: true },
  { t: 'BLACK SEA',         lat: 43.4, lon: 34.0, size: 12, italic: true },
  { t: 'RED SEA',           lat: 22.0, lon: 36.8, size: 10, italic: true },
  { t: 'ARABIAN SEA',       lat: 21.0, lon: 51.0, size: 11, italic: true },
  { t: 'SAHARA',            lat: 25.0, lon: 15.0, size: 18 },
  { t: 'ARABIAN PENINSULA', lat: 23.0, lon: 45.0, size: 10 },
];

function Gazetteer() {
  const raw = [
    { id: 'ainbessem', name: 'Aïn Bessem',  sub: 'born',                  lat: 36.28, lon:  3.68, red: true,  year: '2002', nudge: { dx: -18, dy:  22 }, align: 'right' },
    { id: 'bouira',    name: 'Bouira',      sub: 'studied · B.Sc.',       lat: 36.35, lon:  3.90, red: false, year: '2021', nudge: { dx:  18, dy: -14 }, align: 'left'  },
    { id: 'pozn',      name: 'Poznań',      sub: 'stationed · writing',   lat: 52.41, lon: 16.92, red: true,  year: '2024', nudge: { dx:  18, dy:  18 }, align: 'left'  },
    { id: 'frankfurt', name: 'Frankfurt',   sub: 'aurologic · by proxy',  lat: 50.11, lon:  8.68, red: false, year: '2024', nudge: { dx: -18, dy:  18 }, align: 'right' },
    { id: 'jeddah',    name: 'Jeddah',      sub: 'CyberFilters · remote', lat: 21.49, lon: 39.19, red: false, year: '2023', nudge: { dx:  18, dy:  18 }, align: 'left'  },
  ];
  const places = raw.map(p => {
    const s = gazProj(p.lat, p.lon);
    return { ...p, sx: s.x, sy: s.y };
  });

  return (
    <section id="gazetteer" className="page" style={{ paddingTop: 60, paddingBottom: 60 }}>
      <hr className="rule-thick" />
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', padding: '18px 0 6px'}}>
        <h2 className="display" style={{ fontSize: 'clamp(52px, 7vw, 110px)', lineHeight: .85 }}>
          Gazetteer<span style={{color:'var(--stamp)'}}>.</span>
        </h2>
        <div className="kicker">CHAPTER VII · MAP OF PLACES KEPT</div>
      </div>
      <hr className="rule" style={{marginTop: 8}}/>

      <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 36, marginTop: 30 }}>
        <div style={{ position:'relative', border:'1px solid var(--ink)', background:'var(--paper-shade)', padding: 14, aspectRatio: `${GAZ_W} / ${GAZ_H}` }}>
          <div className="mono gaz-plate-label" style={{ position:'absolute', top: 10, left: 16, fontSize: 10, letterSpacing:'.2em', color:'var(--ink-fade)', zIndex: 3 }}>
            PLATE VII-a · EMEA · EQUIRECTANGULAR · LON −12° → 52°
          </div>
          <div className="mono" style={{ position:'absolute', top: 10, right: 16, fontSize: 10, letterSpacing:'.2em', color:'var(--ink-fade)', zIndex: 3 }}>N ↑</div>

          <div style={{ position:'absolute', inset: 14 }}>
            <svg viewBox={`0 0 ${GAZ_W} ${GAZ_H}`} preserveAspectRatio="xMidYMid meet" style={{ width:'100%', height:'100%', display:'block' }}>
              <defs>
                <pattern id="gaz-sea" patternUnits="userSpaceOnUse" width="14" height="14" patternTransform="rotate(45)">
                  <line x1="0" y1="0" x2="0" y2="14" stroke="#141110" strokeWidth=".6" opacity=".08"/>
                </pattern>
                <clipPath id="gaz-clip">
                  <rect x="0" y="0" width={GAZ_W} height={GAZ_H}/>
                </clipPath>
              </defs>

              <rect x="0" y="0" width={GAZ_W} height={GAZ_H} fill="url(#gaz-sea)"/>

              {/* graticule */}
              {[-10, 0, 10, 20, 30, 40, 50].map(lon => {
                const { x } = gazProj(0, lon);
                return <line key={'v'+lon} x1={x} y1={0} x2={x} y2={GAZ_H} stroke="#141110" strokeWidth=".6" opacity=".18"/>;
              })}
              {[20, 30, 40, 50].map(lat => {
                const { y } = gazProj(lat, 0);
                return <line key={'h'+lat} x1={0} y1={y} x2={GAZ_W} y2={y} stroke="#141110" strokeWidth=".6" opacity=".18"/>;
              })}

              <g clipPath="url(#gaz-clip)">
                <GazetteerMapSVG />
              </g>

              {/* sea + region labels */}
              {GAZ_SEAS.map(s => {
                const { x, y } = gazProj(s.lat, s.lon);
                return (
                  <text key={s.t} x={x} y={y} fontFamily="'JetBrains Mono', monospace"
                        fontSize={s.size} textAnchor="middle"
                        letterSpacing="2.5" fill="#141110" opacity=".5"
                        fontStyle={s.italic ? 'italic' : 'normal'}>{s.t}</text>
                );
              })}

              {/* routes */}
              {places.map((p, i) => i < places.length - 1 && (
                <line key={'r'+i} x1={p.sx} y1={p.sy} x2={places[i+1].sx} y2={places[i+1].sy}
                  stroke="#c0392b" strokeWidth="2.2" strokeDasharray="7 6" opacity=".72"/>
              ))}
            </svg>

            {/* markers + labels (DOM) */}
            {places.map(p => {
              const dotL = `${(p.sx / GAZ_W) * 100}%`;
              const dotT = `${(p.sy / GAZ_H) * 100}%`;
              const labL = `calc(${(p.sx / GAZ_W) * 100}% + ${p.nudge.dx}px)`;
              const labT = `calc(${(p.sy / GAZ_H) * 100}% + ${p.nudge.dy}px)`;
              return (
                <React.Fragment key={p.id}>
                  <svg style={{ position:'absolute', inset:0, width:'100%', height:'100%', pointerEvents:'none', zIndex: 2, overflow:'visible' }}>
                    <line
                      x1={dotL} y1={dotT} x2={labL} y2={labT}
                      stroke={p.red ? '#c0392b' : '#141110'} strokeWidth={p.red ? 1.2 : 0.9} opacity=".55"
                    />
                  </svg>
                  <div style={{ position:'absolute', left: dotL, top: dotT, transform:'translate(-50%,-50%)', zIndex: 4 }}>
                    <div style={{
                      width: p.red ? 13 : 9, height: p.red ? 13 : 9,
                      background: p.red ? 'var(--stamp)' : 'var(--ink)',
                      borderRadius: '50%',
                      boxShadow: p.red ? '0 0 0 3px rgba(192,57,43,.22)' : 'none',
                      border: '1.5px solid var(--paper)',
                    }}/>
                  </div>
                  <div title={`${p.name} · ${p.lat.toFixed(2)}°N · ${p.lon.toFixed(2)}°E`}
                       style={{
                         position:'absolute', left: labL, top: labT,
                         transform: p.align === 'right' ? 'translate(-100%,-50%)' : 'translate(0,-50%)',
                         zIndex: 5,
                       }}>
                    <div className="mono" style={{
                      fontSize: 10, letterSpacing:'.15em', whiteSpace:'nowrap',
                      background: 'var(--paper)', padding: '2px 6px',
                      color: p.red ? 'var(--stamp)' : 'var(--ink)',
                      fontWeight: p.red ? 700 : 500,
                      border: '.5px solid var(--ink)',
                    }}>
                      {p.name.toUpperCase()}
                    </div>
                  </div>
                </React.Fragment>
              );
            })}
          </div>

          {/* corners */}
          <span className="crop-mark" style={{ top: 4, left: 4 }}/>
          <span className="crop-mark" style={{ top: 4, right: 4 }}/>
          <span className="crop-mark" style={{ bottom: 4, left: 4 }}/>
          <span className="crop-mark" style={{ bottom: 4, right: 4 }}/>

          {/* scale */}
          <div className="mono" style={{ position:'absolute', bottom: 14, left: 22, fontSize: 10, letterSpacing:'.15em', color:'var(--ink-fade)', display:'flex', alignItems:'center', gap: 8, zIndex: 2 }}>
            <span style={{ display:'inline-block', width: 70, height: 3, background:'var(--ink)' }}/>
            ~ 1,000 km (at 40° N)
          </div>
        </div>

        <div>
          <div className="kicker" style={{ marginBottom: 10 }}>INDEX OF LOCATIONS</div>
          <div style={{ border:'1px solid var(--ink)' }}>
            {places.map((p, i) => (
              <div key={p.id} className="lift gaz-index-row" style={{
                display:'grid', gridTemplateColumns:'46px 1fr 90px', gap: 12, alignItems:'center',
                padding:'14px 16px',
                background: i % 2 === 0 ? 'var(--paper)' : 'var(--paper-shade)',
                borderBottom: i < places.length - 1 ? '1px solid var(--ink)' : 0,
              }}>
                <span className="didone" style={{ fontSize: 30, color: p.red ? 'var(--stamp)' : 'var(--ink)' }}>
                  {p.red ? '●' : '○'}
                </span>
                <div>
                  <div className="didone" style={{ fontSize: 22, lineHeight: 1 }}>{p.name}</div>
                  <div className="mono" style={{ fontSize: 10, letterSpacing:'.2em', color:'var(--ink-fade)', marginTop: 4 }}>
                    {p.sub.toUpperCase()} · {p.lat.toFixed(2)}°N · {p.lon.toFixed(2)}°E
                  </div>
                </div>
                <div className="didone" style={{ fontSize: 22, textAlign:'right' }}>{p.year}</div>
              </div>
            ))}
          </div>
          <div className="mono" style={{ marginTop: 14, fontSize: 11, letterSpacing:'.15em', color:'var(--ink-fade)' }}>
            ● marks present residence. Red threads show the order of posts, not any single flight.
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { Gazetteer });
