'use client'; import { useCallback, useEffect, useState } from 'react'; /** * Compare tray — the list of entity slugs the visitor is collecting for `/compare?ids=`. * Source of truth: `localStorage['aia-compare']` (mirrored across tabs through the `storage` event and a same-tab * custom event). The tray is type-homogeneous: the first item fixes the entity type; adding another type replaces * the tray (the API compares 2–6 entities of one type). */ export const COMPARE_KEY = 'aia-compare'; export const COMPARE_MAX = 6; export const COMPARE_MIN = 2; const EVENT = 'aia-compare-change'; export type TrayItem = { slug: string; name: string; entity_type: string; organization?: string | null }; function read(): TrayItem[] { if (typeof window === 'undefined') return []; try { const raw = window.localStorage.getItem(COMPARE_KEY); if (!raw) return []; const arr = JSON.parse(raw) as unknown; if (!Array.isArray(arr)) return []; return arr.filter((x): x is TrayItem => !!x && typeof x === 'object' && typeof (x as TrayItem).slug === 'string' && typeof (x as TrayItem).entity_type === 'string').slice(0, COMPARE_MAX); } catch { return []; } } function write(items: TrayItem[]) { if (typeof window === 'undefined') return; try { window.localStorage.setItem(COMPARE_KEY, JSON.stringify(items.slice(0, COMPARE_MAX))); } catch { /* storage full / disabled */ } window.dispatchEvent(new CustomEvent(EVENT)); } /** Normalises the API's type aliases so companies/labs/orgs compare together (the API accepts them under `company`). */ export function trayType(entityType: string): string { if (['company', 'organization', 'lab', 'university'].includes(entityType)) return 'company'; if (['framework', 'library', 'runtime'].includes(entityType)) return 'framework'; if (entityType === 'quantization') return 'model'; return entityType; } export function readTray(): TrayItem[] { return read(); } export function setTray(items: TrayItem[]) { write(items); } export function clearTray() { write([]); } /** Add one item. Returns the new tray; a type change replaces the tray. */ export function addToTray(item: TrayItem): TrayItem[] { const cur = read(); const t = trayType(item.entity_type); const same = cur.filter((c) => trayType(c.entity_type) === t); if (same.some((c) => c.slug === item.slug)) return cur; const next = same.length === cur.length ? [...cur, item] : [item]; const capped = next.slice(0, COMPARE_MAX); write(capped); return capped; } export function removeFromTray(slug: string): TrayItem[] { const next = read().filter((c) => c.slug !== slug); write(next); return next; } export function toggleTray(item: TrayItem): TrayItem[] { return read().some((c) => c.slug === item.slug) ? removeFromTray(item.slug) : addToTray(item); } /** React binding: returns the tray and mutators, subscribed to storage changes. `ready` is false during SSR/hydration. */ export function useCompareTray() { const [items, setItems] = useState([]); const [ready, setReady] = useState(false); useEffect(() => { const sync = () => setItems(read()); sync(); setReady(true); window.addEventListener(EVENT, sync); window.addEventListener('storage', sync); return () => { window.removeEventListener(EVENT, sync); window.removeEventListener('storage', sync); }; }, []); const add = useCallback((item: TrayItem) => setItems(addToTray(item)), []); const remove = useCallback((slug: string) => setItems(removeFromTray(slug)), []); const toggle = useCallback((item: TrayItem) => setItems(toggleTray(item)), []); const clear = useCallback(() => { clearTray(); setItems([]); }, []); const replace = useCallback((next: TrayItem[]) => { write(next); setItems(next.slice(0, COMPARE_MAX)); }, []); const has = useCallback((slug: string) => items.some((c) => c.slug === slug), [items]); const type = items[0] ? trayType(items[0].entity_type) : null; return { items, ready, add, remove, toggle, clear, replace, has, type, full: items.length >= COMPARE_MAX, canCompare: items.length >= COMPARE_MIN }; } export function compareHref(items: { slug: string }[]): string { return items.length ? `/compare?ids=${items.map((i) => encodeURIComponent(i.slug)).join(',')}` : '/compare'; }