import type { CardListPreferences, CardListSortKey, CardSkillLevel, CollectionCardState, SortDirection, UserData } from '$lib/models/user-data'; import { createUserData } from '$lib/models/user-data'; export const USER_DATA_STORAGE_KEY = 'holodori:user-data:v1'; function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function stringArray(value: unknown, fallback: string[]): string[] { return Array.isArray(value) && value.every((item) => typeof item === 'string') ? [...value] : [...fallback]; } function finiteNumber(value: unknown, fallback = 0): number { return typeof value === 'number' && Number.isFinite(value) ? value : fallback; } function cardListSortKey(value: unknown, fallback: CardListSortKey | null): CardListSortKey | null { return value === 'coolTime' || value === 'probability' || value === 'duration' || value === 'effect' ? value : fallback; } function sortDirection(value: unknown, fallback: SortDirection): SortDirection { return value === 'asc' || value === 'desc' ? value : fallback; } function cardSkillLevel(value: unknown, fallback: CardSkillLevel): CardSkillLevel { return value === 1 || value === 2 ? value : fallback; } function parseCollection(value: Record): UserData['collection'] { return Object.fromEntries( Object.entries(value).flatMap(([cardId, state]) => { if (!isRecord(state)) return []; const parsed: CollectionCardState = { owned: state.owned === true, awakening: Math.max(0, Math.min(5, Math.round(finiteNumber(state.awakening)))) }; return [[cardId, parsed]]; }) ); } function parseMemberBoards(value: unknown): UserData['memberBoards'] { if (!isRecord(value)) return {}; return Object.fromEntries( Object.entries(value).filter((entry): entry is [string, string[]] => Array.isArray(entry[1]) && entry[1].every((nodeId) => typeof nodeId === 'string') ) ); } function parseBoardCards(value: unknown): UserData['boardCards'] { if (!isRecord(value)) return {}; return Object.fromEntries(Object.entries(value).flatMap(([memberId, placements]) => { if (!isRecord(placements)) return []; return [[memberId, Object.fromEntries( Object.entries(placements).filter((entry): entry is [string, string] => typeof entry[1] === 'string') )]]; })); } export function parseUserData(text: string, fallbackPreferences: CardListPreferences): UserData { const value: unknown = JSON.parse(text); if (!isRecord(value) || value.schemaVersion !== 1) { throw new Error('지원하지 않는 백업 파일입니다.'); } if (!isRecord(value.preferences) || !isRecord(value.collection) || !Array.isArray(value.savedDecks)) { throw new Error('올바른 홀로도리 백업 파일이 아닙니다.'); } const preferences = value.preferences; const defaults = createUserData(fallbackPreferences); return { schemaVersion: 1, updatedAt: typeof value.updatedAt === 'string' ? value.updatedAt : defaults.updatedAt, memoryCount: Math.max(0, Math.round(finiteNumber(value.memoryCount))), memberPowerUpPercent: Math.max(0, Math.min(50, finiteNumber(value.memberPowerUpPercent))), collection: parseCollection(value.collection), memberBoards: parseMemberBoards(value.memberBoards), boardCards: parseBoardCards(value.boardCards), savedDecks: value.savedDecks as UserData['savedDecks'], preferences: { types: stringArray(preferences.types, fallbackPreferences.types), stats: stringArray(preferences.stats, fallbackPreferences.stats), activeConditions: stringArray(preferences.activeConditions, fallbackPreferences.activeConditions), specialConditions: stringArray(preferences.specialConditions, fallbackPreferences.specialConditions), activeProbabilities: stringArray(preferences.activeProbabilities, fallbackPreferences.activeProbabilities), cardVariants: stringArray(preferences.cardVariants, fallbackPreferences.cardVariants), generations: stringArray(preferences.generations, fallbackPreferences.generations), sortKey: cardListSortKey(preferences.sortKey, fallbackPreferences.sortKey), sortDirection: sortDirection(preferences.sortDirection, fallbackPreferences.sortDirection), skillLevel: cardSkillLevel(preferences.skillLevel, fallbackPreferences.skillLevel) } }; } export function loadBrowserData(fallbackPreferences: CardListPreferences): UserData { const stored = localStorage.getItem(USER_DATA_STORAGE_KEY); return stored ? parseUserData(stored, fallbackPreferences) : createUserData(fallbackPreferences); } export function saveBrowserData(data: UserData): UserData { const saved = { ...data, updatedAt: new Date().toISOString() } satisfies UserData; localStorage.setItem(USER_DATA_STORAGE_KEY, JSON.stringify(saved)); return saved; } export function clearBrowserData(): void { localStorage.removeItem(USER_DATA_STORAGE_KEY); }