import type { PuzzleDraftLevel } from '../../../packages/shared/src/contracts/puzzleAgentDraft'; import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession'; function normalizeRecoveryText(value: string | null | undefined) { return value?.trim() || null; } function hasPuzzleAssetReference( imageSrc: string | null | undefined, objectKey: string | null | undefined, ) { return Boolean(normalizeRecoveryText(imageSrc) || normalizeRecoveryText(objectKey)); } function resolvePrimaryPuzzleLevel(session: PuzzleAgentSessionSnapshot) { return session.draft?.levels?.[0] ?? null; } function resolvePuzzleRecoveryCandidate( session: PuzzleAgentSessionSnapshot, primaryLevel: PuzzleDraftLevel | null, ) { const draft = session.draft; if (!draft) { return null; } return ( primaryLevel?.candidates.find((candidate) => candidate.selected) ?? primaryLevel?.candidates[0] ?? draft.candidates.find((candidate) => candidate.selected) ?? draft.candidates[0] ?? null ); } function resolvePuzzleRecoveryCoverFields( session: PuzzleAgentSessionSnapshot, ) { const draft = session.draft; const primaryLevel = resolvePrimaryPuzzleLevel(session); const selectedCandidate = resolvePuzzleRecoveryCandidate( session, primaryLevel, ); return { coverImageSrc: normalizeRecoveryText(draft?.coverImageSrc) ?? normalizeRecoveryText(primaryLevel?.coverImageSrc) ?? normalizeRecoveryText(selectedCandidate?.imageSrc), coverAssetId: normalizeRecoveryText(draft?.coverAssetId) ?? normalizeRecoveryText(primaryLevel?.coverAssetId) ?? normalizeRecoveryText(selectedCandidate?.assetId), selectedCandidateId: draft?.selectedCandidateId ?? primaryLevel?.selectedCandidateId ?? selectedCandidate?.candidateId ?? null, }; } function hasCompleteGeneratedPuzzleLevelAssets( level: PuzzleDraftLevel | null, coverImageSrc: string | null, ) { return Boolean( normalizeRecoveryText(coverImageSrc) && hasPuzzleAssetReference( level?.levelSceneImageSrc, level?.levelSceneImageObjectKey, ) && hasPuzzleAssetReference( level?.uiSpritesheetImageSrc, level?.uiSpritesheetImageObjectKey, ) && hasPuzzleAssetReference( level?.levelBackgroundImageSrc, level?.levelBackgroundImageObjectKey, ), ); } export function hasRecoverableGeneratedPuzzleDraft( session: PuzzleAgentSessionSnapshot, ) { const draft = session.draft; if (!draft) { return false; } const primaryLevel = resolvePrimaryPuzzleLevel(session); const { coverImageSrc } = resolvePuzzleRecoveryCoverFields(session); return hasCompleteGeneratedPuzzleLevelAssets(primaryLevel, coverImageSrc); } export function normalizeRecoveredPuzzleDraftSession( session: PuzzleAgentSessionSnapshot, ): PuzzleAgentSessionSnapshot { const draft = session.draft; if (!draft) { return session; } const { coverImageSrc, coverAssetId, selectedCandidateId } = resolvePuzzleRecoveryCoverFields(session); const nextLevels = draft.levels?.map((level, index) => index === 0 ? { ...level, coverImageSrc: normalizeRecoveryText(level.coverImageSrc) ? level.coverImageSrc : coverImageSrc, coverAssetId: normalizeRecoveryText(level.coverAssetId) ? level.coverAssetId : coverAssetId, selectedCandidateId: level.selectedCandidateId ?? selectedCandidateId, } : level, ); const nextSession = { ...session, draft: { ...draft, coverImageSrc, coverAssetId, selectedCandidateId, levels: nextLevels, }, } satisfies PuzzleAgentSessionSnapshot; const isRecoverable = hasRecoverableGeneratedPuzzleDraft(nextSession); if (!isRecoverable) { return nextSession; } return { ...nextSession, draft: { ...nextSession.draft, generationStatus: 'ready', levels: nextSession.draft.levels?.map((level, index) => index === 0 ? { ...level, generationStatus: 'ready', } : level, ), }, }; }