import Foundation private struct PersistedAddressState: Codable { let selectedId: String? let display: String let latitude: Double? let longitude: Double? } private struct PersistedCartAddonState: Codable { let id: String let name: String let quantity: Int let unitPrice: Double } private struct PersistedCartItemState: Codable { let id: String let productId: String let storeId: String let name: String let imageURL: String? let details: String? let addons: [PersistedCartAddonState] let quantity: Int let unitPrice: Double } private struct PersistedCartState: Codable { let storeId: String? let storeName: String? let items: [PersistedCartItemState] let total: Double } private struct PersistedTrackedOrdersState: Codable { let orders: [PublicOrderResult] } enum SessionStateStore { private static let legacyAddressKey = "session.address.state.v1" private static let addressKeyPrefix = "session.address.state.v2." private static let activeUserKey = "session.active.user.v1" private static let cartKeyPrefix = "session.cart.state.v1." private static let trackedOrdersKeyPrefix = "session.orders.tracking.v1." private static let pendingCartOrderKeyPrefix = "session.cart.pending-order.v1." static func makeUserKey(profileId: String?, email: String?) -> String? { let id = (profileId ?? "") .trimmingCharacters(in: .whitespacesAndNewlines) .lowercased() if id.isEmpty == false { return "id:\(id)" } let mail = (email ?? "") .trimmingCharacters(in: .whitespacesAndNewlines) .lowercased() if mail.isEmpty == false { return "email:\(mail)" } return nil } static func setActiveUserKey(_ userKey: String?) { let trimmed = (userKey ?? "").trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { UserDefaults.standard.removeObject(forKey: activeUserKey) } else { UserDefaults.standard.set(trimmed, forKey: activeUserKey) } } static func loadActiveUserKey() -> String? { let value = UserDefaults.standard.string(forKey: activeUserKey)? .trimmingCharacters(in: .whitespacesAndNewlines) if let value, value.isEmpty == false { return value } return nil } private static func addressStorageKey(for userKey: String?) -> String { let key = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) let safe = key.replacingOccurrences(of: " ", with: "_") return addressKeyPrefix + safe } static func loadAddress() -> AddressState? { let defaults = UserDefaults.standard let activeKey = loadActiveUserKey() let scopedKey = addressStorageKey(for: activeKey) if let data = defaults.data(forKey: scopedKey), let decoded = try? JSONDecoder().decode(PersistedAddressState.self, from: data) { return AddressState( selectedId: decoded.selectedId, display: decoded.display.isEmpty ? "Defina seu endereco" : decoded.display, latitude: decoded.latitude, longitude: decoded.longitude, onboardingMessage: nil ) } // Backward-compatible fallback for data persisted before user scoping. let anonymousKey = addressStorageKey(for: "anonymous") if let data = defaults.data(forKey: anonymousKey), let decoded = try? JSONDecoder().decode(PersistedAddressState.self, from: data) { let recovered = AddressState( selectedId: decoded.selectedId, display: decoded.display.isEmpty ? "Defina seu endereco" : decoded.display, latitude: decoded.latitude, longitude: decoded.longitude, onboardingMessage: nil ) // Migrate anonymous cache into the current active user namespace. if let activeKey, activeKey.isEmpty == false { let payload = PersistedAddressState( selectedId: recovered.selectedId, display: recovered.display, latitude: recovered.latitude, longitude: recovered.longitude ) if let migratedData = try? JSONEncoder().encode(payload) { defaults.set(migratedData, forKey: scopedKey) } } return recovered } guard let data = defaults.data(forKey: legacyAddressKey), let decoded = try? JSONDecoder().decode(PersistedAddressState.self, from: data) else { return nil } let migrated = AddressState( selectedId: decoded.selectedId, display: decoded.display.isEmpty ? "Defina seu endereco" : decoded.display, latitude: decoded.latitude, longitude: decoded.longitude, onboardingMessage: nil ) saveAddress(migrated) defaults.removeObject(forKey: legacyAddressKey) return migrated } static func saveAddress(_ state: AddressState) { let payload = PersistedAddressState( selectedId: state.selectedId, display: state.display, latitude: state.latitude, longitude: state.longitude ) guard let data = try? JSONEncoder().encode(payload) else { return } UserDefaults.standard.set(data, forKey: addressStorageKey(for: nil)) } static func clearAddress() { let defaults = UserDefaults.standard defaults.removeObject(forKey: addressStorageKey(for: nil)) defaults.removeObject(forKey: legacyAddressKey) } static func clearActiveUser() { UserDefaults.standard.removeObject(forKey: activeUserKey) } private static func cartStorageKey(for userKey: String?) -> String { let key = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) let safe = key.replacingOccurrences(of: " ", with: "_") return cartKeyPrefix + safe } static func loadCart() -> CartState? { let defaults = UserDefaults.standard let key = cartStorageKey(for: nil) if let data = defaults.data(forKey: key), let decoded = try? JSONDecoder().decode(PersistedCartState.self, from: data) { return CartState( storeId: decoded.storeId, storeName: decoded.storeName, items: decoded.items.map { CartItemState( id: $0.id, productId: $0.productId, storeId: $0.storeId, name: $0.name, imageURL: $0.imageURL, details: $0.details, addons: $0.addons.map { CartItemAddonState( id: $0.id, name: $0.name, quantity: $0.quantity, unitPrice: $0.unitPrice ) }, quantity: $0.quantity, unitPrice: $0.unitPrice ) }, total: decoded.total ) } return nil } static func saveCart(_ state: CartState) { let payload = PersistedCartState( storeId: state.storeId, storeName: state.storeName, items: state.items.map { PersistedCartItemState( id: $0.id, productId: $0.productId, storeId: $0.storeId, name: $0.name, imageURL: $0.imageURL, details: $0.details, addons: $0.addons.map { PersistedCartAddonState( id: $0.id, name: $0.name, quantity: $0.quantity, unitPrice: $0.unitPrice ) }, quantity: $0.quantity, unitPrice: $0.unitPrice ) }, total: state.total ) guard let data = try? JSONEncoder().encode(payload) else { return } UserDefaults.standard.set(data, forKey: cartStorageKey(for: nil)) } static func clearCart() { UserDefaults.standard.removeObject(forKey: cartStorageKey(for: nil)) } private static func trackedOrdersStorageKey(for userKey: String?) -> String { let key = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) let safe = key.replacingOccurrences(of: " ", with: "_") return trackedOrdersKeyPrefix + safe } private static func pendingCartOrderStorageKey(for userKey: String?) -> String { let key = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) let safe = key.replacingOccurrences(of: " ", with: "_") return pendingCartOrderKeyPrefix + safe } static func loadTrackedOrders() -> [PublicOrderResult] { let defaults = UserDefaults.standard let key = trackedOrdersStorageKey(for: nil) guard let data = defaults.data(forKey: key), let decoded = try? JSONDecoder().decode(PersistedTrackedOrdersState.self, from: data) else { return [] } return decoded.orders } static func loadTrackedOrder(orderId: String) -> PublicOrderResult? { loadTrackedOrders().first(where: { $0.id == orderId }) } static func saveTrackedOrder(_ order: PublicOrderResult) { var orders = loadTrackedOrders() if let index = orders.firstIndex(where: { $0.id == order.id }) { orders[index] = order } else { orders.insert(order, at: 0) } if orders.count > 60 { orders = Array(orders.prefix(60)) } let payload = PersistedTrackedOrdersState(orders: orders) guard let data = try? JSONEncoder().encode(payload) else { return } UserDefaults.standard.set(data, forKey: trackedOrdersStorageKey(for: nil)) } static func clearTrackedOrders() { UserDefaults.standard.removeObject(forKey: trackedOrdersStorageKey(for: nil)) } static func savePendingCartOrderId(_ orderId: String) { let clean = orderId.trimmingCharacters(in: .whitespacesAndNewlines) guard clean.isEmpty == false else { clearPendingCartOrder() return } UserDefaults.standard.set(clean, forKey: pendingCartOrderStorageKey(for: nil)) } static func loadPendingCartOrderId() -> String? { let value = UserDefaults.standard.string(forKey: pendingCartOrderStorageKey(for: nil))? .trimmingCharacters(in: .whitespacesAndNewlines) if let value, value.isEmpty == false { return value } return nil } static func clearPendingCartOrder() { UserDefaults.standard.removeObject(forKey: pendingCartOrderStorageKey(for: nil)) } }