import type { CaseState, DocumentExhibit, ExhibitRelation, Connection } from '../src/types.js' function requirementsMet(document: DocumentExhibit, earnedFlags: ReadonlySet) { return (document.requiredFlags || []).every(flag => earnedFlags.has(flag)) } export function filterLevelVisibility(full: CaseState, earnedFlags: Iterable, seenDocumentIds: Iterable, authorMode: boolean): CaseState { if (authorMode) return { ...full, newlyVisibleDocumentIds: [] } const earned = new Set(earnedFlags) const seen = new Set(seenDocumentIds) const visibleExhibits = full.exhibits.filter(exhibit => !exhibit.hidden && (exhibit.type !== 'document' || requirementsMet(exhibit, earned))) const visibleIds = new Set(visibleExhibits.map(exhibit => exhibit.id)) const sanitize = (exhibit: typeof visibleExhibits[number]) => { if (exhibit.type !== 'document') return exhibit const { requiredFlags: _requirements, ...document } = exhibit return document } return { ...full, exhibits: visibleExhibits.map(sanitize), relations: full.relations.filter(relation => visibleIds.has(relation.fromExhibitId) && visibleIds.has(relation.toExhibitId)), connections: full.connections.filter(connection => visibleIds.has(connection.fromExhibitId) && visibleIds.has(connection.toExhibitId)), newlyVisibleDocumentIds: visibleExhibits.flatMap(exhibit => exhibit.type === 'document' && !seen.has(exhibit.id) ? [exhibit.id] : []), } } function appendMissingById(submitted: T[], preserved: T[]) { const ids = new Set(submitted.map(item => item.id)) return [...submitted, ...preserved.filter(item => !ids.has(item.id))] } /** Preserve server-hidden objects during the legacy whole-board PUT used by play mode. */ export function mergePlayerStateForPersistence(full: CaseState, visible: CaseState, submitted: CaseState): CaseState { const visibleIds = new Set(visible.exhibits.map(exhibit => exhibit.id)) const unavailableIds = new Set(full.exhibits.filter(exhibit => !visibleIds.has(exhibit.id)).map(exhibit => exhibit.id)) const fullDocuments = new Map(full.exhibits.flatMap(exhibit => exhibit.type === 'document' ? [[exhibit.id, exhibit] as const] : [])) const submittedExhibits = submitted.exhibits.map(exhibit => exhibit.type === 'document' ? { ...exhibit, requiredFlags: fullDocuments.get(exhibit.id)?.requiredFlags || exhibit.requiredFlags || [] } : exhibit) const preservedExhibits = full.exhibits.filter(exhibit => unavailableIds.has(exhibit.id)) const touchesUnavailable = (item: ExhibitRelation | Connection) => unavailableIds.has(item.fromExhibitId) || unavailableIds.has(item.toExhibitId) return { ...submitted, report: full.report, exhibits: appendMissingById(submittedExhibits, preservedExhibits), relations: appendMissingById(submitted.relations, full.relations.filter(touchesUnavailable)), connections: appendMissingById(submitted.connections, full.connections.filter(touchesUnavailable)), newlyVisibleDocumentIds: [], } }