/**
 * Generates a stippled/dotted world map from Natural Earth data.
 *
 * Outputs:
 * - Dots: grid of land points grouped by region
 * - Hit paths: merged region outlines for invisible hover hit areas
 *
 * Run: node scripts/generate-map-paths.mjs
 */

import { readFileSync, writeFileSync } from "fs";
import { geoNaturalEarth1, geoContains, geoPath } from "d3-geo";
import { feature, merge } from "topojson-client";

const world = JSON.parse(
  readFileSync("node_modules/world-atlas/countries-110m.json", "utf8"),
);

const countriesGeo = feature(world, world.objects.countries);
const countries = world.objects.countries;

// ISO 3166-1 numeric → region mapping
const countryToRegion = {
  // North America
  124: "north_america", 840: "north_america", 484: "north_america",
  // South America
  32: "south_america", 68: "south_america", 76: "south_america",
  152: "south_america", 170: "south_america", 218: "south_america",
  328: "south_america", 600: "south_america", 604: "south_america",
  740: "south_america", 858: "south_america", 862: "south_america",
  // Europe
  40: "europe", 56: "europe", 100: "europe", 191: "europe", 196: "europe",
  203: "europe", 208: "europe", 233: "europe", 246: "europe", 250: "europe",
  276: "europe", 300: "europe", 348: "europe", 372: "europe", 380: "europe",
  428: "europe", 440: "europe", 442: "europe", 528: "europe", 616: "europe",
  620: "europe", 642: "europe", 703: "europe", 705: "europe", 724: "europe",
  752: "europe", 826: "europe", 578: "europe", 756: "europe",
  8: "europe", 70: "europe", 499: "europe", 807: "europe", 688: "europe",
  // Asia-Pacific
  36: "asia_pacific", 156: "asia_pacific", 356: "asia_pacific",
  392: "asia_pacific", 410: "asia_pacific", 702: "asia_pacific",
  458: "asia_pacific", 360: "asia_pacific", 764: "asia_pacific",
  704: "asia_pacific", 608: "asia_pacific", 554: "asia_pacific",
  496: "asia_pacific", 408: "asia_pacific", 104: "asia_pacific",
  116: "asia_pacific", 418: "asia_pacific", 524: "asia_pacific",
  50: "asia_pacific", 586: "asia_pacific", 144: "asia_pacific",
  643: "asia_pacific", 398: "asia_pacific", 860: "asia_pacific",
  795: "asia_pacific", 417: "asia_pacific", 762: "asia_pacific",
  598: "asia_pacific", 158: "asia_pacific",
  // Middle East
  682: "middle_east", 784: "middle_east", 48: "middle_east",
  414: "middle_east", 512: "middle_east", 634: "middle_east",
  368: "middle_east", 364: "middle_east", 376: "middle_east",
  400: "middle_east", 422: "middle_east", 760: "middle_east",
  792: "middle_east", 887: "middle_east", 275: "middle_east",
  // Africa
  12: "africa", 24: "africa", 204: "africa", 72: "africa", 854: "africa",
  108: "africa", 120: "africa", 140: "africa", 148: "africa", 178: "africa",
  180: "africa", 384: "africa", 262: "africa", 818: "africa", 226: "africa",
  232: "africa", 748: "africa", 231: "africa", 266: "africa", 270: "africa",
  288: "africa", 324: "africa", 624: "africa", 404: "africa", 426: "africa",
  430: "africa", 434: "africa", 450: "africa", 454: "africa", 466: "africa",
  478: "africa", 508: "africa", 516: "africa", 562: "africa", 566: "africa",
  646: "africa", 694: "africa", 706: "africa", 710: "africa", 728: "africa",
  729: "africa", 834: "africa", 768: "africa", 788: "africa", 800: "africa",
  732: "africa", 894: "africa", 716: "africa", 686: "africa", 504: "africa",
};

const projection = geoNaturalEarth1()
  .scale(185)
  .translate([960 / 2, 500 / 2])
  .rotate([15, 0]);

const pathGenerator = geoPath(projection);

const GRID_SPACING = 5;
const DOT_RADIUS = 1.3;

const countryFeatures = countriesGeo.features.map((f) => ({
  feature: f,
  region: countryToRegion[Number(f.id)] ?? null,
}));

const regionOrder = [
  "north_america",
  "south_america",
  "europe",
  "africa",
  "middle_east",
  "asia_pacific",
];

// --- Generate dots ---
// Clip bounds: ignore dots from date-line wraparound (x < 120)
const CLIP_LEFT = 120;
const CLIP_RIGHT = 960;

const regionDots = {};
for (const id of regionOrder) regionDots[id] = [];

let totalDots = 0;
for (let x = 0; x < 960; x += GRID_SPACING) {
  for (let y = 0; y < 500; y += GRID_SPACING) {
    if (x < CLIP_LEFT) continue;
    const lonLat = projection.invert([x, y]);
    if (!lonLat) continue;
    for (const { feature: feat, region } of countryFeatures) {
      if (!region) continue;
      if (geoContains(feat, lonLat)) {
        regionDots[region].push({ x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 });
        totalDots++;
        break;
      }
    }
  }
}

// --- Generate hit-area paths (merged region outlines) ---
const regionGeometries = {};
for (const geom of countries.geometries) {
  const region = countryToRegion[Number(geom.id)];
  if (region) {
    if (!regionGeometries[region]) regionGeometries[region] = [];
    regionGeometries[region].push(geom);
  }
}

const regionHitPaths = {};
for (const regionId of regionOrder) {
  const geoms = regionGeometries[regionId];
  if (!geoms || geoms.length === 0) continue;
  const merged = merge(world, geoms);
  const d = pathGenerator(merged);
  // Round to 1 decimal
  regionHitPaths[regionId] = d.replace(/(\d+\.\d+)/g, (m) => Math.round(parseFloat(m)).toString());
}

// --- Compute tight viewBox ---
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
for (const dots of Object.values(regionDots)) {
  for (const d of dots) {
    if (d.x < minX) minX = d.x;
    if (d.x > maxX) maxX = d.x;
    if (d.y < minY) minY = d.y;
    if (d.y > maxY) maxY = d.y;
  }
}
const PAD = 15;
const vbX = Math.floor(minX - PAD);
const vbY = Math.floor(minY - PAD);
const vbW = Math.ceil(maxX - minX + PAD * 2);
const vbH = Math.ceil(maxY - minY + PAD * 2);
const viewBox = `${vbX} ${vbY} ${vbW} ${vbH}`;

console.log(`Total land dots: ${totalDots}`);
console.log(`Dot bounds: x=[${minX}, ${maxX}] y=[${minY}, ${maxY}]`);
console.log(`ViewBox: ${viewBox}`);
for (const [region, dots] of Object.entries(regionDots)) {
  console.log(`  ${region}: ${dots.length} dots, hit path: ${regionHitPaths[region]?.length ?? 0} chars`);
}

const output = {
  dots: regionDots,
  hitPaths: regionHitPaths,
  dotRadius: DOT_RADIUS,
  viewBox,
};
writeFileSync("scripts/map-dots.json", JSON.stringify(output));
console.log("\nWrote scripts/map-dots.json");
