import { characterSchema, type Character } from '@/lib/schemas'; import { newId } from '@/lib/ids'; import { downloadJson, safeFilename } from './file'; import { isPathbuilder, pathbuilderToCharacterFields } from './pathbuilder'; const FORMAT = 'ttrpg-manager:character'; const VERSION = 1; interface CharacterFile { format: typeof FORMAT; version: number; character: Character; } /** Download a character as a portable JSON file. */ export function exportCharacter(c: Character): void { const payload: CharacterFile = { format: FORMAT, version: VERSION, character: c }; downloadJson(`${safeFilename(c.name)}.character.json`, payload); } export class CharacterImportError extends Error {} /** * Parse imported JSON into a valid Character bound to the given campaign. Accepts * both our wrapped export format and a bare character object. Always assigns a * fresh id + timestamps so importing never collides with or overwrites existing * data. */ export function parseCharacterImport(text: string, campaignId: string): Character { let raw: unknown; try { raw = JSON.parse(text); } catch { throw new CharacterImportError('That file is not valid JSON.'); } const now = new Date().toISOString(); // Pathbuilder 2e export → convert to our PF2e character shape. if (isPathbuilder(raw)) { const result = characterSchema.safeParse({ ...pathbuilderToCharacterFields(raw.build), id: newId(), campaignId, createdAt: now, updatedAt: now, }); if (!result.success) throw new CharacterImportError(`Pathbuilder import failed: ${result.error.issues[0]?.message ?? 'unknown error'}`); return result.data; } const candidate = raw && typeof raw === 'object' && 'character' in raw ? (raw as { character: unknown }).character : raw; if (!candidate || typeof candidate !== 'object') { throw new CharacterImportError('No character data found in that file.'); } const result = characterSchema.safeParse({ ...(candidate as Record), id: newId(), campaignId, createdAt: now, updatedAt: now, }); if (!result.success) { throw new CharacterImportError( `That file isn't a valid character: ${result.error.issues[0]?.message ?? 'unknown error'}`, ); } return result.data; }