import type { UiMessage } from "@/data/dto/chat"; import { resolveChatCacheOwnerKey } from "@/data/repositories/chat_cache_identity"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { Logger } from "@/utils/logger"; import { Result } from "@/utils/result"; import { todayString } from "@/utils/date"; import { CHAT_HISTORY_LIMIT } from "./helper/history"; import { localMessagesToUi } from "./helper/message-mappers"; const log = new Logger("StoresChatChatHistorySync"); export type ReadAndSyncHistoryOutput = { /** Network-authoritative messages. Empty network history includes a UI greeting. */ messages: UiMessage[]; /** True when network history was written back to local storage. */ localOverwritten: boolean; localCount: number; networkCount: number; total: number; limit: number; }; export type LocalHistorySnapshotOutput = { /** Local cached messages. Empty local history includes a UI greeting. */ messages: UiMessage[]; localCount: number; }; export type NetworkHistorySyncOutput = ReadAndSyncHistoryOutput; export function createGreetingMessage(): UiMessage { return { content: "You're here! Facebook is so restrictive, " + "there were things I couldn't say there. " + "Finally I can relax. How was your day out?", isFromAI: true, date: todayString(), isSynthetic: true, }; } export async function resolveHistoryCacheIdentity(): Promise { const result = await resolveChatCacheOwnerKey(); return Result.isOk(result) ? result.data : null; } export async function readLocalHistorySnapshot( cacheIdentity: string | null, ): Promise { const chatRepo = await loadChatRepository(); const greetingMessage = createGreetingMessage(); const localResult = cacheIdentity ? await chatRepo.getLocalMessages(cacheIdentity) : null; const localMessages = localResult && Result.isOk(localResult) && localResult.data ? localMessagesToUi(localResult.data) : []; log.debug("[chat-machine] loadHistory LOCAL DONE", { count: localMessages.length, }); const snapshotMessages = localMessages.length === 0 ? [greetingMessage] : localMessages; if (snapshotMessages[0] === greetingMessage) { log.debug("[chat-machine] loadHistory EMPTY -> prepend greeting (local)"); } return { messages: snapshotMessages, localCount: localMessages.length, }; } export async function syncNetworkHistory( localCount: number, cacheIdentity: string | null, ): Promise { const chatRepo = await loadChatRepository(); const greetingMessage = createGreetingMessage(); const networkResult = await chatRepo.getHistory(CHAT_HISTORY_LIMIT, 0); if (Result.isErr(networkResult)) { log.error("[chat-machine] loadHistory NETWORK FAILED", { error: networkResult.error, }); return null; } const networkUi = localMessagesToUi(networkResult.data.messages); log.debug("[chat-machine] loadHistory NETWORK DONE", { count: networkUi.length, }); if (cacheIdentity) { void chatRepo.prefetchMediaForMessages( networkResult.data.messages, cacheIdentity, ); } const saveResult = cacheIdentity ? await chatRepo.saveMessagesToLocal( networkResult.data.messages, cacheIdentity, ) : null; const localOverwritten = saveResult !== null && Result.isOk(saveResult); log.debug("[chat-machine] loadHistory SAVE TO LOCAL DONE", { localOverwritten, }); const finalMessages = networkUi.length === 0 ? [greetingMessage] : networkUi; if (finalMessages[0] === greetingMessage) { log.debug("[chat-machine] loadHistory EMPTY -> prepend greeting"); } return { messages: finalMessages, localOverwritten, localCount, networkCount: networkUi.length, total: networkResult.data.total, limit: networkResult.data.limit, }; } /** * History sync flow: * 1. Read local history for fallback. * 2. Read network history as the authoritative source. * 3. Overwrite local history with network data. */ export async function readAndSyncHistory(): Promise { const cacheIdentity = await resolveHistoryCacheIdentity(); const localSnapshot = await readLocalHistorySnapshot(cacheIdentity); const networkSnapshot = await syncNetworkHistory( localSnapshot.localCount, cacheIdentity, ); if (networkSnapshot) return networkSnapshot; return { messages: localSnapshot.messages, localOverwritten: false, localCount: localSnapshot.localCount, networkCount: 0, total: 0, limit: CHAT_HISTORY_LIMIT, }; }