import { BookProps, BookListProps } from '@/lib/models/Book'; import { CharacterProps } from '@/lib/models/Character'; import { Conversation } from '@/lib/models/QuillSense'; import { WorldProps } from '@/lib/models/World'; import { ChapterProps } from '@/lib/models/Chapter'; /** * OfflineDataService - Manages data retrieval with offline/online mode * Automatically caches to local DB when online, serves from local DB when offline */ export class OfflineDataService { private isOffline: boolean = false; /** * Set offline status (called by OfflineProvider) */ setOfflineStatus(offline: boolean): void { this.isOffline = offline; } /** * Get current offline status */ getOfflineStatus(): boolean { return this.isOffline; } /** * Get all books - from local DB if offline, from server otherwise */ async getBooks(fetchFromServer: () => Promise): Promise { if (!window.electron) { return await fetchFromServer(); } if (this.isOffline) { // Fetch from local DB const result = await window.electron.dbGetBooks(); if (result.success && result.data) { const titles = result.data.map(b => b.title).join(', '); console.log(`📚 Books from LOCAL DB: ${titles}`); return result.data; } throw new Error(result.error || 'Failed to get books from local DB'); } else { // Fetch from server and save to local DB const books = await fetchFromServer(); const titles = books.map(b => b.title).join(', '); console.log(`📚 Books from SERVER: ${titles}`); // Save to local DB in background for (const book of books) { try { await window.electron.dbSaveBook(book); } catch (error) { console.error('Failed to save book to local DB:', error); } } return books; } } /** * Get single book - from local DB if offline, from server otherwise */ async getBook( bookId: string, fetchFromServer: () => Promise ): Promise { if (!window.electron) { return await fetchFromServer(); } if (this.isOffline) { const result = await window.electron.dbGetBook(bookId); if (result.success && result.data) { console.log(`📖 "${result.data.title}" from LOCAL DB`); return result.data; } throw new Error(result.error || 'Book not found in local DB'); } else { const book = await fetchFromServer(); console.log(`📖 "${book.title}" from SERVER`); // Save to local DB try { await window.electron.dbSaveBook(book); } catch (error) { console.error('Failed to save book to local DB:', error); } return book; } } /** * Create new book - creates on server if online, local UUID if offline */ async createBook( bookData: Omit, authorId: string, createOnServer: () => Promise ): Promise { if (!window.electron) { return await createOnServer(); } if (this.isOffline) { // Generate local UUID and save to local DB const localBookId = crypto.randomUUID(); const book: BookProps = { ...bookData, bookId: localBookId }; await window.electron.dbSaveBook(book, authorId); console.log(`💾 Book "${book.title}" created locally (offline mode)`); return localBookId; } else { // Create on server and save to local DB const serverBookId = await createOnServer(); const book: BookProps = { ...bookData, bookId: serverBookId }; await window.electron.dbSaveBook(book, authorId); return serverBookId; } } /** * Save book - save to local DB and sync to server later if offline */ async saveBook( book: BookProps, authorId: string | undefined, saveToServer: () => Promise ): Promise { if (!window.electron) { return await saveToServer(); } // Always save to local DB first await window.electron.dbSaveBook(book, authorId); if (!this.isOffline) { // Also save to server try { await saveToServer(); } catch (error) { console.error('Failed to save to server, will sync later:', error); // Data is already in local DB, will be synced later } } else { console.log(`💾 Book queued for sync (offline mode)`); } } /** * Get characters for a book */ async getCharacters( bookId: string, fetchFromServer: () => Promise ): Promise { if (!window.electron) { return await fetchFromServer(); } if (this.isOffline) { const result = await window.electron.dbGetCharacters(bookId); if (result.success && result.data) { const names = result.data.map(c => c.name).join(', '); console.log(`👤 Characters from LOCAL DB: ${names}`); return result.data; } throw new Error(result.error || 'Failed to get characters from local DB'); } else { const characters = await fetchFromServer(); const names = characters.map(c => c.name).join(', '); console.log(`👤 Characters from SERVER: ${names}`); // Save to local DB for (const character of characters) { try { await window.electron.dbSaveCharacter(character, bookId); } catch (error) { console.error('Failed to save character to local DB:', error); } } return characters; } } /** * Create character */ async createCharacter( characterData: Omit, bookId: string, createOnServer: () => Promise ): Promise { if (!window.electron) { return await createOnServer(); } if (this.isOffline) { const localId = crypto.randomUUID(); const character = { ...characterData, id: localId }; await window.electron.dbSaveCharacter(character, bookId); console.log(`💾 Character "${character.name}" created locally (offline mode)`); return localId; } else { const serverId = await createOnServer(); const character = { ...characterData, id: serverId }; await window.electron.dbSaveCharacter(character, bookId); return serverId; } } /** * Save character */ async saveCharacter( character: CharacterProps, bookId: string, saveToServer: () => Promise ): Promise { if (!window.electron) { return await saveToServer(); } // Always save to local DB first await window.electron.dbSaveCharacter(character, bookId); if (!this.isOffline) { try { await saveToServer(); } catch (error) { console.error('Failed to save to server, will sync later:', error); } } else { console.log(`💾 Character queued for sync (offline mode)`); } } /** * Get conversations for a book */ async getConversations( bookId: string, fetchFromServer: () => Promise ): Promise { if (!window.electron) { return await fetchFromServer(); } if (this.isOffline) { const result = await window.electron.dbGetConversations(bookId); if (result.success) { const titles = result.data?.map((c: any) => c.title).join(', ') || 'none'; console.log(`💬 Conversations from LOCAL DB: ${titles}`); return result.data || []; } throw new Error(result.error || 'Failed to get conversations from local DB'); } else { const conversations = await fetchFromServer(); const titles = conversations.map(c => c.title).join(', '); console.log(`💬 Conversations from SERVER: ${titles}`); // Save to local DB for (const conversation of conversations) { try { await window.electron.dbSaveConversation(conversation, bookId); } catch (error) { console.error('Failed to save conversation to local DB:', error); } } return conversations; } } /** * Create conversation */ async createConversation( conversationData: Omit, bookId: string, createOnServer: () => Promise ): Promise { if (!window.electron) { return await createOnServer(); } if (this.isOffline) { const localId = crypto.randomUUID(); const conversation = { ...conversationData, id: localId }; await window.electron.dbSaveConversation(conversation, bookId); console.log(`💾 Conversation "${conversation.title}" created locally (offline mode)`); return localId; } else { const serverId = await createOnServer(); const conversation = { ...conversationData, id: serverId }; await window.electron.dbSaveConversation(conversation, bookId); return serverId; } } /** * Save conversation */ async saveConversation( conversation: Conversation, bookId: string, saveToServer: () => Promise ): Promise { if (!window.electron) { return await saveToServer(); } // Always save to local DB first await window.electron.dbSaveConversation(conversation, bookId); if (!this.isOffline) { try { await saveToServer(); } catch (error) { console.error('Failed to save to server, will sync later:', error); } } else { console.log(`💾 Conversation queued for sync (offline mode)`); } } /** * Create chapter */ async createChapter( chapterData: Omit, bookId: string, createOnServer: () => Promise ): Promise { if (!window.electron) { return await createOnServer(); } if (this.isOffline) { const localId = crypto.randomUUID(); const chapter = { ...chapterData, chapterId: localId }; await window.electron.dbSaveChapter(chapter, bookId); console.log(`💾 Chapter "${chapter.title}" created locally (offline mode)`); return localId; } else { const serverId = await createOnServer(); const chapter = { ...chapterData, chapterId: serverId }; await window.electron.dbSaveChapter(chapter, bookId); return serverId; } } /** * Save chapter content */ async saveChapter( chapter: ChapterProps, bookId: string, saveToServer: () => Promise ): Promise { if (!window.electron) { return await saveToServer(); } // Always save to local DB first await window.electron.dbSaveChapter(chapter, bookId); if (!this.isOffline) { try { await saveToServer(); } catch (error) { console.error('Failed to save to server, will sync later:', error); } } else { console.log(`💾 Chapter "${chapter.title}" queued for sync (offline mode)`); } } } // Singleton instance let offlineDataServiceInstance: OfflineDataService | null = null; /** * Get OfflineDataService singleton instance */ export function getOfflineDataService(): OfflineDataService { if (!offlineDataServiceInstance) { offlineDataServiceInstance = new OfflineDataService(); } return offlineDataServiceInstance; }