import Foundation // MARK: - BookVoicePreferences // Manages per-book voice overrides with global fallback. // Persisted in UserDefaults as a slug → voice dictionary. @MainActor final class BookVoicePreferences: ObservableObject { static let shared = BookVoicePreferences() @Published private(set) var bookVoices: [String: String] = [:] private let key = "v2.bookVoicePreferences" private init() { if let data = UserDefaults.standard.data(forKey: key), let decoded = try? JSONDecoder().decode([String: String].self, from: data) { bookVoices = decoded } } // MARK: - Public API func voice(for slug: String) -> String? { bookVoices[slug] } /// Voice priority: book override → globalVoice → "af_bella" func voiceWithFallback(for slug: String, globalVoice: String) -> String { bookVoices[slug] ?? globalVoice } func setVoice(_ voice: String, for slug: String) { bookVoices[slug] = voice save() } func removeVoice(for slug: String) { bookVoices.removeValue(forKey: slug) save() } func hasOverride(for slug: String) -> Bool { bookVoices[slug] != nil } func clearAll() { bookVoices.removeAll() save() } // MARK: - Persistence private func save() { if let encoded = try? JSONEncoder().encode(bookVoices) { UserDefaults.standard.set(encoded, forKey: key) } } }