import type { ThemeMode } from './types'; export const PREFERENCES_KEY = '@add-ideas/toolbox-portal:v1:preferences'; export interface Preferences { version: 1; pinned: string[]; order: string[]; hidden: string[]; theme: ThemeMode; } export const defaultPreferences: Preferences = { version: 1, pinned: [], order: [], hidden: [], theme: 'system', }; function uniqueStrings(value: unknown, field: string): string[] { if ( !Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0) ) throw new Error(`${field} must be an array of non-empty strings.`); return [...new Set(value)]; } export function parsePreferences(value: unknown): Preferences { if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw new Error('Preferences must be a JSON object.'); } const candidate = value as Record; if (candidate.version !== 1) throw new Error('Unsupported preferences version.'); const theme = candidate.theme; if (!['light', 'dark', 'system'].includes(String(theme))) throw new Error('Invalid theme preference.'); return { version: 1, pinned: uniqueStrings(candidate.pinned, 'pinned'), order: uniqueStrings(candidate.order, 'order'), hidden: uniqueStrings(candidate.hidden, 'hidden'), theme: theme as ThemeMode, }; } export function readPreferences(storage: Storage = localStorage): Preferences { const value = storage.getItem(PREFERENCES_KEY); if (!value) return { ...defaultPreferences }; try { return parsePreferences(JSON.parse(value)); } catch { return { ...defaultPreferences }; } } export function writePreferences( preferences: Preferences, storage: Storage = localStorage ): void { storage.setItem(PREFERENCES_KEY, JSON.stringify(preferences)); } export function cleanPreferences( preferences: Preferences, appIds: string[] ): Preferences { const known = new Set(appIds); return { ...preferences, pinned: preferences.pinned.filter((id) => known.has(id)), hidden: preferences.hidden.filter((id) => known.has(id)), order: [ ...preferences.order.filter((id) => known.has(id)), ...appIds.filter((id) => !preferences.order.includes(id)), ], }; } export function sortAppIds( appIds: string[], preferences: Preferences ): string[] { const order = new Map(preferences.order.map((id, index) => [id, index])); const pinned = new Set(preferences.pinned); return [...appIds].sort((left, right) => { const pinDifference = Number(pinned.has(right)) - Number(pinned.has(left)); if (pinDifference) return pinDifference; return ( (order.get(left) ?? Number.MAX_SAFE_INTEGER) - (order.get(right) ?? Number.MAX_SAFE_INTEGER) ); }); } export function moveId( order: string[], id: string, direction: -1 | 1 ): string[] { const current = order.indexOf(id); if (current < 0) return order; const target = current + direction; if (target < 0 || target >= order.length) return order; const next = [...order]; [next[current], next[target]] = [next[target]!, next[current]!]; return next; }