import Foundation // MARK: - DTOs struct EmptyResult: Decodable {} struct ProfilePatchEnvelope: Decodable { let error: Bool let code: String? let message: String? let profilePictureUrl: String? } struct RegistrationResult: Decodable { let id: String? let name: String? let email: String? } struct LoginResult: Decodable { let token: String let customer: CustomerProfile? } struct CustomerProfile: Decodable { let id: String let name: String let email: String let phoneNumber: String? let profilePicture: String? let favorites: [String]? let addressBook: [CustomerAddress]? let notificationsEnabled: Bool? let faceIdEnabled: Bool? enum CodingKeys: String, CodingKey { case id case name case email case phoneNumber case profilePicture case favorites case addressBook = "address_book" case notificationsEnabled case faceIdEnabled } } struct CustomerAddress: Decodable { let id: String? let label: String? let address: String? let number: String? let complement: String? let neighborhood: String? let city: String? let state: String? let zipCode: String? let latLong: [Double]? let isDefault: Bool? init(id: String?, label: String?, address: String?, number: String?, complement: String?, neighborhood: String?, city: String?, state: String?, zipCode: String?, latLong: [Double]?, isDefault: Bool?) { self.id = id; self.label = label; self.address = address self.number = number; self.complement = complement self.neighborhood = neighborhood; self.city = city self.state = state; self.zipCode = zipCode self.latLong = latLong; self.isDefault = isDefault } enum CodingKeys: String, CodingKey { case id, label, address, number, complement case neighborhood, city, state, zipCode case latLong = "lat_long" case isDefault } } struct StoreSummary: Decodable { let id: String let name: String let logo: String? let cover: String? let category: String? let rating: Double? let reviewsCount: Int? let positiveReviews: Int? let deliveryTime: String? let deliveryFee: Double? let distance: Double? let isOpen: Bool? let statusLabel: String? enum CodingKeys: String, CodingKey { case id case name case logo case cover case category case rating case reviewsCount case totalReviews case reviews case positiveReviews case positive_reviews case deliveryTime case deliveryFee case distance case isOpen case statusLabel } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString name = (try? container.decode(String.self, forKey: .name)) ?? "Loja" logo = try? container.decode(String.self, forKey: .logo) cover = try? container.decode(String.self, forKey: .cover) category = try? container.decode(String.self, forKey: .category) rating = ApiService.decodeFlexibleDouble(from: container, keys: [.rating]) reviewsCount = ApiService.decodeFlexibleInt(from: container, keys: [.reviewsCount, .totalReviews, .reviews]) positiveReviews = ApiService.decodeFlexibleInt(from: container, keys: [.positiveReviews, .positive_reviews]) deliveryTime = try? container.decode(String.self, forKey: .deliveryTime) deliveryFee = ApiService.decodeFlexibleDouble(from: container, keys: [.deliveryFee]) distance = ApiService.decodeFlexibleDouble(from: container, keys: [.distance]) isOpen = try? container.decode(Bool.self, forKey: .isOpen) statusLabel = try? container.decode(String.self, forKey: .statusLabel) } } struct StoreInfoResult: Decodable { let isOpen: Bool? let statusLabel: String? let fantasyName: String? let phone: String? let whatsapp: String? let logo: String? let cover: String? let deliveryTime: String? let minOrder: Double? let address: StoreAddressInfo? let paymentMethods: StorePaymentMethodsInfo? enum CodingKeys: String, CodingKey { case isOpen case statusLabel case fantasyName case phone case whatsapp case logo case cover case deliveryTime case minOrder case address case paymentMethods } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) isOpen = try? container.decode(Bool.self, forKey: .isOpen) statusLabel = try? container.decode(String.self, forKey: .statusLabel) fantasyName = try? container.decode(String.self, forKey: .fantasyName) phone = ApiService.decodeFlexibleString(from: container, keys: [.phone]) whatsapp = ApiService.decodeFlexibleString(from: container, keys: [.whatsapp, .phone]) logo = try? container.decode(String.self, forKey: .logo) cover = try? container.decode(String.self, forKey: .cover) deliveryTime = try? container.decode(String.self, forKey: .deliveryTime) minOrder = ApiService.decodeFlexibleDouble(from: container, keys: [.minOrder]) address = try? container.decode(StoreAddressInfo.self, forKey: .address) paymentMethods = try? container.decode(StorePaymentMethodsInfo.self, forKey: .paymentMethods) } } struct StoreAddressInfo: Decodable { let street: String? let number: String? let neighborhood: String? let city: String? let state: String? let zipCode: String? let latitude: Double? let longitude: Double? enum CodingKeys: String, CodingKey { case street case number case neighborhood case city case state case zipCode case zipcode case latitude case longitude } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) street = try? container.decode(String.self, forKey: .street) number = try? container.decode(String.self, forKey: .number) neighborhood = try? container.decode(String.self, forKey: .neighborhood) city = try? container.decode(String.self, forKey: .city) state = try? container.decode(String.self, forKey: .state) zipCode = (try? container.decode(String.self, forKey: .zipCode)) ?? (try? container.decode(String.self, forKey: .zipcode)) latitude = ApiService.decodeFlexibleDouble(from: container, keys: [.latitude]) longitude = ApiService.decodeFlexibleDouble(from: container, keys: [.longitude]) } } struct StorePaymentMethodsInfo: Decodable { let paymentOnDelivery: Bool? let paymentOnPickup: Bool? let acceptPix: Bool? let acceptCash: Bool? let acceptCreditCard: Bool? let acceptDebitCard: Bool? let acceptCreditVisa: Bool? let acceptCreditMaster: Bool? let acceptCreditElo: Bool? let acceptCreditAmex: Bool? let acceptCreditHipercard: Bool? let acceptDebitVisa: Bool? let acceptDebitMaster: Bool? let acceptDebitElo: Bool? let acceptVoucherAlelo: Bool? let acceptVoucherSodexo: Bool? let acceptVoucherTicket: Bool? let acceptVoucherVR: Bool? enum CodingKeys: String, CodingKey { case paymentOnDelivery case paymentOnPickup case acceptPix case acceptCash case acceptCreditCard case acceptDebitCard case acceptCreditVisa case acceptCreditMaster case acceptCreditElo case acceptCreditAmex case acceptCreditHipercard case acceptDebitVisa case acceptDebitMaster case acceptDebitElo case acceptVoucherAlelo case acceptVoucherSodexo case acceptVoucherTicket case acceptVoucherVR } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) paymentOnDelivery = try? container.decode(Bool.self, forKey: .paymentOnDelivery) paymentOnPickup = try? container.decode(Bool.self, forKey: .paymentOnPickup) acceptPix = try? container.decode(Bool.self, forKey: .acceptPix) acceptCash = try? container.decode(Bool.self, forKey: .acceptCash) acceptCreditCard = try? container.decode(Bool.self, forKey: .acceptCreditCard) acceptDebitCard = try? container.decode(Bool.self, forKey: .acceptDebitCard) acceptCreditVisa = try? container.decode(Bool.self, forKey: .acceptCreditVisa) acceptCreditMaster = try? container.decode(Bool.self, forKey: .acceptCreditMaster) acceptCreditElo = try? container.decode(Bool.self, forKey: .acceptCreditElo) acceptCreditAmex = try? container.decode(Bool.self, forKey: .acceptCreditAmex) acceptCreditHipercard = try? container.decode(Bool.self, forKey: .acceptCreditHipercard) acceptDebitVisa = try? container.decode(Bool.self, forKey: .acceptDebitVisa) acceptDebitMaster = try? container.decode(Bool.self, forKey: .acceptDebitMaster) acceptDebitElo = try? container.decode(Bool.self, forKey: .acceptDebitElo) acceptVoucherAlelo = try? container.decode(Bool.self, forKey: .acceptVoucherAlelo) acceptVoucherSodexo = try? container.decode(Bool.self, forKey: .acceptVoucherSodexo) acceptVoucherTicket = try? container.decode(Bool.self, forKey: .acceptVoucherTicket) acceptVoucherVR = try? container.decode(Bool.self, forKey: .acceptVoucherVR) } var hasAnyCreditCard: Bool { (acceptCreditCard ?? false) || (acceptCreditVisa ?? false) || (acceptCreditMaster ?? false) || (acceptCreditElo ?? false) || (acceptCreditAmex ?? false) || (acceptCreditHipercard ?? false) } var hasAnyDebitCard: Bool { (acceptDebitCard ?? false) || (acceptDebitVisa ?? false) || (acceptDebitMaster ?? false) || (acceptDebitElo ?? false) } var hasAnyVoucher: Bool { (acceptVoucherAlelo ?? false) || (acceptVoucherSodexo ?? false) || (acceptVoucherTicket ?? false) || (acceptVoucherVR ?? false) } } struct StoreCatalogCategory: Decodable { let id: String let name: String let isPizzaCategory: Bool let pizzaConfig: StorePizzaConfig? let products: [StoreCatalogProduct] enum CodingKeys: String, CodingKey { case id case name case isPizzaCategory case pizzaConfig case products } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString name = (try? container.decode(String.self, forKey: .name)) ?? "Categoria" isPizzaCategory = (try? container.decode(Bool.self, forKey: .isPizzaCategory)) ?? false pizzaConfig = try? container.decode(StorePizzaConfig.self, forKey: .pizzaConfig) products = (try? container.decode([StoreCatalogProduct].self, forKey: .products)) ?? [] } } struct StoreCatalogProduct: Decodable, Identifiable { let id: String let type: String? let name: String let description: String? let image: String? let price: Double? let originalPrice: Double? let pizzaPrices: [String: Double] let addonGroups: [StoreAddonGroup] enum CodingKeys: String, CodingKey { case id case type case name case description case desc case image case cover case photo case price case originalPrice case oldPrice case pizzaPrices case addonGroups case addons } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString type = try? container.decode(String.self, forKey: .type) name = (try? container.decode(String.self, forKey: .name)) ?? "Produto" description = (try? container.decode(String.self, forKey: .description)) ?? (try? container.decode(String.self, forKey: .desc)) image = (try? container.decode(String.self, forKey: .image)) ?? (try? container.decode(String.self, forKey: .cover)) ?? (try? container.decode(String.self, forKey: .photo)) price = ApiService.decodeFlexibleDouble(from: container, keys: [.price]) originalPrice = ApiService.decodeFlexibleDouble(from: container, keys: [.originalPrice, .oldPrice]) pizzaPrices = StoreCatalogProduct.decodePizzaPrices(container: container) addonGroups = (try? container.decode([StoreAddonGroup].self, forKey: .addonGroups)) ?? (try? container.decode([StoreAddonGroup].self, forKey: .addons)) ?? [] } private static func decodePizzaPrices(container: KeyedDecodingContainer) -> [String: Double] { if let direct = try? container.decode([String: Double].self, forKey: .pizzaPrices) { return direct } if let asInt = try? container.decode([String: Int].self, forKey: .pizzaPrices) { return asInt.mapValues { Double($0) } } if let asString = try? container.decode([String: String].self, forKey: .pizzaPrices) { var parsed: [String: Double] = [:] for (key, value) in asString { let normalized = value .trimmingCharacters(in: .whitespacesAndNewlines) .replacingOccurrences(of: ",", with: ".") if let number = Double(normalized) { parsed[key] = number } } return parsed } return [:] } } struct StoreAddonGroup: Decodable, Identifiable { let id: String let name: String let minSelectors: Int? let maxSelectors: Int? let items: [StoreAddonItem] enum CodingKeys: String, CodingKey { case id case name case minSelectors case maxSelectors case items } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString name = (try? container.decode(String.self, forKey: .name)) ?? "Adicionais" minSelectors = try? container.decode(Int.self, forKey: .minSelectors) maxSelectors = try? container.decode(Int.self, forKey: .maxSelectors) items = (try? container.decode([StoreAddonItem].self, forKey: .items)) ?? [] } } struct StoreAddonItem: Decodable, Identifiable { let id: String let name: String let price: Double? enum CodingKeys: String, CodingKey { case id case name case price } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString name = (try? container.decode(String.self, forKey: .name)) ?? "Item" price = ApiService.decodeFlexibleDouble(from: container, keys: [.price]) } } struct CepLookupResult: Decodable { let zipCode: String? let street: String? let neighborhood: String? let city: String? let state: String? let complement: String? let latitude: Double? let longitude: Double? enum CodingKeys: String, CodingKey { case zipCode case cep case zip case normalized case raw case street case logradouro case address case neighborhood case bairro case district case city case cidade case localidade case state case estado case uf case complement case complemento case latitude case lat case longitude case lng } enum NormalizedKeys: String, CodingKey { case cep case logradouro case bairro case cidade case uf case latitude case longitude } enum RawKeys: String, CodingKey { case cep case address case district case city case state case lat case lng } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let normalizedContainer = try? container.nestedContainer(keyedBy: NormalizedKeys.self, forKey: .normalized) let rawContainer = try? container.nestedContainer(keyedBy: RawKeys.self, forKey: .raw) let directZip = CepLookupResult.decodeString(from: container, keys: [.zipCode, .cep, .zip]) let directStreet = CepLookupResult.decodeString(from: container, keys: [.street, .logradouro, .address]) let directNeighborhood = CepLookupResult.decodeString(from: container, keys: [.neighborhood, .bairro, .district]) let directCity = CepLookupResult.decodeString(from: container, keys: [.city, .cidade, .localidade]) let directState = CepLookupResult.decodeString(from: container, keys: [.state, .estado, .uf]) let directComplement = CepLookupResult.decodeString(from: container, keys: [.complement, .complemento]) let directLatitude = CepLookupResult.decodeFlexibleDouble(from: container, keys: [.latitude, .lat]) let directLongitude = CepLookupResult.decodeFlexibleDouble(from: container, keys: [.longitude, .lng]) let normalizedZip = normalizedContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.cep]) } let normalizedStreet = normalizedContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.logradouro]) } let normalizedNeighborhood = normalizedContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.bairro]) } let normalizedCity = normalizedContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.cidade]) } let normalizedState = normalizedContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.uf]) } let normalizedLatitude = normalizedContainer.flatMap { CepLookupResult.decodeFlexibleDouble(from: $0, keys: [.latitude]) } let normalizedLongitude = normalizedContainer.flatMap { CepLookupResult.decodeFlexibleDouble(from: $0, keys: [.longitude]) } let rawZip = rawContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.cep]) } let rawStreet = rawContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.address]) } let rawNeighborhood = rawContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.district]) } let rawCity = rawContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.city]) } let rawState = rawContainer.flatMap { CepLookupResult.decodeString(from: $0, keys: [.state]) } let rawLatitude = rawContainer.flatMap { CepLookupResult.decodeFlexibleDouble(from: $0, keys: [.lat]) } let rawLongitude = rawContainer.flatMap { CepLookupResult.decodeFlexibleDouble(from: $0, keys: [.lng]) } zipCode = directZip ?? normalizedZip ?? rawZip street = directStreet ?? normalizedStreet ?? rawStreet neighborhood = directNeighborhood ?? normalizedNeighborhood ?? rawNeighborhood city = directCity ?? normalizedCity ?? rawCity state = directState ?? normalizedState ?? rawState complement = directComplement latitude = directLatitude ?? normalizedLatitude ?? rawLatitude longitude = directLongitude ?? normalizedLongitude ?? rawLongitude } private static func decodeString(from container: KeyedDecodingContainer, keys: [K]) -> String? { for key in keys { if let value = try? container.decode(String.self, forKey: key) { return value } } return nil } private static func decodeFlexibleDouble(from container: KeyedDecodingContainer, keys: [K]) -> Double? { for key in keys { if let value = try? container.decode(Double.self, forKey: key) { return value } if let valueAsString = try? container.decode(String.self, forKey: key), let parsed = Double(valueAsString.replacingOccurrences(of: ",", with: ".")) { return parsed } } return nil } }