import { weekMonday } from '~/utils/toronto' const TTL_MS = 24 * 60 * 60 * 1000 interface CacheEntry { data: T ts: number } function storageKey(key: string) { return `cache:${key}` } export function useAppCache() { function set(key: string, data: T): void { try { localStorage.setItem(storageKey(key), JSON.stringify({ data, ts: Date.now() } satisfies CacheEntry)) } catch { /* quota exceeded or unavailable */ } } /** Returns data only if fresh (< 24 h). Returns null if stale or absent. */ function get(key: string): T | null { try { const raw = localStorage.getItem(storageKey(key)) if (!raw) return null const entry = JSON.parse(raw) as CacheEntry if (Date.now() - entry.ts > TTL_MS) return null return entry.data } catch { return null } } /** Returns data regardless of age — for offline fallback when cache is stale. */ function peek(key: string): T | null { try { const raw = localStorage.getItem(storageKey(key)) if (!raw) return null return (JSON.parse(raw) as CacheEntry).data } catch { return null } } /** Returns age in ms, or null if absent. */ function age(key: string): number | null { try { const raw = localStorage.getItem(storageKey(key)) if (!raw) return null return Date.now() - (JSON.parse(raw) as CacheEntry).ts } catch { return null } } function invalidate(key: string): void { localStorage.removeItem(storageKey(key)) } /** Removes all cache entries whose key starts with prefix. */ function invalidatePrefix(prefix: string): void { const full = storageKey(prefix) const toRemove: string[] = [] for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i) if (k?.startsWith(full)) toRemove.push(k) } toRemove.forEach(k => localStorage.removeItem(k)) } /** * Cache key for schedule data (intervals or slots) for the ISO week containing * the given UTC ISO timestamp. Desktop and mobile both key to week-Monday. */ function weekKey(utcIso: string): string { return weekMonday(utcIso.slice(0, 10)) } return { set, get, peek, age, invalidate, invalidatePrefix, weekKey } }