import Foundation /// Encrypted local message cache. /// Matches Python: chat_core.py message cache (message_cache/{conv_id}.json) enum MessageCache { /// Save messages for a conversation (encrypted with local storage key) static func save(email: String, convId: String, messages: [[String: Any]], cacheKey: Data?) throws { let dir = try KeyStorage.getKeyDir(email: email).appendingPathComponent("message_cache") try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) let jsonData = try JSONSerialization.data(withJSONObject: messages) let dataToWrite: Data if let cacheKey = cacheKey { dataToWrite = try CryptoUtils.encryptLocal(jsonData, key: cacheKey) } else { dataToWrite = jsonData } try dataToWrite.write(to: dir.appendingPathComponent("\(convId).json"), options: .completeFileProtection) } /// Load messages for a conversation static func load(email: String, convId: String, cacheKey: Data?) -> [[String: Any]]? { guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil } let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json") guard let raw = try? Data(contentsOf: path) else { return nil } let jsonData: Data if let cacheKey = cacheKey { if let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) { jsonData = decrypted } else { // Plaintext fallback (migration) jsonData = raw } } else { jsonData = raw } return try? JSONSerialization.jsonObject(with: jsonData) as? [[String: Any]] } /// Search messages in a conversation static func search(email: String, convId: String, query: String, cacheKey: Data?) -> [[String: Any]] { guard let messages = load(email: email, convId: convId, cacheKey: cacheKey) else { return [] } let lowerQuery = query.lowercased() return messages.filter { msg in if let text = msg["text"] as? String, text.lowercased().contains(lowerQuery) { return true } return false } } /// Delete cache for a conversation static func delete(email: String, convId: String) { guard let dir = try? KeyStorage.getKeyDir(email: email) else { return } let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json") try? FileManager.default.removeItem(at: path) } }