import Foundation import Combine // MARK: - ViewModel @MainActor final class ShareViewModel: ObservableObject { // MARK: Published state @Published var shelves: [ShelfSummary] = [] @Published var books: [BookSummary] = [] @Published var chapters: [ChapterSummary] = [] @Published var selectedShelf: ShelfSummary? @Published var selectedBook: BookSummary? @Published var selectedChapter: ChapterSummary? @Published var pageTitle: String = "" @Published var isLoading: Bool = false @Published var errorMessage: String? @Published var isSaved: Bool = false // MARK: Read-only let sharedText: String let isConfigured: Bool let serverURL: String // MARK: Private private let apiService: ShareAPIServiceProtocol private let defaults: UserDefaults? private let lastShelfIDKey = "shareExtension.lastShelfID" private let lastBookIDKey = "shareExtension.lastBookID" // MARK: Computed var isSaveDisabled: Bool { pageTitle.trimmingCharacters(in: .whitespaces).isEmpty || selectedBook == nil || isLoading } // MARK: - Init init( sharedText: String, apiService: ShareAPIServiceProtocol, serverURL: String = "", isConfigured: Bool = true, defaults: UserDefaults? = nil ) { self.sharedText = sharedText self.isConfigured = isConfigured self.serverURL = serverURL self.apiService = apiService self.defaults = defaults // Auto-populate title from the first non-empty line of the shared text. let firstLine = sharedText .components(separatedBy: .newlines) .first { !$0.trimmingCharacters(in: .whitespaces).isEmpty } ?? "" self.pageTitle = String(firstLine.prefix(100)) } // MARK: - Actions /// Loads all shelves and restores the last used shelf/book selection. func loadShelves() async { isLoading = true errorMessage = nil defer { isLoading = false } do { shelves = try await apiService.fetchShelves() // Restore last selected shelf let lastShelfID = defaults?.integer(forKey: lastShelfIDKey) ?? 0 if lastShelfID != 0, let match = shelves.first(where: { $0.id == lastShelfID }) { await selectShelf(match) } } catch { errorMessage = error.localizedDescription } } func selectShelf(_ shelf: ShelfSummary) async { selectedShelf = shelf selectedBook = nil selectedChapter = nil books = [] chapters = [] defaults?.set(shelf.id, forKey: lastShelfIDKey) isLoading = true defer { isLoading = false } do { books = try await apiService.fetchBooks(shelfId: shelf.id) // Restore last selected book let lastBookID = defaults?.integer(forKey: lastBookIDKey) ?? 0 if lastBookID != 0, let match = books.first(where: { $0.id == lastBookID }) { await selectBook(match) } } catch { errorMessage = error.localizedDescription } } func selectBook(_ book: BookSummary) async { selectedBook = book selectedChapter = nil chapters = [] defaults?.set(book.id, forKey: lastBookIDKey) isLoading = true defer { isLoading = false } do { chapters = try await apiService.fetchChapters(bookId: book.id) } catch { errorMessage = error.localizedDescription } } func savePage() async { guard let book = selectedBook, !pageTitle.trimmingCharacters(in: .whitespaces).isEmpty else { return } isLoading = true errorMessage = nil defer { isLoading = false } do { _ = try await apiService.createPage( bookId: book.id, chapterId: selectedChapter?.id, title: pageTitle.trimmingCharacters(in: .whitespaces), markdown: sharedText ) isSaved = true } catch { errorMessage = error.localizedDescription } } }