import Foundation import Observation struct Bookmark: Codable, Identifiable { let id: UUID let createdAt: Date let itemId: String let episodeId: String? let itemTitle: String var title: String let chapterTitle: String? let time: Double } @Observable @MainActor final class BookmarkManager { private(set) var bookmarks: [Bookmark] = [] private var saveFile: URL { AppPaths.supportDirectory.appendingPathComponent("bookmarks.json") } init() { load() } func add(item: LibraryItem, time: Double, title: String, chapters: [Chapter]) { let chapter = chapters.last { $0.start <= time } let bm = Bookmark( id: UUID(), createdAt: Date(), itemId: item.id, episodeId: item.episodeId, itemTitle: item.title, title: title, chapterTitle: chapter?.title, time: time ) bookmarks.insert(bm, at: 0) save() } func delete(_ bookmark: Bookmark) { bookmarks.removeAll { $0.id == bookmark.id } save() } func bookmarks(for item: LibraryItem) -> [Bookmark] { bookmarks.filter { $0.itemId == item.id && $0.episodeId == item.episodeId } .sorted { $0.time < $1.time } } /// Replaces bookmarks for an item with the server-loaded list (keeps bookmarks for other items). func mergeFromServer(_ serverBookmarks: [ServerBookmark], for item: LibraryItem) { bookmarks.removeAll { $0.itemId == item.id && $0.episodeId == item.episodeId } let chapters = item.chapters for sb in serverBookmarks { let chapter = chapters.last { $0.start <= sb.time } let bm = Bookmark( id: UUID(), createdAt: sb.createdAt.map { Date(timeIntervalSince1970: $0 / 1000) } ?? Date(), itemId: item.id, episodeId: item.episodeId, itemTitle: item.title, title: sb.title, chapterTitle: chapter?.title, time: sb.time ) bookmarks.append(bm) } bookmarks.sort { $0.createdAt > $1.createdAt } save() } private func save() { try? FileManager.default.createDirectory( at: AppPaths.supportDirectory, withIntermediateDirectories: true) if let data = try? JSONEncoder().encode(bookmarks) { try? data.write(to: saveFile) } } private func load() { guard let data = try? Data(contentsOf: saveFile) else { return } bookmarks = (try? JSONDecoder().decode([Bookmark].self, from: data)) ?? [] } }