/**
 * Matching DB country names to the world map's country names.
 *
 * The map geometry is Natural Earth (via `world-atlas`), which abbreviates a
 * number of names — "W. Sahara", "Dem. Rep. Congo", "Bosnia and Herz.",
 * "United States of America". Our `countries` table stores its own spellings.
 * So a plain string compare would silently drop those countries from the map.
 *
 * Rather than hand-typing an ISO-numeric → alpha-2 table from memory (which
 * would be easy to get subtly wrong, and a wrong entry colours the wrong
 * country), this normalises both sides and keeps an explicit alias list built
 * against the 37 actual Natural Earth names that don't match a common spelling.
 *
 * Anything still unmatched is *reported*, not swallowed — see `matchCountries`.
 */

/**
 * Lowercase, strip punctuation/diacritics, collapse whitespace, and drop the
 * leading article. Handles "Côte d'Ivoire" vs "Cote dIvoire" and
 * "The Gambia" vs "Gambia" without needing an alias each.
 */
export function normaliseCountry(value: string): string {
  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "") // combining accents left by NFD
    .toLowerCase()
    .replace(/^the\s+/, "")
    .replace(/[.'’`]/g, "")
    .replace(/[-_/]/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

/**
 * Alternative spellings → the normalised Natural Earth name used by the map.
 * Keys are already normalised. Only entries that genuinely differ are listed.
 */
const ALIASES: Record<string, string> = {
  // Abbreviated on the map
  "western sahara": "w sahara",
  "bosnia and herzegovina": "bosnia and herz",
  "central african republic": "central african rep",
  "democratic republic of the congo": "dem rep congo",
  "dr congo": "dem rep congo",
  "congo kinshasa": "dem rep congo",
  "congo brazzaville": "congo",
  "republic of the congo": "congo",
  "dominican republic": "dominican rep",
  "equatorial guinea": "eq guinea",
  "falkland islands": "falkland is",
  "solomon islands": "solomon is",
  "south sudan": "s sudan",
  "northern cyprus": "n cyprus",
  "french southern and antarctic lands": "fr s antarctic lands",

  // Common short/long forms
  "united states": "united states of america",
  usa: "united states of america",
  us: "united states of america",
  "united states of america": "united states of america",
  uk: "united kingdom",
  "great britain": "united kingdom",
  "united kingdom of great britain and northern ireland": "united kingdom",
  uae: "united arab emirates",
  "russian federation": "russia",
  "republic of korea": "south korea",
  "korea republic of": "south korea",
  "korea south": "south korea",
  "democratic peoples republic of korea": "north korea",
  "korea north": "north korea",
  "viet nam": "vietnam",
  "lao peoples democratic republic": "laos",
  lao: "laos",
  "syrian arab republic": "syria",
  "iran islamic republic of": "iran",
  "republic of moldova": "moldova",
  "united republic of tanzania": "tanzania",
  "czech republic": "czechia",
  "north macedonia": "macedonia",
  "republic of north macedonia": "macedonia",
  eswatini: "eswatini",
  swaziland: "eswatini",
  burma: "myanmar",
  "east timor": "timor leste",
  "bolivia plurinational state of": "bolivia",
  "venezuela bolivarian republic of": "venezuela",
  "brunei darussalam": "brunei",
  "cote divoire": "cote divoire",
  "ivory coast": "cote divoire",
  "palestine state of": "palestine",
  "palestinian territory": "palestine",
  "state of palestine": "palestine",
  "cape verde": "cabo verde",
  "myanmar burma": "myanmar",
};

/** Resolves a DB country name to the normalised map name. */
export function toMapKey(dbName: string): string {
  const key = normaliseCountry(dbName);
  return ALIASES[key] ?? key;
}

export interface CountryMatchResult<T> {
  /** Normalised map name → the caller's datum. */
  matched: Map<string, T>;
  /** DB names that found no geometry, so the UI can say so out loud. */
  unmatched: string[];
}

/**
 * Joins caller rows to map geometry by name.
 *
 * `mapNames` is the set of normalised names present in the geometry, so a row
 * that resolves to something the map doesn't have is surfaced rather than
 * silently dropped — small territories legitimately aren't in the 110m file.
 */
export function matchCountries<T>(
  rows: Array<{ country: string; datum: T }>,
  mapNames: Set<string>,
): CountryMatchResult<T> {
  const matched = new Map<string, T>();
  const unmatched: string[] = [];

  for (const row of rows) {
    const key = toMapKey(row.country);
    if (mapNames.has(key)) matched.set(key, row.datum);
    else unmatched.push(row.country);
  }

  return { matched, unmatched };
}
