import type { GeoapifyFeature } from './geoapify-types'; const CACHE_TTL_MS = 30 * 60 * 1000; const MAX_MEMORY_ENTRIES = 80; const MAX_SESSION_ENTRIES = 40; const MIN_REQUEST_INTERVAL_MS = 750; const SESSION_CACHE_KEY = 'shahi-geoapify-cache'; interface CacheEntry { features: GeoapifyFeature[]; cachedAt: number; } const memoryCache = new Map(); const inFlight = new Map>(); let lastNetworkRequestAt = 0; let sessionHydrated = false; export function buildGeoapifyCacheKey(query: string, lang: string): string { return `${lang}:${query.trim().toLowerCase()}`; } function isFresh(entry: CacheEntry): boolean { return Date.now() - entry.cachedAt < CACHE_TTL_MS; } function trimMemoryCache(): void { while (memoryCache.size > MAX_MEMORY_ENTRIES) { const oldestKey = memoryCache.keys().next().value; if (!oldestKey) break; memoryCache.delete(oldestKey); } } function hydrateFromSessionStorage(): void { if (sessionHydrated || typeof window === 'undefined') return; sessionHydrated = true; try { const raw = sessionStorage.getItem(SESSION_CACHE_KEY); if (!raw) return; const parsed = JSON.parse(raw) as Record; for (const [key, entry] of Object.entries(parsed)) { if (isFresh(entry)) { memoryCache.set(key, entry); } } } catch { // Ignore corrupt cache payloads } } function persistToSessionStorage(): void { if (typeof window === 'undefined') return; try { const payload: Record = {}; const entries = [...memoryCache.entries()] .filter(([, entry]) => isFresh(entry)) .slice(-MAX_SESSION_ENTRIES); for (const [key, entry] of entries) { payload[key] = entry; } sessionStorage.setItem(SESSION_CACHE_KEY, JSON.stringify(payload)); } catch { // sessionStorage may be full or unavailable } } export function getCachedGeoapifyFeatures( query: string, lang: string, ): GeoapifyFeature[] | null { hydrateFromSessionStorage(); const key = buildGeoapifyCacheKey(query, lang); const entry = memoryCache.get(key); if (!entry || !isFresh(entry)) { if (entry) memoryCache.delete(key); return null; } return entry.features; } export function setCachedGeoapifyFeatures( query: string, lang: string, features: GeoapifyFeature[], ): void { hydrateFromSessionStorage(); const key = buildGeoapifyCacheKey(query, lang); memoryCache.set(key, { features, cachedAt: Date.now() }); trimMemoryCache(); persistToSessionStorage(); } export function getInFlightGeoapifyRequest( query: string, lang: string, ): Promise | null { return inFlight.get(buildGeoapifyCacheKey(query, lang)) ?? null; } export function trackInFlightGeoapifyRequest( query: string, lang: string, request: Promise, ): Promise { const key = buildGeoapifyCacheKey(query, lang); inFlight.set(key, request); return request.finally(() => { if (inFlight.get(key) === request) { inFlight.delete(key); } }); } export async function waitForGeoapifyRateLimit(): Promise { const elapsed = Date.now() - lastNetworkRequestAt; const waitMs = MIN_REQUEST_INTERVAL_MS - elapsed; if (waitMs > 0) { await new Promise((resolve) => setTimeout(resolve, waitMs)); } lastNetworkRequestAt = Date.now(); }