import Foundation struct Product: Identifiable, Hashable { static func == (lhs: Product, rhs: Product) -> Bool { lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) } let id: Int let barcode: String let name: String let brand: String let imageUrl: String? let score: Int // 0-100 weighted index let novaGroup: Int // 1-4 let servingSize: String let calories: Double let fat: Double let saturatedFat: Double let carbs: Double let sugar: Double let fiber: Double let protein: Double let sodium: Double let ingredients: String let dietaryTags: [String] // ["vegan", "gluten-free", etc.] init(json: [String: Any]) { // Handle nested structures from API let rating = json["Rating"] as? [String: Any] ?? [:] let nutrition = json["Nutrition"] as? [String: Any] ?? [:] let dietary = json["Dietary"] as? [String: Any] ?? [:] id = JSON.parseInt(json["id"] ?? json["ID"] ?? json["productId"]) barcode = JSON.parseString(json["barcode"] ?? json["Barcode"] ?? json["upc"]) name = JSON.parseString(json["name"] ?? json["Name"] ?? json["productName"]) brand = JSON.parseString(json["brand"] ?? json["Brand"] ?? json["brandName"]) imageUrl = JSON.parseOptionalString(json["imageUrl"] ?? json["ImageURL"] ?? json["image"]) // Score from Rating.OverallScore or top-level score = JSON.parseInt(rating["OverallScore"] ?? json["score"] ?? json["Score"]) novaGroup = JSON.parseInt(json["novaGroup"] ?? json["NovaGroup"] ?? json["nova"]) // Nutrition from nested object or top-level servingSize = JSON.parseString(nutrition["ServingSize"] ?? json["servingSize"] ?? json["ServingSize"]) calories = JSON.parseDouble(nutrition["Calories"] ?? json["calories"] ?? json["Calories"]) fat = JSON.parseDouble(nutrition["Fat"] ?? json["fat"] ?? json["Fat"]) saturatedFat = JSON.parseDouble(nutrition["SaturatedFat"] ?? json["saturatedFat"] ?? json["SaturatedFat"]) carbs = JSON.parseDouble(nutrition["Carbohydrates"] ?? json["carbs"] ?? json["Carbs"]) sugar = JSON.parseDouble(nutrition["Sugars"] ?? json["sugar"] ?? json["Sugar"]) fiber = JSON.parseDouble(nutrition["Fiber"] ?? json["fiber"] ?? json["Fiber"]) protein = JSON.parseDouble(nutrition["Protein"] ?? json["protein"] ?? json["Protein"]) sodium = JSON.parseDouble(nutrition["Sodium"] ?? json["sodium"] ?? json["Sodium"]) ingredients = JSON.parseString(json["ingredients"] ?? json["Ingredients"]) // Build dietary tags from Dietary object or use existing array var tags: [String] = [] if JSON.parseBool(dietary["IsVegan"]) { tags.append("Vegan") } if JSON.parseBool(dietary["IsVegetarian"]) { tags.append("Vegetarian") } if JSON.parseBool(dietary["IsGlutenFree"]) { tags.append("Gluten-Free") } if JSON.parseBool(dietary["IsDairyFree"]) { tags.append("Dairy-Free") } if JSON.parseBool(dietary["IsNutFree"]) { tags.append("Nut-Free") } if JSON.parseBool(dietary["IsOrganic"]) { tags.append("Organic") } if tags.isEmpty { dietaryTags = JSON.parseStringArray(json["dietaryTags"] ?? json["DietaryTags"] ?? json["tags"]) } else { dietaryTags = tags } } init( id: Int, barcode: String, name: String, brand: String, imageUrl: String? = nil, score: Int, novaGroup: Int, servingSize: String, calories: Double, fat: Double, saturatedFat: Double, carbs: Double, sugar: Double, fiber: Double, protein: Double, sodium: Double, ingredients: String, dietaryTags: [String] ) { self.id = id self.barcode = barcode self.name = name self.brand = brand self.imageUrl = imageUrl self.score = score self.novaGroup = novaGroup self.servingSize = servingSize self.calories = calories self.fat = fat self.saturatedFat = saturatedFat self.carbs = carbs self.sugar = sugar self.fiber = fiber self.protein = protein self.sodium = sodium self.ingredients = ingredients self.dietaryTags = dietaryTags } // MARK: - Computed Properties var imageURL: URL? { guard let imageUrl = imageUrl else { return nil } return URL(string: imageUrl) } var scoreColor: ScoreColor { switch score { case 80...100: return .excellent case 50..<80: return .good case 30..<50: return .fair default: return .poor } } var novaColor: NovaColor { switch novaGroup { case 1: return .nova1 case 2: return .nova2 case 3: return .nova3 default: return .nova4 } } enum ScoreColor { case excellent, good, fair, poor var color: String { switch self { case .excellent: return "scoreGreen" case .good: return "scoreYellow" case .fair: return "scoreOrange" case .poor: return "scoreRed" } } } enum NovaColor { case nova1, nova2, nova3, nova4 var color: String { switch self { case .nova1: return "novaGreen" case .nova2: return "novaYellow" case .nova3: return "novaOrange" case .nova4: return "novaRed" } } var label: String { switch self { case .nova1: return "Unprocessed" case .nova2: return "Processed ingredients" case .nova3: return "Processed" case .nova4: return "Ultra-processed" } } } }