import type { Encounter, ExportBundle, PlayerCharacter, UndoRedoState, } from "@initiative/domain"; import { rehydrateEncounter } from "./encounter-storage.js"; import { rehydrateCharacter } from "./player-character-storage.js"; function rehydrateStack(raw: unknown[]): Encounter[] { const result: Encounter[] = []; for (const entry of raw) { const rehydrated = rehydrateEncounter(entry); if (rehydrated !== null) { result.push(rehydrated); } } return result; } function rehydrateCharacters(raw: unknown[]): PlayerCharacter[] { const result: PlayerCharacter[] = []; for (const entry of raw) { const rehydrated = rehydrateCharacter(entry); if (rehydrated !== null) { result.push(rehydrated); } } return result; } export function validateImportBundle(data: unknown): ExportBundle | string { if (typeof data !== "object" || data === null || Array.isArray(data)) { return "Invalid file format"; } const obj = data as Record; if (typeof obj.version !== "number" || obj.version !== 1) { return "Invalid file format"; } if (typeof obj.exportedAt !== "string") { return "Invalid file format"; } if (!Array.isArray(obj.undoStack) || !Array.isArray(obj.redoStack)) { return "Invalid file format"; } if (!Array.isArray(obj.playerCharacters)) { return "Invalid file format"; } const encounter = rehydrateEncounter(obj.encounter); if (encounter === null) { return "Invalid encounter data"; } return { version: 1, exportedAt: obj.exportedAt, encounter, undoStack: rehydrateStack(obj.undoStack), redoStack: rehydrateStack(obj.redoStack), playerCharacters: rehydrateCharacters(obj.playerCharacters), }; } export function assembleExportBundle( encounter: Encounter, undoRedoState: UndoRedoState, playerCharacters: readonly PlayerCharacter[], includeHistory = true, ): ExportBundle { return { version: 1, exportedAt: new Date().toISOString(), encounter, undoStack: includeHistory ? undoRedoState.undoStack : [], redoStack: includeHistory ? undoRedoState.redoStack : [], playerCharacters: [...playerCharacters], }; } export function bundleToJson(bundle: ExportBundle): string { return JSON.stringify(bundle, null, 2); } export function triggerDownload(bundle: ExportBundle): void { const blob = new Blob([bundleToJson(bundle)], { type: "application/json" }); const url = URL.createObjectURL(blob); const date = new Date().toISOString().slice(0, 10); const filename = `initiative-export-${date}.json`; const anchor = document.createElement("a"); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); } export async function readImportFile( file: File, ): Promise { try { const text = await file.text(); const parsed: unknown = JSON.parse(text); return validateImportBundle(parsed); } catch { return "Invalid file format"; } }