import type { PlayerCharacter } from "./player-character-types.js"; import { playerCharacterId, VALID_PLAYER_COLORS, VALID_PLAYER_ICONS, } from "./player-character-types.js"; function isValidOptionalMember( value: unknown, valid: ReadonlySet, ): boolean { return value === undefined || (typeof value === "string" && valid.has(value)); } export function rehydratePlayerCharacter(raw: unknown): PlayerCharacter | null { if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null; const entry = raw as Record; if (typeof entry.id !== "string" || entry.id.length === 0) return null; if (typeof entry.name !== "string" || entry.name.trim().length === 0) return null; if ( typeof entry.ac !== "number" || !Number.isInteger(entry.ac) || entry.ac < 0 ) return null; if ( typeof entry.maxHp !== "number" || !Number.isInteger(entry.maxHp) || entry.maxHp < 1 ) return null; if (!isValidOptionalMember(entry.color, VALID_PLAYER_COLORS)) return null; if (!isValidOptionalMember(entry.icon, VALID_PLAYER_ICONS)) return null; if ( entry.level !== undefined && (typeof entry.level !== "number" || !Number.isInteger(entry.level) || entry.level < 1 || entry.level > 20) ) return null; return { id: playerCharacterId(entry.id), name: entry.name, ac: entry.ac, maxHp: entry.maxHp, color: entry.color as PlayerCharacter["color"], icon: entry.icon as PlayerCharacter["icon"], level: entry.level, }; }