import Foundation import SwiftUI struct PizzaProductDetailSheet: View { let category: StoreCatalogCategory let storeId: String let resolveImageURL: (String?) -> String? let currentQuantityForItemId: (String) -> Int let onAdd: (CartItemState) -> Void @Environment(\.dismiss) var dismiss @State var selectedSizeId: String? = nil @State var selectedDoughId: String? = nil @State var selectedCrustId: String? = nil @State var selectedFlavorIds: Set = [] @State var flavorAddonQuantities: [String: [String: Int]] = [:] @State var selectedFlavorForAddons: StoreCatalogProduct? = nil @State var quantity: Int = 1 var flavors: [StoreCatalogProduct] { category.products } var pizzaConfig: StorePizzaConfig? { category.pizzaConfig } var sizes: [StorePizzaSize] { pizzaConfig?.sizes ?? [] } var doughs: [StorePizzaDough] { (pizzaConfig?.doughs ?? []).filter { $0.active ?? true } } var crusts: [StorePizzaCrust] { (pizzaConfig?.crusts ?? []).filter { $0.active ?? true } } private var representativeImage: String? { let firstImage = flavors .compactMap(\.image) .first { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false } return resolveImageURL(firstImage) } private var selectedSize: StorePizzaSize? { guard let selectedSizeId else { return nil } return sizes.first(where: { $0.id == selectedSizeId }) } private var selectedDoughName: String? { guard let selectedDoughId else { return nil } return doughs.first(where: { $0.id == selectedDoughId })?.name } private var selectedCrust: StorePizzaCrust? { guard let selectedCrustId else { return nil } return crusts.first(where: { $0.id == selectedCrustId }) } var maxFlavorsAllowed: Int { max(1, selectedSize?.maxFlavors ?? 1) } var isDoughReady: Bool { selectedSizeId != nil && (doughs.isEmpty || selectedDoughId != nil) } var isCrustReady: Bool { isDoughReady && (crusts.isEmpty || selectedCrustId != nil) } var canShowFlavors: Bool { isCrustReady } private var selectedFlavorProducts: [StoreCatalogProduct] { flavors .filter { selectedFlavorIds.contains($0.id) } .sorted { $0.name < $1.name } } private var canConfirm: Bool { selectedSizeId != nil && isDoughReady && isCrustReady && selectedFlavorIds.isEmpty == false && quantity > 0 } private var crustPriceModifier: Double { selectedCrust?.priceModifier ?? 0 } private var addonsTotal: Double { selectedFlavorProducts.reduce(0) { partial, flavor in let byAddon = flavorAddonQuantities[flavor.id] ?? [:] let pricesByAddon = Dictionary(uniqueKeysWithValues: flavor.addonGroups.flatMap(\.items).map { ($0.id, $0.price ?? 0) }) let subtotal = byAddon.reduce(0.0) { line, pair in line + (Double(max(0, pair.value)) * (pricesByAddon[pair.key] ?? 0)) } return partial + subtotal } } private var basePizzaPrice: Double { selectedFlavorProducts .map { flavor in guard let selectedSizeId else { return flavor.price ?? 0 } return flavor.pizzaPrices[selectedSizeId] ?? flavor.price ?? 0 } .max() ?? 0 } private var unitPrice: Double { basePizzaPrice + crustPriceModifier + addonsTotal } private var totalPrice: Double { unitPrice * Double(quantity) } private var cartItemId: String { var tokens: [String] = [] if let selectedSizeId { tokens.append("size:\(selectedSizeId)") } if let selectedDoughId { tokens.append("dough:\(selectedDoughId)") } if let selectedCrustId { tokens.append("crust:\(selectedCrustId)") } let flavorsToken = selectedFlavorIds.sorted().joined(separator: ",") tokens.append("flavors:\(flavorsToken)") let addonsToken = flavorAddonQuantities .flatMap { flavorId, addons in addons .filter { $0.value > 0 } .map { "\(flavorId):\($0.key):\($0.value)" } } .sorted() .joined(separator: ",") if addonsToken.isEmpty == false { tokens.append("addons:\(addonsToken)") } return "\(storeId)::pizza::\(category.id)::" + tokens.joined(separator: "|") } private var selectedAddonsPayload: [CartItemAddonState] { var payload: [CartItemAddonState] = [] for flavor in selectedFlavorProducts { let byAddon = flavorAddonQuantities[flavor.id] ?? [:] let addonMap = Dictionary(uniqueKeysWithValues: flavor.addonGroups.flatMap(\.items).map { ($0.id, $0) }) for (addonId, qty) in byAddon { guard qty > 0, let addon = addonMap[addonId] else { continue } payload.append( CartItemAddonState( id: "\(flavor.id)::\(addon.id)", name: "\(flavor.name) • \(addon.name)", quantity: qty, unitPrice: addon.price ?? 0 ) ) } } return payload } private var selectedDetailsText: String? { var chunks: [String] = [] if let selectedSizeName = selectedSize?.name { chunks.append("Tamanho: \(selectedSizeName)") } if let selectedDoughName, selectedDoughName.isEmpty == false { chunks.append("Massa: \(selectedDoughName)") } if let crustName = selectedCrust?.name, crustName.isEmpty == false { chunks.append("Borda: \(crustName)") } if selectedFlavorProducts.isEmpty == false { chunks.append("Sabores: " + selectedFlavorProducts.map(\.name).joined(separator: ", ")) } return chunks.isEmpty ? nil : chunks.joined(separator: " • ") } private var addButtonTitle: String { if canConfirm == false { return "Selecione as opções" } return "Adicionar • \(formatCurrency(totalPrice))" } var body: some View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 16) { AsyncStoreImage(imageURL: representativeImage) .frame(height: 220) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous)) Text("Pizza de varios sabores") .font(AppTypography.heading2) .foregroundStyle(AppColors.textPrimary) Text("Escolha o tamanho da sua fome") .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) Text(formatCurrency(unitPrice)) .font(AppTypography.heading2) .foregroundStyle(AppColors.primary) stepSizes if selectedSizeId != nil { stepDoughs } if isDoughReady { stepCrusts } if canShowFlavors { stepFlavors } } .padding(20) .padding(.bottom, 90) } .appBottomSafeAreaInset { HStack(spacing: 12) { HStack(spacing: 10) { Button(action: { if quantity > 1 { quantity -= 1 } }) { Image(systemName: "minus") .font(.system(size: 14, weight: .bold)) .foregroundStyle(AppColors.textPrimary) .frame(width: 32, height: 32) .background(AppColors.surface) .clipShape(Circle()) } .buttonStyle(.plain) .disabled(quantity <= 1) Text("\(quantity)") .font(AppTypography.heading3) .foregroundStyle(AppColors.textPrimary) .frame(minWidth: 20) Button(action: { quantity += 1 }) { Image(systemName: "plus") .font(.system(size: 14, weight: .bold)) .foregroundStyle(AppColors.textPrimary) .frame(width: 32, height: 32) .background(AppColors.surface) .clipShape(Circle()) } .buttonStyle(.plain) } .padding(.horizontal, 8) .frame(height: 48) .background(AppColors.surface) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) PrimaryButton(title: addButtonTitle) { guard canConfirm else { return } let item = CartItemState( id: cartItemId, productId: selectedFlavorProducts.first?.id ?? category.id, storeId: storeId, name: "Pizza de varios sabores", imageURL: representativeImage, details: selectedDetailsText, addons: selectedAddonsPayload, quantity: quantity, unitPrice: unitPrice ) onAdd(item) dismiss() } .disabled(canConfirm == false) } .padding(.horizontal, 20) .padding(.top, 8) .padding(.bottom, 12) .background(.ultraThinMaterial) } .sheet(item: $selectedFlavorForAddons) { flavor in NavigationStack { PizzaFlavorAddonsSheet( flavor: flavor, quantities: Binding( get: { flavorAddonQuantities[flavor.id] ?? [:] }, set: { flavorAddonQuantities[flavor.id] = $0 } ) ) } } .background(AppColors.backgroundLight.ignoresSafeArea()) .navigationTitle("Monte sua pizza") .appInlineNavigationTitle() .appTopBarTrailingToolbar { Button("Fechar") { dismiss() } .foregroundStyle(AppColors.primary) } .onAppear { applyAutoSelections() let existing = currentQuantityForItemId(cartItemId) if existing > 0 { quantity = existing } } .onChange(of: selectedSizeId) { _, _ in trimFlavorSelectionByLimit() applyAutoSelections() } .onChange(of: selectedFlavorIds) { _, newValue in let selected = newValue flavorAddonQuantities = flavorAddonQuantities.filter { selected.contains($0.key) } } } }