import Foundation @MainActor @Observable final class AIHistoryManager { static let shared = AIHistoryManager() private(set) var entries: [AIHistoryEntry] = [] private let defaults: UserDefaults private let storageKey = "reddora.ai.history.entries" private let maxEntries = 30 private let persistQueue = DispatchQueue(label: "com.app-for-reddit.ai-history.persist", qos: .utility) init(defaults: UserDefaults = .standard) { self.defaults = defaults load() } var isEmpty: Bool { entries.isEmpty } func entries(matching filter: AIHistoryFilter) -> [AIHistoryEntry] { guard let toolKind = filter.toolKind else { return entries } return entries.filter { $0.toolKind == toolKind } } func save(_ entry: AIHistoryEntry) { entries.removeAll { $0.id == entry.id } entries.insert(entry, at: 0) enforceLimit() persist() } func delete(id: UUID) { guard entries.contains(where: { $0.id == id }) else { return } entries.removeAll { $0.id == id } persist() } func deleteAll() { guard !entries.isEmpty else { return } entries.removeAll() persist() } private func load() { guard let data = defaults.data(forKey: storageKey) else { entries = [] return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 entries = try decoder.decode([AIHistoryEntry].self, from: data) let hadExcessEntries = entries.count > maxEntries enforceLimit() if hadExcessEntries { persist() } } catch { entries = [] } } private func enforceLimit() { guard entries.count > maxEntries else { return } entries = Array(entries.prefix(maxEntries)) } private func persist() { let snapshot = entries let storageKey = storageKey let defaults = defaults persistQueue.async { do { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 let data = try encoder.encode(snapshot) defaults.set(data, forKey: storageKey) } catch { // Keep in-memory entries if persistence fails. } } } }