import Foundation import SwiftUI #if canImport(LCEssentials) import LCEssentials #endif struct ProductDetailSheet: View { let product: StoreCatalogProduct let imageURL: String? let storeId: String let currentQuantityForItemId: (String) -> Int let onAdd: (CartItemState) -> Void @Environment(\.dismiss) var dismiss @State var selectedAddonQuantities: [String: Int] = [:] @State var quantity: Int = 0 private var addonItemsById: [String: StoreAddonItem] { Dictionary(uniqueKeysWithValues: product.addonGroups.flatMap(\.items).map { ($0.id, $0) }) } private var selectedAddonItems: [(item: StoreAddonItem, quantity: Int)] { selectedAddonQuantities .compactMap { key, qty in guard qty > 0, let item = addonItemsById[key] else { return nil } return (item, qty) } .sorted { $0.item.name < $1.item.name } } private var addonsTotal: Double { selectedAddonItems.reduce(0) { partial, pair in partial + (Double(pair.quantity) * (pair.item.price ?? 0)) } } private var unitPrice: Double { (product.price ?? 0) + addonsTotal } private var totalPrice: Double { unitPrice * Double(quantity) } private var cartItemId: String { let addonKey = encodedAddonKey return "\(storeId)::\(product.id)::\(addonKey)" } private var selectedAddonsSummary: String? { let names = selectedAddonItems.map { pair in pair.quantity > 1 ? "\(pair.item.name) x\(pair.quantity)" : pair.item.name } if names.isEmpty { return nil } return names.joined(separator: ", ") } private var selectedAddonsPayload: [CartItemAddonState] { selectedAddonItems.map { pair in CartItemAddonState( id: pair.item.id, name: pair.item.name, quantity: pair.quantity, unitPrice: pair.item.price ?? 0 ) } } private var addButtonTitle: String { if quantity <= 0 { return "Remover do carrinho" } return "Atualizar • \(formatCurrency(totalPrice))" } var body: some View { LCENavigationView { content } .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() } .setTitle(text: Text("Detalhes").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary)) .setNavigationBarBackgroundColor(AppColors.backgroundLight) } private var content: some View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 16) { AsyncStoreImage(imageURL: imageURL) .frame(height: 220) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous)) Text(product.name) .font(AppTypography.heading2) .foregroundStyle(AppColors.textPrimary) if let description = product.description, description.isEmpty == false { Text(description) .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) } Text(formatCurrency(unitPrice)) .font(AppTypography.heading2) .foregroundStyle(AppColors.primary) if addonsTotal > 0 { Text("Inclui adicionais: \(formatCurrency(addonsTotal))") .font(.caption) .foregroundStyle(AppColors.textMuted) } else { Text("Sem adicionais") .font(.caption) .foregroundStyle(AppColors.textMuted) } if product.addonGroups.isEmpty == false { Text("Adicionais") .font(AppTypography.heading3) .foregroundStyle(AppColors.textPrimary) ForEach(product.addonGroups) { group in VStack(alignment: .leading, spacing: 8) { Text(group.name) .font(AppTypography.heading3) .foregroundStyle(AppColors.textPrimary) ForEach(group.items) { item in HStack(spacing: 10) { VStack(alignment: .leading, spacing: 4) { Text(item.name) .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) Text(String(format: "+ R$ %.2f", item.price ?? 0).replacingOccurrences(of: ".", with: ",")) .font(.caption) .foregroundStyle(AppColors.textMuted) } Spacer() HStack(spacing: 8) { Button(action: { decrementAddon(item.id) }) { Image(systemName: "minus") .font(.system(size: 12, weight: .bold)) .foregroundStyle(AppColors.textPrimary) .frame(width: 26, height: 26) .background(AppColors.brandSoft) .clipShape(Circle()) } .buttonStyle(.plain) .disabled(quantity(forAddonId: item.id) <= 0 || quantity <= 0) Text("\(quantity(forAddonId: item.id))") .font(AppTypography.heading3) .foregroundStyle(AppColors.textPrimary) .frame(minWidth: 18) Button(action: { incrementAddon(item.id) }) { Image(systemName: "plus") .font(.system(size: 12, weight: .bold)) .foregroundStyle(AppColors.textPrimary) .frame(width: 26, height: 26) .background(AppColors.tertiary) .clipShape(Circle()) } .buttonStyle(.plain) .disabled(quantity <= 0) } } } } .padding(12) .background(AppColors.surface) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) } } } .padding(20) .padding(.bottom, 80) } .appBottomSafeAreaInset { HStack(spacing: 12) { HStack(spacing: 10) { Button(action: { if quantity > 0 { 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 <= 0) 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)) .buttonStyle(.plain) PrimaryButton(title: addButtonTitle) { let item = CartItemState( id: cartItemId, productId: product.id, storeId: storeId, name: product.name, imageURL: imageURL, details: selectedAddonsSummary, addons: selectedAddonsPayload, quantity: quantity, unitPrice: unitPrice ) onAdd(item) dismiss() } } .padding(.horizontal, 20) .padding(.top, 8) .padding(.bottom, 12) .background(.ultraThinMaterial) } .background(AppColors.backgroundLight.ignoresSafeArea()) .appHiddenNavigationBar() .navigationBarBackButtonHidden(true) .onAppear { let existing = currentQuantityForItemId(cartItemId) quantity = existing > 0 ? existing : 1 } .onChange(of: selectedAddonQuantities) { _, _ in // Keep the main quantity stable when changing addon quantities. // Only hydrate from cart if this exact configuration already exists. let existingQuantity = currentQuantityForItemId(cartItemId) if existingQuantity > 0 { quantity = existingQuantity } } .onChange(of: quantity) { _, newValue in if newValue <= 0 { selectedAddonQuantities.removeAll() } } } private func formatCurrency(_ value: Double) -> String { String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",") } private var encodedAddonKey: String { let tokens = selectedAddonQuantities .filter { $0.value > 0 } .map { "\($0.key):\($0.value)" } .sorted() return tokens.isEmpty ? "base" : tokens.joined(separator: ",") } private func quantity(forAddonId addonId: String) -> Int { selectedAddonQuantities[addonId] ?? 0 } private func incrementAddon(_ addonId: String) { selectedAddonQuantities[addonId, default: 0] += 1 } private func decrementAddon(_ addonId: String) { let current = selectedAddonQuantities[addonId] ?? 0 if current <= 1 { selectedAddonQuantities.removeValue(forKey: addonId) } else { selectedAddonQuantities[addonId] = current - 1 } } }