// Client-side region-key data access. Fetches the two static JSON files served // from public/ and builds the lookups the region-level UI needs: // - idToKey(): feature id -> region key (to stamp each map feature + resolve owner) // - keyToName(): region key -> human name (to show region names in the dossier) // Each is fetched once and cached (Promise singleton) so WorldMap and NationPanel // share a single fetch. Same-origin relative URLs (proxy/Traefik safe) — never a // hardcoded origin. type GeoLike = { features?: Array<{ id?: string | number; properties?: { name?: string } }>; }; let idKeyPromise: Promise> | null = null; let keyNamePromise: Promise> | null = null; async function fetchJson(url: string): Promise { const r = await fetch(url); if (!r.ok) throw new Error(`${url} → HTTP ${r.status}`); return (await r.json()) as T; } /** feature id (from regions.geo.json) -> region key (from region-keys.json). */ export function idToKey(): Promise> { idKeyPromise ??= fetchJson>('/region-keys.json'); return idKeyPromise; } /** * region key -> human region name. Built by joining region-keys.json (id -> key) * with regions.geo.json (id -> properties.name). A key with no known name is * simply absent (callers fall back to the raw key). */ export function keyToName(): Promise> { keyNamePromise ??= Promise.all([idToKey(), fetchJson('/regions.geo.json')]).then( ([keys, geo]) => { const out: Record = {}; for (const f of geo.features ?? []) { const id = f.id == null ? '' : String(f.id); const key = keys[id]; const name = f.properties?.name; if (key && typeof name === 'string') out[key] = name; } return out; }, ); return keyNamePromise; }