import { apiFetch } from './client'; import { API_ENDPOINTS } from './config'; export interface BrandResponse { brand_id: string; name: string; slug: string; logo_url: string | null; is_active: boolean; created_at: string; } export interface CategoryResponse { category_id: string; parent_category_id: string | null; name: string; slug: string; description: string | null; image_url: string | null; sort_order: string; is_parent_feature?: boolean; is_active: boolean; product_count?: number; created_at: string; } // --- Parent Hierarchy Types --- export interface HierarchyModelItem { model_id: string; name: string; slug: string; image_url: string | null; } export interface HierarchySeriesItem { series_id: string; name: string; slug: string; models: HierarchyModelItem[]; } export interface HierarchyBrandItem { brand_id: string; name: string; slug: string; logo_url: string | null; series: HierarchySeriesItem[]; } export interface CategoryParentHierarchyResponse { category_id: string; brands: HierarchyBrandItem[]; } export interface DeviceSeriesResponse { series_id: string; brand_id: string; name: string; slug: string; sort_order: number; is_active: boolean; } export interface VariantAttributeResponse { id: string; variant_id: string; attribute_id: string; attribute_value: string; attribute_name?: string; attribute_code?: string; } export interface VariantImageResponse { image_id: string; variant_id: string; image_url: string; sort_order: number; is_primary: boolean; } export interface ProductVariantResponse { variant_id: string; product_id: string; sku: string; barcode: string | null; /** API may return decimal strings like "199.00" */ price: number | string; compare_price: number | string | null; cost_price: number | string; low_stock_threshold: number; status: string; available_stock?: number; attributes?: VariantAttributeResponse[]; images?: VariantImageResponse[]; } export interface ProductImageResponse { image_id: string; product_id: string; image_url: string; alt_text: string | null; sort_order: number; is_banner: boolean; } export interface ProductResponse { product_id: string; category_id: string; brand_id: string | null; device_series_id: string | null; device_model_id: string | null; name: string; slug: string; full_path: string; description: string | null; status: string; created_at: string; images: ProductImageResponse[]; variants: ProductVariantResponse[]; rating?: number; review_count?: number; badge?: 'HOT' | 'NEW' | 'SALE' | null; /** Present on list cards; used for Sale / Hot highlight filters */ discount_percent?: number; color?: string | null; colors?: string[]; brand_name?: string | null; } export interface DeviceModelResponse { model_id: string; series_id: string; brand_id: string; name: string; slug: string; full_path: string; release_year: number | null; image_url: string | null; is_active: boolean; } export interface ServiceTypeResponse { service_type_id: string; name: string; slug: string; icon_url: string | null; description: string | null; is_active: boolean; } export interface RepairServiceResponse { repair_service_id: string; model_id: string; service_type_id: string; slug: string; full_path: string; description: string | null; } export interface ProductCardResponse { product_id: string; category_id?: string; slug: string; name: string; thumbnail_url: string | null; /** List API returns decimal strings like "199.00" */ price: number | string; compare_price: number | string | null; discount_percent: number; rating: number; stock_count: number; first_variant_id?: string | null; badge: string | null; brand_name: string | null; color?: string | null; colors?: string[]; } export interface ProductPaginatedResponse { total: number; page: number; limit: number; cursor: string | null; products: ProductCardResponse[]; } export const catalogService = { async getBrands(categoryId?: string): Promise { try { const url = categoryId ? `${API_ENDPOINTS.CATALOG_BRANDS_ALL}?category_id=${categoryId}` : API_ENDPOINTS.CATALOG_BRANDS_ALL; return await apiFetch(url); } catch { return MOCK_BRANDS; } }, async getCategories(): Promise { try { return await apiFetch(API_ENDPOINTS.CATALOG_CATEGORIES_ALL); } catch { return MOCK_CATEGORIES; } }, async getProducts(): Promise { try { const data = await apiFetch(`${API_ENDPOINTS.CATALOG_PRODUCTS_ALL}?page=1&limit=48`); const products = Array.isArray(data) ? data : data?.products || []; return products; } catch { return []; } }, async getProductsPaginated(params: Record = {}): Promise { const query = new URLSearchParams(); Object.entries(params).forEach(([key, val]) => { if (val !== undefined && val !== null && val !== '') { query.append(key, String(val)); } }); try { const queryString = query.toString(); const endpoint = `/api/v1/catalog/products${queryString ? '?' + queryString : ''}`; return await apiFetch(endpoint); } catch (e) { const limit = Number(params.limit) || 24; const page = Number(params.page) || 1; let cards = MOCK_PRODUCTS.map((p) => { const firstVar = p.variants[0]; const price = firstVar?.price ? Number(firstVar.price) : 0; const compare_price = firstVar?.compare_price ? Number(firstVar.compare_price) : null; let discount_percent = 0; if (price && compare_price && compare_price > price) { discount_percent = Math.round(((compare_price - price) / compare_price) * 100); } const colorAttr = firstVar?.attributes?.find((a) => `${a.attribute_code || ''} ${a.attribute_name || ''}`.toLowerCase().includes('color') ); return { product_id: p.product_id, category_id: p.category_id, slug: p.slug, name: p.name, thumbnail_url: p.images[0]?.image_url || null, price, compare_price, discount_percent, rating: p.rating || 4.8, stock_count: 15, badge: p.badge || null, brand_name: MOCK_BRANDS.find((b) => b.brand_id === p.brand_id)?.name || 'Apple', brand_id: p.brand_id, color: colorAttr?.attribute_value || null, colors: colorAttr?.attribute_value ? [colorAttr.attribute_value] : [], }; }); if (params.category) { cards = cards.filter((p) => p.category_id === params.category); } if (params.brand) { cards = cards.filter( (p) => p.brand_id === params.brand || String(p.brand_name).toLowerCase() === String(params.brand).toLowerCase() ); } if (params.search) { const q = String(params.search).toLowerCase(); cards = cards.filter( (p) => p.name.toLowerCase().includes(q) || String(p.color || '').toLowerCase().includes(q) || String(p.brand_name || '').toLowerCase().includes(q) ); } const total = cards.length; const start = (page - 1) * limit; return { total, page, limit, cursor: null, products: cards.slice(start, start + limit), }; } }, async getProductDetail(slug: string): Promise { try { return await apiFetch(`/api/v1/catalog/products/detail/${slug}`); } catch (e) { const found = MOCK_PRODUCTS.find(p => p.slug === slug) || MOCK_PRODUCTS[0]; return { product: found, reviews: [ { review_id: 'rev_1', author_name: 'Alex Morgan', rating: 5, verified_buyer: true, review_date: '2026-07-20', title: 'Exceptional Quality!', comment: 'Exceptional build quality and super-fast delivery!' } ], related_products: MOCK_PRODUCTS.filter(p => p.product_id !== found.product_id), compatible_devices: [ { model_id: 'm1', name: 'iPhone 15 Pro Max', slug: 'iphone-15-pro-max' } ], specifications: [ { label: 'Manufacturer', value: 'Apple' }, { label: 'Model Reference', value: found.name } ] }; } }, async getProductsBySlugs(slugs: string[]): Promise { const unique = [...new Set(slugs.map((s) => String(s || '').trim()).filter(Boolean))]; if (!unique.length) return []; const cards = await Promise.all( unique.map(async (slug) => { try { const data = await apiFetch(`/api/v1/catalog/products/detail/${slug}`); const p = data?.product || data; if (!p?.slug && !p?.product_id) return null; const variant = Array.isArray(p.variants) ? p.variants[0] : null; const thumb = p.thumbnail_url || p.images?.[0]?.image_url || variant?.images?.[0]?.image_url || null; return { product_id: p.product_id || slug, category_id: p.category_id, slug: p.slug || slug, name: p.name || slug, thumbnail_url: thumb, price: variant?.price ?? p.price ?? 0, compare_price: variant?.compare_price ?? p.compare_price ?? null, discount_percent: Number(p.discount_percent) || 0, rating: Number(p.rating) || 5, stock_count: Number(p.stock_count) || 0, first_variant_id: variant?.variant_id || null, badge: p.badge || null, brand_name: p.brand_name || null, } as ProductCardResponse; } catch { return null; } }) ); return cards.filter((c): c is ProductCardResponse => Boolean(c)); }, async getDeviceModels(): Promise { try { return await apiFetch(API_ENDPOINTS.CATALOG_DEVICE_MODELS_ALL); } catch { return MOCK_DEVICE_MODELS; } }, async getServiceTypes(): Promise { try { return await apiFetch(API_ENDPOINTS.CATALOG_SERVICE_TYPES_ALL); } catch { return MOCK_SERVICE_TYPES; } }, async getRepairServices(): Promise { try { return await apiFetch(API_ENDPOINTS.CATALOG_REPAIR_SERVICES_ALL); } catch { return []; } }, async getParentHierarchy(categoryId: string): Promise { try { return await apiFetch( `/api/v1/storefront/catalog/categories/${categoryId}/parent-hierarchy` ); } catch { return null; } }, async getDeviceSeries(): Promise { try { return await apiFetch('/api/v1/catalog/device-series/all'); } catch { return []; } }, }; // Fallback catalog mock dataset (empty by default to ensure only real API data renders) export const MOCK_BRANDS: BrandResponse[] = []; export const MOCK_CATEGORIES: CategoryResponse[] = []; export const MOCK_PRODUCTS: ProductResponse[] = []; export const MOCK_DEVICE_MODELS: DeviceModelResponse[] = []; export const MOCK_SERVICE_TYPES: ServiceTypeResponse[] = [];