MAP_HTML = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>geo serve</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
  body { margin: 0; font-family: monospace; }
  #map { width: 100vw; height: 100vh; }
  #status {
    position: fixed; bottom: 8px; left: 8px; z-index: 1000;
    background: rgba(0,0,0,0.7); color: #0f0; padding: 4px 10px;
    border-radius: 4px; font-size: 12px;
  }
</style>
</head>
<body>
<div id="map"></div>
<div id="status">loading...</div>
<script>
const map = L.map('map');
const status = document.getElementById('status');

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; OSM contributors',
  maxZoom: 19
}).addTo(map);

let layer = null;

fetch('/api/bounds')
  .then(r => r.json())
  .then(data => {
    map.fitBounds([
      [data.bounds[1], data.bounds[0]],
      [data.bounds[3], data.bounds[2]]
    ]);
    loadFeatures();
  });

function loadFeatures() {
  const b = map.getBounds();
  const bbox = [b.getWest(), b.getSouth(), b.getEast(), b.getNorth()].join(',');
  status.textContent = 'loading...';
  fetch('/api/features?bbox=' + bbox)
    .then(r => r.json())
    .then(geojson => {
      if (layer) map.removeLayer(layer);
      layer = L.geoJSON(geojson, {
        style: { color: '#0066ff', weight: 2, fillOpacity: 0.15 },
        pointToLayer: function(f, ll) {
          return L.circleMarker(ll, { radius: 5, color: '#0066ff', fillOpacity: 0.6 });
        },
        onEachFeature: function(f, l) {
          if (f.properties) {
            const rows = Object.entries(f.properties)
              .map(([k,v]) => '<tr><td><b>'+k+'</b></td><td>'+v+'</td></tr>')
              .join('');
            l.bindPopup('<table>'+rows+'</table>');
          }
        }
      }).addTo(map);
      const n = geojson.features.length;
      const t = geojson.truncated ? ' (truncated)' : '';
      status.textContent = n + ' features' + t;
    });
}

map.on('moveend', loadFeatures);
</script>
</body>
</html>
"""
