const BOSS_CARD_RARITIES = ["normal", "rare", "epic", "legendary"]; export function emptyBossCardCollection() { return { counts: {}, lastClaims: {} }; } /** Keep this normalization in step with src/game/bossCards.ts. */ export function normalizeBossCardCollection(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return emptyBossCardCollection(); const raw = value; const counts = Object.create(null); if (raw.counts && typeof raw.counts === "object" && !Array.isArray(raw.counts)) { for (const [key, candidate] of Object.entries(raw.counts)) { if (!key || !candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue; const entry = {}; for (const rarity of BOSS_CARD_RARITIES) { const count = candidate[rarity]; if (typeof count === "number" && Number.isFinite(count) && count >= 1) { entry[rarity] = Math.min(Number.MAX_SAFE_INTEGER, Math.floor(count)); } } if (Object.keys(entry).length) counts[key] = entry; } } const lastClaims = raw.lastClaims && typeof raw.lastClaims === "object" && !Array.isArray(raw.lastClaims) ? Object.fromEntries(Object.entries(raw.lastClaims).filter(([key, claim]) => key && typeof claim === "string")) : {}; return { counts, lastClaims }; } /** Max-reconcile copies of one account collection, matching source mergeBossCardCollections(..., "max"). */ export function mergeBossCardCollections(collections) { const counts = Object.create(null); const lastClaims = Object.create(null); for (const value of collections) { const collection = normalizeBossCardCollection(value); for (const [bossKey, entry] of Object.entries(collection.counts)) { const merged = counts[bossKey] ??= {}; for (const rarity of BOSS_CARD_RARITIES) { const count = entry[rarity]; if (count === undefined) continue; merged[rarity] = Math.max(merged[rarity] ?? 0, count); } } for (const [spawnId, claim] of Object.entries(collection.lastClaims)) { const previous = lastClaims[spawnId]; if (!previous) { lastClaims[spawnId] = claim; continue; } if (/^\d+:/.test(previous) && /^\d+:/.test(claim)) { const [previousTime, previousRevision] = previous.split(":").map(Number); const [time, revision] = claim.split(":").map(Number); if (time > previousTime || (time === previousTime && revision > previousRevision)) lastClaims[spawnId] = claim; } else if (claim > previous) lastClaims[spawnId] = claim; } } return { counts, lastClaims }; }