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] } struct OrderReviewRecord: Codable, Identifiable, Hashable { var id: String { orderId } let orderId: String let storeId: String? let shortId: String? let storeName: String? let storeLogoURL: String? let createdAt: String? let submittedAt: String let rating: Int let comment: String let orderPositiveTags: [String]? let orderImprovementTags: [String]? let deliverySentiment: String? let deliveryPositiveTags: [String]? let deliveryNegativeTags: [String]? let appNps: Int? let platform: String? let editableUntil: String? let storeReplyUntil: String? let reviewWindowExpiresAt: String? let storeReplyMessage: String? let storeReplyAt: String? } struct OrderReviewDraftState: Codable, Hashable { var orderId: String var orderRate: Int var orderComment: String var orderPositiveTags: [String] var orderImprovementTags: [String] var deliverySentiment: String var deliveryPositiveTags: [String] var deliveryNegativeTags: [String] var appNps: Int var platform: String } private struct PersistedOrderReviewsState: Codable { let reviews: [OrderReviewRecord] } 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." private static let orderReviewsKeyPrefix = "session.orders.reviews.v1." private static let orderReviewDraftKeyPrefix = "session.orders.review-draft.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 } private static func orderReviewsStorageKey(for userKey: String?) -> String { let key = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) let safe = key.replacingOccurrences(of: " ", with: "_") return orderReviewsKeyPrefix + safe } private static func orderReviewDraftStorageKey(for orderId: String, userKey: String?) -> String { let scope = (userKey ?? loadActiveUserKey() ?? "anonymous") .trimmingCharacters(in: .whitespacesAndNewlines) .replacingOccurrences(of: " ", with: "_") let id = orderId .trimmingCharacters(in: .whitespacesAndNewlines) .lowercased() return "\(orderReviewDraftKeyPrefix)\(scope).\(id)" } 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)) } static func loadOrderReviews() -> [OrderReviewRecord] { let key = orderReviewsStorageKey(for: nil) guard let data = UserDefaults.standard.data(forKey: key), let decoded = try? JSONDecoder().decode(PersistedOrderReviewsState.self, from: data) else { return [] } return decoded.reviews.sorted { lhs, rhs in lhs.submittedAt > rhs.submittedAt } } static func loadOrderReview(orderId: String) -> OrderReviewRecord? { let normalized = orderId.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard normalized.isEmpty == false else { return nil } return loadOrderReviews().first { review in review.orderId.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized } } static func hasOrderReview(orderId: String) -> Bool { loadOrderReview(orderId: orderId) != nil } static func saveOrderReview(_ review: OrderReviewRecord) { let cleanId = review.orderId.trimmingCharacters(in: .whitespacesAndNewlines) guard cleanId.isEmpty == false else { return } var reviews = loadOrderReviews() if let index = reviews.firstIndex(where: { $0.orderId.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == cleanId.lowercased() }) { reviews[index] = review } else { reviews.insert(review, at: 0) } let payload = PersistedOrderReviewsState(reviews: reviews) guard let data = try? JSONEncoder().encode(payload) else { return } UserDefaults.standard.set(data, forKey: orderReviewsStorageKey(for: nil)) } static func loadOrderReviewDraft(orderId: String) -> OrderReviewDraftState? { let key = orderReviewDraftStorageKey(for: orderId, userKey: nil) guard let data = UserDefaults.standard.data(forKey: key), let draft = try? JSONDecoder().decode(OrderReviewDraftState.self, from: data) else { return nil } return draft } static func saveOrderReviewDraft(_ draft: OrderReviewDraftState) { let key = orderReviewDraftStorageKey(for: draft.orderId, userKey: nil) guard let data = try? JSONEncoder().encode(draft) else { return } UserDefaults.standard.set(data, forKey: key) } static func clearOrderReviewDraft(orderId: String) { let key = orderReviewDraftStorageKey(for: orderId, userKey: nil) UserDefaults.standard.removeObject(forKey: key) } }