import Foundation // MARK: - DTOs struct EmptyResult: Decodable {} 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 addressBook: [CustomerAddress]? enum CodingKeys: String, CodingKey { case id case name case email case phoneNumber case profilePicture case addressBook = "address_book" } } 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]? enum CodingKeys: String, CodingKey { case id case label case address case number case complement case neighborhood case city case state case zipCode case latLong = "lat_long" } } struct StoreSummary: Decodable { let id: String let name: String let logo: String? let cover: String? let category: String? let rating: Double? let deliveryTime: String? let deliveryFee: Double? let distance: Double? let isOpen: Bool? let statusLabel: String? } struct StoreInfoResult: Decodable { let isOpen: Bool? let statusLabel: String? let deliveryTime: String? let minOrder: Double? let address: StoreAddressInfo? let paymentMethods: StorePaymentMethodsInfo? enum CodingKeys: String, CodingKey { case isOpen case statusLabel 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) 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 latitude: Double? let longitude: Double? enum CodingKeys: String, CodingKey { case street case number case neighborhood case city case state 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) latitude = ApiService.decodeFlexibleDouble(from: container, keys: [.latitude]) longitude = ApiService.decodeFlexibleDouble(from: container, keys: [.longitude]) } } struct StorePaymentMethodsInfo: Decodable { let acceptPix: Bool? let acceptCash: Bool? let acceptCreditCard: Bool? let acceptDebitCard: Bool? } struct StoreCatalogCategory: Decodable { let id: String let name: String let products: [StoreCatalogProduct] enum CodingKeys: String, CodingKey { case id case name 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" products = (try? container.decode([StoreCatalogProduct].self, forKey: .products)) ?? [] } } struct StoreCatalogProduct: Decodable, Identifiable { let id: String let name: String let description: String? let image: String? let price: Double? let originalPrice: Double? let addonGroups: [StoreAddonGroup] enum CodingKeys: String, CodingKey { case id case name case description case desc case image case cover case photo case price case originalPrice case oldPrice 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 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]) addonGroups = (try? container.decode([StoreAddonGroup].self, forKey: .addonGroups)) ?? (try? container.decode([StoreAddonGroup].self, forKey: .addons)) ?? [] } } 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 PublicCategory: Decodable { let id: String let name: String let icon: String? } struct CustomerProfileUpdatePayload: Encodable { let addressBook: [CustomerAddressPayload] enum CodingKeys: String, CodingKey { case addressBook = "address_book" } } struct CustomerAddressPayload: Encodable { 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]? enum CodingKeys: String, CodingKey { case label case address case number case complement case neighborhood case city case state case zipCode case latLong = "lat_long" } init(from address: CustomerAddress) { self.label = address.label self.address = address.address self.number = address.number self.complement = address.complement self.neighborhood = address.neighborhood self.city = address.city self.state = address.state self.zipCode = address.zipCode self.latLong = address.latLong } } 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 } }