import { Injectable, signal, computed, effect } from '@angular/core'; import { ApiService } from './api.service'; import { Item, CartItem } from '../models'; import { getDiscountedPrice } from '../utils/item.utils'; import { environment } from '../../environments/environment'; import type { } from '../types/telegram.types'; @Injectable({ providedIn: 'root' }) export class CartService { private readonly STORAGE_KEY = `${environment.brandName.toLowerCase().replace(/\s+/g, '_')}_cart`; private cartItems = signal([]); private isTelegram = typeof window !== 'undefined' && !!window.Telegram?.WebApp; private addingItems = new Set(); items = this.cartItems.asReadonly(); itemCount = computed(() => { const items = this.cartItems(); if (!Array.isArray(items)) return 0; return items.reduce((total, item) => total + item.quantity, 0); }); totalPrice = computed(() => { const items = this.cartItems(); if (!Array.isArray(items)) return 0; return items.reduce((total, item) => { return total + (getDiscountedPrice(item) * item.quantity); }, 0); }); constructor(private apiService: ApiService) { this.loadCart(); // Auto-save whenever cart changes effect(() => { const items = this.cartItems(); this.saveToStorage(items); }); } private saveToStorage(items: CartItem[]): void { const data = JSON.stringify(items); // Always save to localStorage localStorage.setItem(this.STORAGE_KEY, data); // Also save to Telegram CloudStorage if available if (this.isTelegram) { window.Telegram!.WebApp.CloudStorage.setItem(this.STORAGE_KEY, data, (err) => { if (err) { console.error('Error saving to Telegram CloudStorage:', err); } }); } } private loadCart(): void { if (this.isTelegram) { // Load from Telegram CloudStorage first window.Telegram!.WebApp.CloudStorage.getItem(this.STORAGE_KEY, (err, value) => { if (err) { console.error('Error loading from Telegram CloudStorage:', err); this.loadFromLocalStorage(); } else if (value) { this.parseAndSetCart(value) || this.loadFromLocalStorage(); } else { // No data in CloudStorage, try localStorage this.loadFromLocalStorage(); } }); } else { this.loadFromLocalStorage(); } } private loadFromLocalStorage(): void { const stored = localStorage.getItem(this.STORAGE_KEY); if (stored) { this.parseAndSetCart(stored); } } /** Parse JSON cart data, migrate legacy items, and set the signal. Returns true on success. */ private parseAndSetCart(json: string): boolean { try { const items = JSON.parse(json); if (Array.isArray(items)) { this.cartItems.set(items.map(item => ({ ...item, quantity: item.quantity || 1 }))); return true; } } catch (err) { console.error('Error parsing cart data:', err); } this.cartItems.set([]); return false; } addItem(itemID: number, quantity: number = 1): void { // Prevent duplicate API calls for same item if (this.addingItems.has(itemID)) return; const currentItems = this.cartItems(); const existingItem = currentItems.find(i => i.itemID === itemID); if (existingItem) { // Item exists, increase quantity this.updateQuantity(itemID, existingItem.quantity + quantity); } else { // Get item details from API and add to cart this.addingItems.add(itemID); this.apiService.getItem(itemID).subscribe({ next: (item) => { const cartItem: CartItem = { ...item, quantity }; this.cartItems.set([...this.cartItems(), cartItem]); this.addingItems.delete(itemID); }, error: (err) => { console.error('Error adding to cart:', err); this.addingItems.delete(itemID); } }); } } updateQuantity(itemID: number, quantity: number): void { if (quantity <= 0) { this.removeItem(itemID); return; } const currentItems = this.cartItems(); const updatedItems = currentItems.map(item => item.itemID === itemID ? { ...item, quantity } : item ); this.cartItems.set(updatedItems); } removeItems(itemIDs: number[]): void { const currentItems = this.cartItems(); const updatedItems = currentItems.filter(item => !itemIDs.includes(item.itemID)); this.cartItems.set(updatedItems); } removeItem(itemID: number): void { this.removeItems([itemID]); } clearCart(): void { this.cartItems.set([]); } }