import 'package:dio/dio.dart'; import '../../../../core/constants/api_constants.dart'; import '../../../../core/errors/exceptions.dart'; import '../../../../core/network/dio_client.dart'; import '../models/category_model.dart'; /// Remote data source for categories API operations abstract class CategoryRemoteDataSource { /// Fetch all categories from the API Future> fetchCategories(); /// Fetch a single category by ID Future fetchCategoryById(String id); /// Sync categories (bulk update/create) Future syncCategories(List categories); } /// Implementation of CategoryRemoteDataSource using Dio class CategoryRemoteDataSourceImpl implements CategoryRemoteDataSource { final DioClient _dioClient; CategoryRemoteDataSourceImpl(this._dioClient); @override Future> fetchCategories() async { try { final response = await _dioClient.get(ApiConstants.categories); if (response.statusCode == ApiConstants.statusOk) { final data = response.data; // Handle different response structures List categoriesJson; if (data is Map) { // Response wrapped in object: { "categories": [...] } categoriesJson = data['categories'] as List? ?? data['data'] as List? ?? []; } else if (data is List) { // Direct array response categoriesJson = data; } else { throw DataParsingException('Unexpected response format'); } return categoriesJson .map((json) => CategoryModel.fromJson(json as Map)) .toList(); } else { throw ServerException( 'Failed to fetch categories', response.statusCode ?? 500, ); } } on AppException { rethrow; } catch (e, stackTrace) { throw NetworkException( 'Failed to fetch categories: ${e.toString()}', null, e, stackTrace, ); } } @override Future fetchCategoryById(String id) async { try { final response = await _dioClient.get(ApiConstants.categoryById(id)); if (response.statusCode == ApiConstants.statusOk) { final data = response.data; // Handle different response structures Map categoryJson; if (data is Map) { // Check if category is wrapped in a key categoryJson = data['category'] as Map? ?? data['data'] as Map? ?? data; } else { throw DataParsingException('Unexpected response format'); } return CategoryModel.fromJson(categoryJson); } else if (response.statusCode == ApiConstants.statusNotFound) { throw NotFoundException('Category with ID $id not found'); } else { throw ServerException( 'Failed to fetch category', response.statusCode ?? 500, ); } } on AppException { rethrow; } catch (e, stackTrace) { throw NetworkException( 'Failed to fetch category: ${e.toString()}', null, e, stackTrace, ); } } @override Future syncCategories(List categories) async { try { final categoriesJson = categories.map((c) => c.toJson()).toList(); final response = await _dioClient.post( ApiConstants.syncCategories, data: { 'categories': categoriesJson, }, ); if (response.statusCode != ApiConstants.statusOk && response.statusCode != ApiConstants.statusCreated) { throw ServerException( 'Failed to sync categories', response.statusCode ?? 500, ); } } on AppException { rethrow; } catch (e, stackTrace) { throw NetworkException( 'Failed to sync categories: ${e.toString()}', null, e, stackTrace, ); } } } /// Mock implementation for testing and development class CategoryRemoteDataSourceMock implements CategoryRemoteDataSource { /// Simulate network delay Future _delay() async { await Future.delayed( Duration(milliseconds: ApiConstants.mockApiDelay), ); } @override Future> fetchCategories() async { await _delay(); // Return mock categories return [ CategoryModel( id: '1', name: 'Electronics', description: 'Electronic devices and accessories', iconPath: 'assets/icons/electronics.png', color: '#2196F3', productCount: 25, createdAt: DateTime.now().subtract(const Duration(days: 60)), ), CategoryModel( id: '2', name: 'Clothing', description: 'Fashion and apparel', iconPath: 'assets/icons/clothing.png', color: '#E91E63', productCount: 50, createdAt: DateTime.now().subtract(const Duration(days: 50)), ), CategoryModel( id: '3', name: 'Food & Beverages', description: 'Food, drinks, and snacks', iconPath: 'assets/icons/food.png', color: '#FF9800', productCount: 100, createdAt: DateTime.now().subtract(const Duration(days: 40)), ), CategoryModel( id: '4', name: 'Home & Garden', description: 'Home improvement and garden supplies', iconPath: 'assets/icons/home.png', color: '#4CAF50', productCount: 30, createdAt: DateTime.now().subtract(const Duration(days: 30)), ), ]; } @override Future fetchCategoryById(String id) async { await _delay(); // Return mock category return CategoryModel( id: id, name: 'Sample Category $id', description: 'This is sample category $id', iconPath: 'assets/icons/category.png', color: '#2196F3', productCount: 10, createdAt: DateTime.now().subtract(const Duration(days: 30)), ); } @override Future syncCategories(List categories) async { await _delay(); // Mock sync - do nothing } }