import { useCallback, useEffect, useRef, useState } from 'react'; import type { CustomWorldWorkSummary, } from '../../../packages/shared/src/contracts/customWorldAgent'; import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, PlatformBrowseHistoryEntry, PlatformBrowseHistoryWriteEntry, ProfileDashboardSummary, ProfileSaveArchiveSummary, } from '../../../packages/shared/src/contracts/runtime'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import type { AuthUser } from '../../services/authService'; import { listRpgCreationWorks } from '../../services/rpg-creation/index'; import { listRpgEntryWorldGallery, listRpgEntryWorldLibrary, listRpgProfileBrowseHistory, listRpgProfileSaveArchives, resumeRpgProfileSaveArchive, upsertRpgProfileBrowseHistory, } from '../../services/rpg-entry'; import { RUNTIME_BACKGROUND_AUTH_OPTIONS, type RuntimeRequestOptions, } from '../../services/rpg-runtime/rpgRuntimeRequest'; import type { CustomWorldProfile } from '../../types'; import type { PlatformHomeTab } from './RpgEntryHomeView'; import { resolveRpgEntryErrorMessage } from './rpgEntryShared'; const PLATFORM_BOOTSTRAP_AUTH_OPTIONS = RUNTIME_BACKGROUND_AUTH_OPTIONS; type UseRpgEntryBootstrapParams = { user: AuthUser | null | undefined; canAccessProtectedData?: boolean | undefined; getProfileDashboard: ( options?: RuntimeRequestOptions, ) => Promise; handleContinueGame: ( snapshot?: HydratedSavedGameSnapshot | null, ) => void; hasInitialAgentSession: boolean; }; export function useRpgEntryBootstrap( params: UseRpgEntryBootstrapParams, ) { const { user, canAccessProtectedData = Boolean(user), getProfileDashboard, handleContinueGame, hasInitialAgentSession, } = params; const isAuthenticated = Boolean(user); const canReadProtectedData = Boolean(user) && canAccessProtectedData; const platformTabBootstrapUserIdRef = useRef( undefined, ); const hasExplicitPlatformTabSelectionRef = useRef(false); const [savedCustomWorldEntries, setSavedCustomWorldEntries] = useState< CustomWorldLibraryEntry[] >([]); const [customWorldWorkEntries, setCustomWorldWorkEntries] = useState< CustomWorldWorkSummary[] >([]); const [publishedGalleryEntries, setPublishedGalleryEntries] = useState< CustomWorldGalleryCard[] >([]); const [historyEntries, setHistoryEntries] = useState< PlatformBrowseHistoryEntry[] >([]); const [saveEntries, setSaveEntries] = useState([]); const [platformTab, setPlatformTabState] = useState('category'); const [platformError, setPlatformError] = useState(null); const [dashboardError, setDashboardError] = useState(null); const [historyError, setHistoryError] = useState(null); const [saveError, setSaveError] = useState(null); const [isLoadingPlatform, setIsLoadingPlatform] = useState(false); const [isLoadingDashboard, setIsLoadingDashboard] = useState(false); const [isResumingSaveWorldKey, setIsResumingSaveWorldKey] = useState< string | null >(null); const [profileDashboard, setProfileDashboard] = useState(null); const setPlatformTab = useCallback((nextTab: PlatformHomeTab) => { // 区分“平台首屏默认落点”和“用户/流程显式切换”。 // 一旦显式切过 Tab,就不能再被首屏异步请求回刷成首页或存档。 hasExplicitPlatformTabSelectionRef.current = true; setPlatformTabState(nextTab); }, []); const refreshProfileDashboard = useCallback(async () => { if (!user || !canReadProtectedData) { setProfileDashboard(null); setDashboardError(null); setIsLoadingDashboard(false); return; } setIsLoadingDashboard(true); setDashboardError(null); try { setProfileDashboard( await getProfileDashboard(PLATFORM_BOOTSTRAP_AUTH_OPTIONS), ); } catch (error) { setDashboardError( resolveRpgEntryErrorMessage(error, '读取个人数据看板失败。'), ); } finally { setIsLoadingDashboard(false); } }, [canReadProtectedData, getProfileDashboard, user]); const refreshCustomWorldWorks = useCallback(async () => { if (!user || !canReadProtectedData) { setCustomWorldWorkEntries([]); return []; } const nextItems = await listRpgCreationWorks( PLATFORM_BOOTSTRAP_AUTH_OPTIONS, ); setCustomWorldWorkEntries(nextItems); return nextItems; }, [canReadProtectedData, user]); const refreshPublishedGallery = useCallback(async () => { const nextEntries = await listRpgEntryWorldGallery(); setPublishedGalleryEntries(nextEntries); return nextEntries; }, []); const refreshSavedCustomWorldLibrary = useCallback(async () => { if (!user || !canReadProtectedData) { setSavedCustomWorldEntries([]); return []; } const nextEntries = await listRpgEntryWorldLibrary( PLATFORM_BOOTSTRAP_AUTH_OPTIONS, ); setSavedCustomWorldEntries(nextEntries); return nextEntries; }, [canReadProtectedData, user]); const refreshSaveArchives = useCallback(async () => { if (!user || !canReadProtectedData) { setSaveEntries([]); setSaveError(null); return []; } setSaveError(null); try { const nextEntries = await listRpgProfileSaveArchives( PLATFORM_BOOTSTRAP_AUTH_OPTIONS, ); setSaveEntries(nextEntries); return nextEntries; } catch (error) { setSaveError(resolveRpgEntryErrorMessage(error, '读取存档列表失败。')); return []; } }, [canReadProtectedData, user]); const appendBrowseHistoryEntry = useCallback( async (entry: PlatformBrowseHistoryWriteEntry) => { setHistoryError(null); try { const syncedEntries = await upsertRpgProfileBrowseHistory( entry, PLATFORM_BOOTSTRAP_AUTH_OPTIONS, ); setHistoryEntries(syncedEntries); } catch (error) { setHistoryError( resolveRpgEntryErrorMessage(error, '写入浏览历史失败。'), ); } }, [], ); const handleResumeSaveEntry = useCallback( async (entry: ProfileSaveArchiveSummary) => { if (!user || !canReadProtectedData || isResumingSaveWorldKey) { return; } setIsResumingSaveWorldKey(entry.worldKey); setSaveError(null); try { const resumedArchive = await resumeRpgProfileSaveArchive(entry.worldKey); setSaveEntries((currentEntries) => currentEntries.map((currentEntry) => currentEntry.worldKey === resumedArchive.entry.worldKey ? resumedArchive.entry : currentEntry, ), ); handleContinueGame(resumedArchive.snapshot); } catch (error) { setSaveError(resolveRpgEntryErrorMessage(error, '恢复存档失败。')); } finally { setIsResumingSaveWorldKey(null); } }, [canReadProtectedData, handleContinueGame, isResumingSaveWorldKey, user], ); useEffect(() => { let isActive = true; const nextPlatformBootstrapUserId = user?.id ?? null; const shouldApplyInitialPlatformTab = platformTabBootstrapUserIdRef.current !== nextPlatformBootstrapUserId; if (shouldApplyInitialPlatformTab) { // 在请求发出前先占位,避免首屏请求未完成时用户切了 Tab, // 返回结果又被误判成“还没初始化过”并强制跳回默认页。 platformTabBootstrapUserIdRef.current = nextPlatformBootstrapUserId; } void (async () => { setHistoryEntries([]); setHistoryError(null); setSaveError(null); setIsLoadingPlatform(true); setPlatformError(null); setIsLoadingDashboard(canReadProtectedData); setDashboardError(null); if (!canReadProtectedData) { setSavedCustomWorldEntries([]); setCustomWorldWorkEntries([]); setSaveEntries([]); setProfileDashboard(null); } try { const [ libraryEntriesResult, workEntriesResult, galleryEntriesResult, dashboardResult, historyResult, saveArchivesResult, ] = await Promise.allSettled([ canReadProtectedData ? listRpgEntryWorldLibrary(PLATFORM_BOOTSTRAP_AUTH_OPTIONS) : Promise.resolve([]), canReadProtectedData ? listRpgCreationWorks(PLATFORM_BOOTSTRAP_AUTH_OPTIONS) : Promise.resolve([]), listRpgEntryWorldGallery(), canReadProtectedData ? getProfileDashboard(PLATFORM_BOOTSTRAP_AUTH_OPTIONS) : Promise.resolve(null), canReadProtectedData ? listRpgProfileBrowseHistory(PLATFORM_BOOTSTRAP_AUTH_OPTIONS) : Promise.resolve([]), canReadProtectedData ? listRpgProfileSaveArchives(PLATFORM_BOOTSTRAP_AUTH_OPTIONS) : Promise.resolve([]), ]); if (!isActive) { return; } if (libraryEntriesResult.status === 'fulfilled') { setSavedCustomWorldEntries(libraryEntriesResult.value); } else { setSavedCustomWorldEntries([]); } if (workEntriesResult.status === 'fulfilled') { setCustomWorldWorkEntries(workEntriesResult.value); } else { setCustomWorldWorkEntries([]); } if (galleryEntriesResult.status === 'fulfilled') { setPublishedGalleryEntries(galleryEntriesResult.value); } else { // 中文注释:公开广场只影响首页展示,失败时降级为空列表; // 私有作品库和创作作品列表的受保护失败才需要阻塞提示。 setPublishedGalleryEntries([]); } if ( (canReadProtectedData && libraryEntriesResult.status === 'rejected') || (canReadProtectedData && workEntriesResult.status === 'rejected') ) { const platformFailure = libraryEntriesResult.status === 'rejected' ? libraryEntriesResult.reason : workEntriesResult.status === 'rejected' ? workEntriesResult.reason : null; setPlatformError( resolveRpgEntryErrorMessage(platformFailure, '读取平台数据失败。'), ); } if (dashboardResult.status === 'fulfilled') { setProfileDashboard(dashboardResult.value); } else if (canReadProtectedData) { setProfileDashboard(null); setDashboardError( resolveRpgEntryErrorMessage( dashboardResult.reason, '读取个人数据看板失败。', ), ); } if (historyResult.status === 'fulfilled') { setHistoryEntries(historyResult.value); } else if (canReadProtectedData) { setHistoryError( resolveRpgEntryErrorMessage(historyResult.reason, '读取浏览历史失败。'), ); } if (saveArchivesResult.status === 'fulfilled') { setSaveEntries(saveArchivesResult.value); } else if (canReadProtectedData) { setSaveEntries([]); setSaveError( resolveRpgEntryErrorMessage( saveArchivesResult.reason, '读取存档列表失败。', ), ); } if ( shouldApplyInitialPlatformTab && !hasInitialAgentSession && !hasExplicitPlatformTabSelectionRef.current ) { // 中文注释:新用户先进入发现页;推荐页只在用户主动点击后作为登录门禁入口。 setPlatformTabState(isAuthenticated ? 'home' : 'category'); } } finally { if (isActive) { setIsLoadingPlatform(false); setIsLoadingDashboard(false); } } })(); return () => { isActive = false; }; }, [ canReadProtectedData, getProfileDashboard, hasInitialAgentSession, isAuthenticated, user, ]); return { isAuthenticated, canReadProtectedData, platformTab, setPlatformTab, savedCustomWorldEntries, setSavedCustomWorldEntries, customWorldWorkEntries, setCustomWorldWorkEntries, publishedGalleryEntries, setPublishedGalleryEntries, historyEntries, setHistoryEntries, saveEntries, setSaveEntries, platformError, setPlatformError, dashboardError, setDashboardError, historyError, setHistoryError, saveError, setSaveError, isLoadingPlatform, isLoadingDashboard, isResumingSaveWorldKey, profileDashboard, setProfileDashboard, refreshProfileDashboard, refreshCustomWorldWorks, refreshPublishedGallery, refreshSavedCustomWorldLibrary, refreshSaveArchives, appendBrowseHistoryEntry, handleResumeSaveEntry, }; } /** * 兼容旧创作链命名,避免并行工作包在本轮迁移后断开导入。 */ export const useRpgCreationPlatformBootstrap = useRpgEntryBootstrap;