import { AppError } from '@/lib/error-model'; export type GameAssetCandidate = { id: string; name: string; category: string; status: string; purpose: string; source: string; license: string; localPath?: string; mediaKind: 'image' | 'audio' | 'bundle' | 'unavailable'; previewDataUrl?: string; audioDataUrl?: string; manifest: string[]; unavailableReason?: string; }; export type GameAssetReviewSnapshot = { invocationId: string; candidateIds: string[]; decisions: Record; status: 'pending' | 'resolved'; pendingAssetIds: string[]; approvedAssetIds: string[]; discardedAssetIds: string[]; }; export type GameAssetReviewResponse = { review: GameAssetReviewSnapshot; assets: GameAssetCandidate[]; }; export type GameAssetReviewLoadFailure = 'stale' | 'retryable'; const STALE_HTTP_STATUSES = new Set([400, 403, 404, 409, 410, 422]); const MEDIA_KINDS = new Set(['image', 'audio', 'bundle', 'unavailable']); const REVIEW_DECISIONS = new Set(['approved', 'discarded', 'replace-requested']); function record(value: unknown): Record | null { return value !== null && typeof value === 'object' ? value as Record : null; } function stringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === 'string'); } function optionalString(value: unknown): boolean { return value === undefined || typeof value === 'string'; } function validAsset(value: unknown): value is GameAssetCandidate { const item = record(value); return Boolean( item && typeof item.id === 'string' && typeof item.name === 'string' && typeof item.category === 'string' && typeof item.status === 'string' && typeof item.purpose === 'string' && typeof item.source === 'string' && typeof item.license === 'string' && typeof item.mediaKind === 'string' && MEDIA_KINDS.has(item.mediaKind) && stringArray(item.manifest) && optionalString(item.localPath) && optionalString(item.previewDataUrl) && optionalString(item.audioDataUrl) && optionalString(item.unavailableReason), ); } export function isGameAssetReviewResponse(value: unknown): value is GameAssetReviewResponse { const response = record(value); const review = record(response?.review); const decisions = record(review?.decisions); return Boolean( response && review && typeof review.invocationId === 'string' && stringArray(review.candidateIds) && decisions && !Array.isArray(review.decisions) && Object.values(decisions).every((decision) => typeof decision === 'string' && REVIEW_DECISIONS.has(decision)) && (review.status === 'pending' || review.status === 'resolved') && stringArray(review.pendingAssetIds) && stringArray(review.approvedAssetIds) && stringArray(review.discardedAssetIds) && Array.isArray(response.assets) && response.assets.every(validAsset), ); } export function classifyGameAssetReviewLoadFailure(error: unknown): GameAssetReviewLoadFailure { const status = error instanceof AppError && typeof error.details?.status === 'number' ? error.details.status : null; return status !== null && STALE_HTTP_STATUSES.has(status) ? 'stale' : 'retryable'; }