import type { BarkBattleDraftConfig } from '../../../packages/shared/src/contracts/barkBattle'; import type { BigFishSessionSnapshotResponse } from '../../../packages/shared/src/contracts/bigFish'; import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent'; import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import type { PuzzleClearSessionSnapshotResponse, PuzzleClearWorkProfileResponse, } from '../../../packages/shared/src/contracts/puzzleClear'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent'; import type { VisualNovelAgentSessionSnapshot } from '../../../packages/shared/src/contracts/visualNovel'; import { type CreationUrlState, isCreationRestorePath, } from '../../services/creationUrlState'; import type { JumpHopSessionSnapshotResponse, JumpHopWorkProfileResponse, } from '../../services/jump-hop/jumpHopClient'; import { buildPuzzlePublicWorkCode } from '../../services/publicWorkCode'; import type { PuzzleRuntimeUrlState } from '../../services/puzzleRuntimeUrlState'; import type { WoodenFishSessionSnapshotResponse, WoodenFishWorkProfileResponse, } from '../../services/wooden-fish/woodenFishClient'; import type { SelectionStage } from './platformEntryTypes'; import { buildPuzzleResultProfileId, buildPuzzleResultWorkId, buildPuzzleSessionIdFromProfileId, } from './platformPuzzleIdentityModel'; /** 平台创作恢复 URL 私有 query 的纯模型,调用方只需传入玩法快照。 */ export function normalizeCreationUrlValue(value: string | null | undefined) { return value?.trim() || null; } export function hasCreationUrlStateValue(state: CreationUrlState) { return Boolean( normalizeCreationUrlValue(state.sessionId) || normalizeCreationUrlValue(state.profileId) || normalizeCreationUrlValue(state.draftId) || normalizeCreationUrlValue(state.workId), ); } export function hasPuzzleRuntimeUrlStateValue(state: PuzzleRuntimeUrlState) { return Boolean( normalizeCreationUrlValue(state.runtimeSessionId) || normalizeCreationUrlValue(state.runtimeProfileId) || normalizeCreationUrlValue(state.runtimeLevelId) || normalizeCreationUrlValue(state.publicWorkCode) || normalizeCreationUrlValue(state.mode), ); } export function buildPuzzleRuntimeUrlStateKey(state: PuzzleRuntimeUrlState) { return [ normalizeCreationUrlValue(state.mode), normalizeCreationUrlValue(state.runtimeSessionId), normalizeCreationUrlValue(state.runtimeProfileId), normalizeCreationUrlValue(state.runtimeLevelId), normalizeCreationUrlValue(state.publicWorkCode), ].join('|'); } export type CreationUrlRestoreTargetKind = | 'big-fish' | 'match3d' | 'square-hole' | 'puzzle' | 'visual-novel' | 'bark-battle' | 'baby-object-match' | 'jump-hop' | 'wooden-fish'; type CreationUrlRestoreTargetBase = { kind: CreationUrlRestoreTargetKind; sessionId: string | null; profileId: string | null; draftId: string | null; workId: string | null; isGeneratingPath: boolean; }; export type BigFishCreationUrlRestoreTarget = CreationUrlRestoreTargetBase & { kind: 'big-fish'; bigFishSessionId: string | null; }; type NonBigFishCreationUrlRestoreTarget = CreationUrlRestoreTargetBase & { kind: Exclude; }; export type CreationUrlRestoreTarget = | BigFishCreationUrlRestoreTarget | NonBigFishCreationUrlRestoreTarget; export type BigFishRestoreWorkIdentity = { sourceSessionId?: string | null; workId?: string | null; }; export type SessionProfileWorkRestoreIdentity = { sourceSessionId?: string | null; profileId?: string | null; workId?: string | null; }; export type ProfileRestoreWorkIdentity = { profileId?: string | null; }; export type BarkBattleRestoreWorkIdentity = { workId?: string | null; draftId?: string | null; }; export type BabyObjectMatchRestoreDraftIdentity = { profileId?: string | null; draftId?: string | null; }; const CREATION_URL_RESTORE_TARGET_ROUTES = [ ['/creation/big-fish', 'big-fish'], ['/creation/match3d', 'match3d'], ['/creation/square-hole', 'square-hole'], ['/creation/puzzle', 'puzzle'], ['/creation/visual-novel', 'visual-novel'], ['/creation/bark-battle', 'bark-battle'], ['/creation/baby-object-match', 'baby-object-match'], ['/creation/jump-hop', 'jump-hop'], ['/creation/wooden-fish', 'wooden-fish'], ] as const satisfies readonly (readonly [ string, CreationUrlRestoreTargetKind, ])[]; export function resolveCreationUrlRestoreTarget( pathname: string | undefined, state: CreationUrlState, ): CreationUrlRestoreTarget | null { const path = pathname?.trim() ?? ''; const route = CREATION_URL_RESTORE_TARGET_ROUTES.find( ([prefix]) => path === prefix || path.startsWith(`${prefix}/`), ); if (!route) { return null; } const kind = route[1]; const sessionId = normalizeCreationUrlValue(state.sessionId); const profileId = normalizeCreationUrlValue(state.profileId); const draftId = normalizeCreationUrlValue(state.draftId); const workId = normalizeCreationUrlValue(state.workId); const base = { kind, sessionId, profileId, draftId, workId, isGeneratingPath: path.includes('/generating'), }; if (kind === 'big-fish') { return { ...base, kind, bigFishSessionId: sessionId ?? workId?.replace(/^big-fish-work-/u, '') ?? null, }; } return base as NonBigFishCreationUrlRestoreTarget; } function matchesRestoreValue( itemValue: string | null | undefined, targetValue: string | null, ) { return Boolean(targetValue && itemValue === targetValue); } export function matchesBigFishCreationUrlRestoreTarget( item: BigFishRestoreWorkIdentity, target: BigFishCreationUrlRestoreTarget, ) { return ( matchesRestoreValue(item.sourceSessionId, target.bigFishSessionId) || matchesRestoreValue(item.workId, target.workId) ); } export function matchesSessionProfileWorkCreationUrlRestoreTarget( item: SessionProfileWorkRestoreIdentity, target: Pick, ) { return ( matchesRestoreValue(item.sourceSessionId, target.sessionId) || matchesRestoreValue(item.profileId, target.profileId) || matchesRestoreValue(item.workId, target.workId) ); } export function matchesVisualNovelCreationUrlRestoreTarget( item: ProfileRestoreWorkIdentity, target: Pick, ) { return matchesRestoreValue(item.profileId, target.profileId); } export function matchesBarkBattleCreationUrlRestoreTarget( item: BarkBattleRestoreWorkIdentity, target: Pick, ) { return ( matchesRestoreValue(item.workId, target.workId) || matchesRestoreValue(item.draftId, target.draftId) ); } export function matchesBabyObjectMatchCreationUrlRestoreTarget( item: BabyObjectMatchRestoreDraftIdentity, target: Pick, ) { return ( matchesRestoreValue(item.profileId, target.profileId) || matchesRestoreValue(item.draftId, target.draftId) || matchesRestoreValue(item.profileId, target.workId) ); } export function resolveJumpHopCreationUrlRestoreStage(params: { isGeneratingPath: boolean; hasRestoredDraft: boolean; hasRestoredWork: boolean; }): SelectionStage { if (params.isGeneratingPath) { return 'jump-hop-generating'; } return params.hasRestoredDraft || params.hasRestoredWork ? 'jump-hop-result' : 'jump-hop-workspace'; } export function resolveWoodenFishCreationUrlRestoreStage(params: { isGeneratingPath: boolean; hasRestoredDraft: boolean; }): SelectionStage { if (params.isGeneratingPath) { return 'wooden-fish-generating'; } return params.hasRestoredDraft ? 'wooden-fish-result' : 'wooden-fish-workspace'; } export type InitialCreationUrlRestoreDecision = | { type: 'skip' } | { type: 'mark-handled' } | { type: 'wait' } | { type: 'restore' }; export function resolveInitialCreationUrlRestoreDecision(params: { handled: boolean; pathname: string | undefined; state: CreationUrlState; isLoadingPlatform: boolean; canReadProtectedData: boolean; }): InitialCreationUrlRestoreDecision { if (params.handled) { return { type: 'skip' }; } if ( !isCreationRestorePath(params.pathname) || !hasCreationUrlStateValue(params.state) ) { return { type: 'mark-handled' }; } if (params.isLoadingPlatform || !params.canReadProtectedData) { return { type: 'wait' }; } return { type: 'restore' }; } export function buildBigFishCreationUrlState( session: BigFishSessionSnapshotResponse | null, ): CreationUrlState { const sessionId = normalizeCreationUrlValue(session?.sessionId); return { sessionId, workId: sessionId ? `big-fish-work-${sessionId}` : null, }; } export function buildMatch3DCreationUrlState( session: Match3DAgentSessionSnapshot | null, ): CreationUrlState { const sessionId = normalizeCreationUrlValue(session?.sessionId); const profileId = normalizeCreationUrlValue( session?.draft?.profileId ?? session?.publishedProfileId, ); return { sessionId, profileId, workId: profileId, }; } export function buildSquareHoleCreationUrlState( session: SquareHoleSessionSnapshot | null, ): CreationUrlState { const sessionId = normalizeCreationUrlValue(session?.sessionId); const profileId = normalizeCreationUrlValue( session?.draft?.profileId ?? session?.publishedProfileId, ); return { sessionId, profileId, workId: profileId, }; } export function buildPuzzleCreationUrlState( session: PuzzleAgentSessionSnapshot | null, ): CreationUrlState { const sessionId = normalizeCreationUrlValue(session?.sessionId); const profileId = normalizeCreationUrlValue( session?.publishedProfileId ?? buildPuzzleResultProfileId(sessionId), ); return { sessionId, profileId, workId: sessionId ? buildPuzzleResultWorkId(sessionId) : null, }; } export function buildPuzzleDraftRuntimeUrlState( item: PuzzleWorkSummary, levelId?: string | null, ): PuzzleRuntimeUrlState { const runtimeSessionId = normalizeCreationUrlValue(item.sourceSessionId) ?? buildPuzzleSessionIdFromProfileId(item.profileId); return { mode: 'draft', runtimeSessionId, runtimeProfileId: normalizeCreationUrlValue(item.profileId), runtimeLevelId: normalizeCreationUrlValue(levelId), }; } export function buildPuzzlePublishedRuntimeUrlState( item: PuzzleWorkSummary, levelId?: string | null, ): PuzzleRuntimeUrlState { return { mode: 'published', runtimeProfileId: normalizeCreationUrlValue(item.profileId), runtimeLevelId: normalizeCreationUrlValue(levelId), publicWorkCode: buildPuzzlePublicWorkCode(item.profileId), }; } export function buildVisualNovelCreationUrlState( session: VisualNovelAgentSessionSnapshot | null, ): CreationUrlState { const sessionId = normalizeCreationUrlValue(session?.sessionId); const profileId = normalizeCreationUrlValue(session?.draft?.profileId); return { sessionId, profileId, workId: profileId ?? sessionId, }; } export function buildJumpHopCreationUrlState(params: { session?: JumpHopSessionSnapshotResponse | null; work?: JumpHopWorkProfileResponse | null; }): CreationUrlState { const sessionId = normalizeCreationUrlValue(params.session?.sessionId); const profileId = normalizeCreationUrlValue( params.work?.summary.profileId ?? params.session?.draft?.profileId, ); return { sessionId, profileId, workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId), }; } export function buildPuzzleClearCreationUrlState(params: { session?: PuzzleClearSessionSnapshotResponse | null; work?: PuzzleClearWorkProfileResponse | null; }): CreationUrlState { const sessionId = normalizeCreationUrlValue(params.session?.sessionId); const profileId = normalizeCreationUrlValue( params.work?.summary.profileId ?? params.session?.draft?.profileId, ); return { sessionId, profileId, workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId), }; } export function buildWoodenFishCreationUrlState(params: { session?: WoodenFishSessionSnapshotResponse | null; work?: WoodenFishWorkProfileResponse | null; }): CreationUrlState { const sessionId = normalizeCreationUrlValue(params.session?.sessionId); const profileId = normalizeCreationUrlValue( params.work?.summary.profileId ?? params.session?.draft?.profileId, ); const draftId = profileId ?? sessionId; return { sessionId, profileId, draftId, workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId), }; } export function buildBarkBattleCreationUrlState( draft: BarkBattleDraftConfig | null, ): CreationUrlState { return { draftId: normalizeCreationUrlValue(draft?.draftId), workId: normalizeCreationUrlValue(draft?.workId ?? draft?.draftId), }; } export function buildBabyObjectMatchCreationUrlState( draft: BabyObjectMatchDraft | null, ): CreationUrlState { const profileId = normalizeCreationUrlValue(draft?.profileId); return { profileId, draftId: normalizeCreationUrlValue(draft?.draftId), workId: profileId, }; }