using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; public record CartItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity) { public long LineTotal => UnitPrice * Quantity; } public class CartService { private readonly ShopingCartContract.ShopingCartContractClient _client; private readonly List _items = new(); public event Action? OnChange; public CartService(ShopingCartContract.ShopingCartContractClient client) { _client = client; _ = LoadFromServerAsync(); } public IReadOnlyList Items => _items.AsReadOnly(); public long Total => _items.Sum(i => i.LineTotal); public int Count => _items.Sum(i => i.Quantity); public async Task Add(Product product, int quantity = 1) { if (quantity <= 0) return; var existing = _items.FirstOrDefault(i => i.ProductId == product.Id); if (existing is null) { _items.Add(new CartItem(product.Id, product.Title, product.ImageUrl, product.Price, quantity)); } else { var idx = _items.IndexOf(existing); _items[idx] = existing with { Quantity = existing.Quantity + quantity }; } Notify(); try { await _client.AddNewUserCartAsync(new AddNewUserCartRequest { ProductId = product.Id, Count = quantity }); } catch { // Best-effort sync with backend; keep local state on failure } } public async Task UpdateQuantity(long productId, int quantity) { var existing = _items.FirstOrDefault(i => i.ProductId == productId); if (existing is null) return; if (quantity <= 0) { Remove(productId); return; } var idx = _items.IndexOf(existing); _items[idx] = existing with { Quantity = quantity }; Notify(); try { await _client.UpdateUserCartAsync(new UpdateUserCartRequest { UserCartId = productId, Count = quantity }); } catch { // Best-effort sync with backend; keep local state on failure } } public async Task Remove(long productId) { _items.RemoveAll(i => i.ProductId == productId); Notify(); try { // Interpret Count=0 as remove from cart await _client.UpdateUserCartAsync(new UpdateUserCartRequest { UserCartId = productId, Count = 0 }); } catch { // Best-effort sync with backend; keep local state on failure } } public async Task Clear() { var productIds = _items.Select(i => i.ProductId).ToList(); _items.Clear(); Notify(); try { foreach (var id in productIds) { await _client.UpdateUserCartAsync(new UpdateUserCartRequest { UserCartId = id, Count = 0 }); } } catch { // Best-effort sync with backend; keep local state on failure } } private void Notify() => OnChange?.Invoke(); private async Task LoadFromServerAsync() { try { var response = await _client.GetAllUserCartAsync(new Empty()); _items.Clear(); foreach (var m in response.Models) { var item = new CartItem( ProductId: m.Id, Title: m.Title ?? string.Empty, ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath, UnitPrice: m.Price, Quantity: m.SaleCount > 0 ? m.SaleCount : 1); _items.Add(item); } Notify(); } catch { // If backend is unreachable or user is unauthenticated, fall back to local-only cart. } } }