/** * AI Coach — Educational Session Context (architecture prep) * * Resolves the learner's current session and packages it as context * for a future AI Coach. Session data is knowledge for the AI, not UI. * * Does NOT call any AI provider or send messages. */ import { getSessionByOrder, PROGRAM_SESSION_COUNT, type ProgramSession, type ContentBlock } from '@/data/program/sessions'; import { isProgramCompleted, isSessionCompleted, isSessionUnlocked, isWaitingForNextSession, getProgramProgress, } from '@/services/programService'; /** * Structured payload the future AI Coach will receive. * Built from course data; not rendered in Chat UI. */ export interface AiCoachSessionContext { sessionId: string; order: number; title: string; description: string; estimatedDurationMinutes: number; objectivesHint: string; /** Full session blocks — teaching material for the AI, not for direct display */ blocks: ContentBlock[]; totalSessions: number; isFinalSession: boolean; /** ISO timestamp when the next session becomes available (only during cooldown) */ nextSessionUnlocksAt?: string | null; } /** * First unlocked incomplete session (sequential). * Uses existing unlock/completion helpers — does not skip locked sessions. */ export function getCurrentEducationalSession(userId: string): ProgramSession | null { if (isProgramCompleted(userId)) return null; for (let order = 1; order <= PROGRAM_SESSION_COUNT; order++) { if (isSessionCompleted(userId, order)) continue; if (!isSessionUnlocked(userId, order)) break; return getSessionByOrder(order) ?? null; } return null; } /** * Package a ProgramSession for future AI Coach injection. * Chat must not render this as a content panel. */ export function buildAiCoachSessionContext( session: ProgramSession, extras?: { nextSessionUnlocksAt?: string | null }, ): AiCoachSessionContext { return { sessionId: session.id, order: session.order, title: session.title, description: session.description, estimatedDurationMinutes: session.estimatedDurationMinutes, objectivesHint: session.description, blocks: session.blocks, totalSessions: PROGRAM_SESSION_COUNT, isFinalSession: session.order >= PROGRAM_SESSION_COUNT, nextSessionUnlocksAt: extras?.nextSessionUnlocksAt ?? null, }; } /** * Resolve + package current session context for a user. * Returns null when the program is finished or no session is available. */ export function resolveAiCoachSessionContext( userId: string ): AiCoachSessionContext | null { const session = getCurrentEducationalSession(userId); if (!session) return null; const progress = getProgramProgress(userId); return buildAiCoachSessionContext(session, { nextSessionUnlocksAt: isWaitingForNextSession(userId) ? progress.nextSessionUnlocksAt : null, }); } export function getWaitingForNextSession(userId: string): { sessionOrder: number; unlocksAt: string; title: string; } | null { const progress = getProgramProgress(userId); if (!isWaitingForNextSession(userId) || !progress.nextSessionUnlocksAt || !progress.waitingForSessionOrder) { return null; } const session = getSessionByOrder(progress.waitingForSessionOrder); return { sessionOrder: progress.waitingForSessionOrder, unlocksAt: progress.nextSessionUnlocksAt, title: session?.title ?? `جلسه ${progress.waitingForSessionOrder}`, }; }