import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; import type { PuzzleDraftLevel } from '../../../packages/shared/src/contracts/puzzleAgentDraft'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, } from '../../../packages/shared/src/contracts/runtime'; import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'; import { resolveCustomWorldCampSceneImage } from '../../data/customWorldVisuals'; import { buildBigFishPublicWorkCode, buildMatch3DPublicWorkCode, buildPuzzlePublicWorkCode, } from '../../services/publicWorkCode'; import type { CustomWorldProfile } from '../../types'; export const PLATFORM_WORK_NAME_DISPLAY_LIMIT = 8; export const PLATFORM_WORK_TAG_DISPLAY_LIMIT = 4; export type PlatformWorldCardLike = | CustomWorldGalleryCard | CustomWorldLibraryEntry | PlatformBigFishGalleryCard | PlatformMatch3DGalleryCard | PlatformPuzzleGalleryCard; export type PlatformPuzzleGalleryCard = { sourceType: 'puzzle'; workId: string; profileId: string; sourceSessionId?: string | null; publicWorkCode: string; ownerUserId: string; authorDisplayName: string; worldName: string; subtitle: string; summaryText: string; coverImageSrc: string | null; coverSlides?: PlatformPuzzleCoverSlide[]; themeTags: string[]; playCount?: number; remixCount?: number; likeCount?: number; recentPlayCount7d?: number; visibility: 'published'; publishedAt: string | null; updatedAt: string; }; export type PlatformPuzzleCoverSlide = { id: string; imageSrc: string; label: string; }; export type PlatformBigFishGalleryCard = { sourceType: 'big-fish'; workId: string; profileId: string; publicWorkCode: string; ownerUserId: string; authorDisplayName: string; worldName: string; subtitle: string; summaryText: string; coverImageSrc: string | null; themeTags: string[]; playCount?: number; remixCount?: number; likeCount?: number; recentPlayCount7d?: number; visibility: 'published'; publishedAt: string | null; updatedAt: string; }; export type PlatformMatch3DGalleryCard = { sourceType: 'match3d'; workId: string; profileId: string; sourceSessionId?: string | null; publicWorkCode: string; ownerUserId: string; authorDisplayName: string; worldName: string; subtitle: string; summaryText: string; coverImageSrc: string | null; themeTags: string[]; playCount?: number; remixCount?: number; likeCount?: number; recentPlayCount7d?: number; visibility: 'published'; publishedAt: string | null; updatedAt: string; }; export type PlatformPublicGalleryCard = | CustomWorldGalleryCard | PlatformBigFishGalleryCard | PlatformMatch3DGalleryCard | PlatformPuzzleGalleryCard; export function isLibraryWorldEntry( entry: PlatformWorldCardLike, ): entry is CustomWorldLibraryEntry { return 'profile' in entry; } export function isPuzzleGalleryEntry( entry: PlatformWorldCardLike, ): entry is PlatformPuzzleGalleryCard { return 'sourceType' in entry && entry.sourceType === 'puzzle'; } export function isBigFishGalleryEntry( entry: PlatformWorldCardLike, ): entry is PlatformBigFishGalleryCard { return 'sourceType' in entry && entry.sourceType === 'big-fish'; } export function isMatch3DGalleryEntry( entry: PlatformWorldCardLike, ): entry is PlatformMatch3DGalleryCard { return 'sourceType' in entry && entry.sourceType === 'match3d'; } export function mapPuzzleWorkToPlatformGalleryCard( work: PuzzleWorkSummary, ): PlatformPuzzleGalleryCard { return { sourceType: 'puzzle', workId: work.workId, profileId: work.profileId, sourceSessionId: work.sourceSessionId ?? null, publicWorkCode: buildPuzzlePublicWorkCode(work.profileId), ownerUserId: work.ownerUserId, authorDisplayName: work.authorDisplayName, worldName: work.workTitle || work.levelName, subtitle: '拼图关卡', summaryText: work.workDescription || work.summary, coverImageSrc: work.coverImageSrc, coverSlides: buildPuzzleWorkCoverSlides(work), themeTags: work.themeTags, playCount: work.playCount ?? 0, remixCount: work.remixCount ?? 0, likeCount: work.likeCount ?? 0, recentPlayCount7d: work.recentPlayCount7d ?? 0, visibility: 'published', publishedAt: work.publishedAt, updatedAt: work.updatedAt, }; } export function mapMatch3DWorkToPlatformGalleryCard( work: Match3DWorkSummary, ): PlatformMatch3DGalleryCard { return { sourceType: 'match3d', workId: work.workId, profileId: work.profileId, sourceSessionId: work.sourceSessionId ?? null, publicWorkCode: buildMatch3DPublicWorkCode(work.profileId), ownerUserId: work.ownerUserId, authorDisplayName: '玩家', worldName: work.gameName, subtitle: '经典消除玩法', summaryText: work.summary, coverImageSrc: work.coverImageSrc ?? null, themeTags: work.tags.length > 0 ? work.tags : [work.themeText, '抓大鹅'], playCount: work.playCount ?? 0, remixCount: 0, likeCount: 0, recentPlayCount7d: 0, visibility: 'published', publishedAt: work.publishedAt ?? null, updatedAt: work.updatedAt, }; } export function mapBigFishWorkToPlatformGalleryCard( work: BigFishWorkSummary, ): PlatformBigFishGalleryCard { return { sourceType: 'big-fish', workId: work.workId, profileId: work.sourceSessionId, publicWorkCode: buildBigFishPublicWorkCode(work.sourceSessionId), ownerUserId: work.ownerUserId, authorDisplayName: work.authorDisplayName, worldName: work.title, subtitle: work.subtitle || '大鱼吃小鱼', summaryText: work.summary, coverImageSrc: work.coverImageSrc, themeTags: ['大鱼', `${work.levelCount}级`], playCount: work.playCount ?? 0, remixCount: work.remixCount ?? 0, likeCount: work.likeCount ?? 0, recentPlayCount7d: work.recentPlayCount7d ?? 0, visibility: 'published', publishedAt: work.publishedAt ?? work.updatedAt, updatedAt: work.updatedAt, }; } export function resolvePlatformWorldStats(entry: PlatformWorldCardLike) { return { playCount: 'playCount' in entry ? (entry.playCount ?? 0) : 0, remixCount: 'remixCount' in entry ? (entry.remixCount ?? 0) : 0, likeCount: 'likeCount' in entry ? (entry.likeCount ?? 0) : 0, recentPlayCount7d: 'recentPlayCount7d' in entry ? (entry.recentPlayCount7d ?? 0) : 0, publishedAt: entry.publishedAt ?? null, updatedAt: entry.updatedAt ?? null, }; } export function resolvePlatformWorldCoverImage(entry: PlatformWorldCardLike) { if (entry.coverImageSrc) { return entry.coverImageSrc; } if (isLibraryWorldEntry(entry)) { return resolveCustomWorldCampSceneImage(entry.profile) ?? ''; } return ''; } export function resolvePlatformWorldCoverSlides( entry: PlatformWorldCardLike, ): PlatformPuzzleCoverSlide[] { const fallbackCoverImage = resolvePlatformWorldCoverImage(entry).trim(); const puzzleCoverSlides = isPuzzleGalleryEntry(entry) ? (entry.coverSlides ?? []) : []; const normalizedSlides = puzzleCoverSlides .map((slide, index) => ({ id: slide.id.trim() || `cover-${index + 1}`, imageSrc: slide.imageSrc.trim(), label: slide.label.trim() || entry.worldName, })) .filter((slide) => Boolean(slide.imageSrc)); if (normalizedSlides.length > 0) { return normalizedSlides; } return fallbackCoverImage ? [ { id: 'cover', imageSrc: fallbackCoverImage, label: entry.worldName, }, ] : []; } export function resolvePuzzleLevelFormalImageSrc(level: PuzzleDraftLevel) { const selectedCandidate = level.candidates.find( (candidate) => candidate.selected || (level.selectedCandidateId ? candidate.candidateId === level.selectedCandidateId : false), ) ?? level.candidates[level.candidates.length - 1] ?? null; return ( selectedCandidate?.imageSrc?.trim() || level.coverImageSrc?.trim() || '' ); } export function buildPuzzleWorkCoverSlides( work: PuzzleWorkSummary, ): PlatformPuzzleCoverSlide[] { const slides: PlatformPuzzleCoverSlide[] = []; const usedImageSrcSet = new Set(); work.levels?.forEach((level, index) => { const imageSrc = resolvePuzzleLevelFormalImageSrc(level); if (!imageSrc || usedImageSrcSet.has(imageSrc)) { return; } usedImageSrcSet.add(imageSrc); slides.push({ id: level.levelId?.trim() || `puzzle-level-${index + 1}`, imageSrc, label: level.levelName?.trim() || `第 ${index + 1} 关`, }); }); if (slides.length > 0) { return slides; } const fallbackImageSrc = work.coverImageSrc?.trim() ?? ''; return fallbackImageSrc ? [ { id: 'cover', imageSrc: fallbackImageSrc, label: work.levelName, }, ] : []; } export function resolvePlatformWorldLeadPortrait(entry: PlatformWorldCardLike) { if (!isLibraryWorldEntry(entry)) { return ''; } return buildCustomWorldPlayableCharacters(entry.profile)[0]?.portrait ?? ''; } function limitPlatformDisplayText(value: string, maxLength: number) { const normalized = value.trim(); const chars = Array.from(normalized); if (chars.length <= maxLength) { return normalized; } return chars.slice(0, maxLength).join(''); } export function formatPlatformWorkDisplayName(value: string) { return limitPlatformDisplayText(value, PLATFORM_WORK_NAME_DISPLAY_LIMIT); } export function formatPlatformWorkDisplayTag(value: string) { return limitPlatformDisplayText(value, PLATFORM_WORK_TAG_DISPLAY_LIMIT); } export function formatPlatformWorkDisplayTags( tags: string[], limit = tags.length, ) { return [ ...new Set( tags .map((tag) => formatPlatformWorkDisplayTag(tag)) .filter(Boolean), ), ].slice(0, limit); } export function buildPlatformWorldDisplayTags( entry: PlatformWorldCardLike, limit = 3, ) { return formatPlatformWorkDisplayTags(buildPlatformWorldTags(entry), limit); } export function buildPlatformWorldTags(entry: PlatformWorldCardLike) { if (isBigFishGalleryEntry(entry)) { return entry.themeTags.length > 0 ? entry.themeTags.slice(0, 3) : ['大鱼']; } if (isPuzzleGalleryEntry(entry)) { return entry.themeTags.length > 0 ? entry.themeTags.slice(0, 3) : ['拼图']; } if (isMatch3DGalleryEntry(entry)) { return entry.themeTags.length > 0 ? entry.themeTags.slice(0, 3) : ['抓大鹅']; } if (!isLibraryWorldEntry(entry)) { return [ describePlatformThemeLabel(entry.themeMode), `${entry.playableNpcCount} 角色`, `${entry.landmarkCount} 地标`, ]; } return [ ...entry.profile.majorFactions.slice(0, 2), ...entry.profile.coreConflicts.slice(0, 1), ] .map((value) => value.trim()) .filter(Boolean) .slice(0, 3); } function parsePlatformWorldDate(value: string) { const normalized = value.trim(); const numericTimestamp = normalized.match(/^(-?\d+(?:\.\d+)?)(?:Z)?$/u); if (numericTimestamp?.[1]) { const rawTimestamp = Number(numericTimestamp[1]); if (Number.isFinite(rawTimestamp)) { const absoluteTimestamp = Math.abs(rawTimestamp); const timestampMs = absoluteTimestamp >= 1_000_000_000_000_000 ? rawTimestamp / 1000 : absoluteTimestamp >= 1_000_000_000_000 ? rawTimestamp : absoluteTimestamp >= 1_000_000_000 ? rawTimestamp * 1000 : Number.NaN; const date = new Date(timestampMs); if (!Number.isNaN(date.getTime())) { return date; } } } const date = new Date(normalized); return Number.isNaN(date.getTime()) ? null : date; } function formatPlatformDateOnly(date: Date) { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } export function formatPlatformWorldTime(value: string | null) { if (!value) { return '未发布'; } const date = parsePlatformWorldDate(value); if (!date) { return value; } return formatPlatformDateOnly(date); } export function resolvePlatformPublicWorkCode( entry: PlatformWorldCardLike, ): string | null { if (isBigFishGalleryEntry(entry)) { return entry.publicWorkCode; } if (isPuzzleGalleryEntry(entry)) { return entry.publicWorkCode; } if (isMatch3DGalleryEntry(entry)) { return entry.publicWorkCode; } return entry.publicWorkCode; } export function describePlatformThemeLabel( themeMode: CustomWorldGalleryCard['themeMode'], ) { switch (themeMode) { case 'martial': return '江湖'; case 'arcane': return '灵脉'; case 'machina': return '机巧'; case 'tide': return '潮痕'; case 'rift': return '裂界'; default: return '回响'; } }