import 'dart:async' show unawaited, StreamController; import 'package:dartz/dartz.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/errors/failures.dart'; import 'package:superport/core/utils/debug_logger.dart'; import 'package:superport/data/datasources/remote/lookup_remote_datasource.dart'; import 'package:superport/data/models/lookups/lookup_data.dart'; /// 전역 Lookups 캐싱 서비스 (Singleton 패턴) @LazySingleton() class LookupsService { final LookupRemoteDataSource _dataSource; // 캐시된 데이터 LookupData? _cachedData; DateTime? _lastUpdated; bool _isInitialized = false; bool _isLoading = false; // 캐시 만료 시간 (기본: 30분) static const Duration _cacheExpiry = Duration(minutes: 30); // 초기화 완료 스트림 final StreamController _initializationController = StreamController.broadcast(); LookupsService(this._dataSource); /// 초기화 상태 스트림 Stream get initializationStream => _initializationController.stream; /// 초기화 여부 bool get isInitialized => _isInitialized; /// 로딩 상태 bool get isLoading => _isLoading; /// 캐시 만료 여부 bool get _isCacheExpired { if (_lastUpdated == null) return true; return DateTime.now().difference(_lastUpdated!) > _cacheExpiry; } /// 서비스 초기화 (앱 시작 시 호출) Future> initialize() async { if (_isInitialized && !_isCacheExpired) { DebugLogger.log('Lookups 서비스가 이미 초기화되어 있습니다', tag: 'LOOKUPS'); return const Right(true); } if (_isLoading) { DebugLogger.log('Lookups 초기화가 이미 진행 중입니다', tag: 'LOOKUPS'); return const Left(ServerFailure(message: '초기화가 이미 진행 중입니다')); } _isLoading = true; DebugLogger.log('Lookups 서비스 초기화 시작', tag: 'LOOKUPS'); try { final result = await _dataSource.getAllLookups(); return result.fold( (failure) { _isLoading = false; _isInitialized = false; _initializationController.add(false); DebugLogger.logError('Lookups 초기화 실패', error: failure.message); return Left(failure); }, (data) { _cachedData = data; _lastUpdated = DateTime.now(); _isInitialized = true; _isLoading = false; _initializationController.add(true); DebugLogger.log('Lookups 서비스 초기화 완료', tag: 'LOOKUPS', data: { 'manufacturers': data.manufacturers.length, 'equipment_names': data.equipmentNames.length, 'equipment_categories': data.equipmentCategories.length, 'equipment_statuses': data.equipmentStatuses.length, }); return const Right(true); }, ); } catch (e) { _isLoading = false; _isInitialized = false; _initializationController.add(false); DebugLogger.logError('Lookups 초기화 예외', error: e); return Left(ServerFailure(message: 'Lookups 초기화 중 예외 발생: $e')); } } /// 캐시 새로고침 Future> refresh() async { _isInitialized = false; _cachedData = null; _lastUpdated = null; return await initialize(); } /// 전체 Lookups 데이터 조회 Either getAllLookups() { if (!_isInitialized || _cachedData == null) { return const Left(ServerFailure(message: 'Lookups 서비스가 초기화되지 않았습니다')); } if (_isCacheExpired) { // 백그라운드에서 캐시 갱신 unawaited(refresh()); DebugLogger.log('Lookups 캐시가 만료되어 백그라운드 갱신을 시작합니다', tag: 'LOOKUPS'); } return Right(_cachedData!); } /// 제조사 목록 조회 Either> getManufacturers() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.manufacturers), ); } /// 장비명 목록 조회 Either> getEquipmentNames() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.equipmentNames), ); } /// 장비 카테고리 조합 목록 조회 Either> getEquipmentCategories() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.equipmentCategories), ); } /// 회사 목록 조회 Either> getCompanies() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.companies), ); } /// 창고 목록 조회 Either> getWarehouses() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.warehouses), ); } /// 장비 상태 목록 조회 Either> getEquipmentStatuses() { return getAllLookups().fold( (failure) => Left(failure), (data) => Right(data.equipmentStatuses), ); } /// 특정 제조사 정보 조회 Either getManufacturerById(int id) { return getManufacturers().fold( (failure) => Left(failure), (manufacturers) { try { final manufacturer = manufacturers.firstWhere((item) => item.id == id); return Right(manufacturer); } catch (e) { return const Right(null); } }, ); } /// 특정 장비 상태 정보 조회 Either getEquipmentStatusById(String id) { return getEquipmentStatuses().fold( (failure) => Left(failure), (statuses) { try { final status = statuses.firstWhere((item) => item.id == id); return Right(status); } catch (e) { return const Right(null); } }, ); } /// Equipment 폼용 매번 API 호출 메서드 (캐싱 없이) Future> getLookupDataForEquipmentForm() async { DebugLogger.log('Equipment 폼용 Lookups 데이터 API 호출', tag: 'LOOKUPS'); return await _dataSource.getAllLookups(); } /// 대분류 목록 추출 (Equipment 폼용) Future>> getCategory1List() async { final result = await getLookupDataForEquipmentForm(); return result.fold( (failure) => Left(failure), (data) { final category1List = data.equipmentCategories .map((item) => item.category1) .toSet() .toList() ..sort(); return Right(category1List); }, ); } /// 중분류 목록 추출 (Equipment 폼용) Future>> getCategory2List(String category1) async { final result = await getLookupDataForEquipmentForm(); return result.fold( (failure) => Left(failure), (data) { final category2List = data.equipmentCategories .where((item) => item.category1 == category1) .map((item) => item.category2) .toSet() .toList() ..sort(); return Right(category2List); }, ); } /// 소분류 목록 추출 (Equipment 폼용) Future>> getCategory3List(String category1, String category2) async { final result = await getLookupDataForEquipmentForm(); return result.fold( (failure) => Left(failure), (data) { final category3List = data.equipmentCategories .where((item) => item.category1 == category1 && item.category2 == category2) .map((item) => item.category3) .toSet() .toList() ..sort(); return Right(category3List); }, ); } /// 캐시 통계 정보 Map getCacheStats() { return { 'initialized': _isInitialized, 'loading': _isLoading, 'last_updated': _lastUpdated?.toIso8601String(), 'cache_expired': _isCacheExpired, 'data_available': _cachedData != null, 'manufacturers_count': _cachedData?.manufacturers.length ?? 0, 'equipment_names_count': _cachedData?.equipmentNames.length ?? 0, 'equipment_categories_count': _cachedData?.equipmentCategories.length ?? 0, 'equipment_statuses_count': _cachedData?.equipmentStatuses.length ?? 0, 'companies_count': _cachedData?.companies.length ?? 0, 'warehouses_count': _cachedData?.warehouses.length ?? 0, }; } /// 메모리 정리 void dispose() { _initializationController.close(); _cachedData = null; _lastUpdated = null; _isInitialized = false; _isLoading = false; } } /// LookupsService 편의 확장 메서드 extension LookupsServiceExtensions on LookupsService { /// 드롭다운용 제조사 리스트 (id, name 맵) Either> getManufacturerDropdownItems() { return getManufacturers().fold( (failure) => Left(failure), (manufacturers) { final Map items = {}; for (int i = 0; i < manufacturers.length; i++) { final manufacturer = manufacturers[i]; final id = manufacturer.id ?? (i + 1); // id가 null이면 인덱스 기반 ID 사용 items[id] = manufacturer.name; } return Right(items); }, ); } /// 드롭다운용 장비 상태 리스트 (id, name 맵) Either> getEquipmentStatusDropdownItems() { return getEquipmentStatuses().fold( (failure) => Left(failure), (statuses) { final Map items = {}; for (final status in statuses) { items[status.id] = status.name; } return Right(items); }, ); } /// Equipment 폼용 드롭다운 리스트 생성 (매번 API 호출) Future>> getEquipmentFormDropdownData() async { final result = await getLookupDataForEquipmentForm(); return result.fold( (failure) => Left(failure), (data) { // 제조사 리스트 (드롭다운 + 직접입력용) final List manufacturers = data.manufacturers.map((item) => item.name).toList(); // 장비명 리스트 (드롭다운 + 직접입력용) final List equipmentNames = data.equipmentNames.map((item) => item.name).toList(); // 회사 리스트 (드롭다운 전용) final Map companies = {}; for (final company in data.companies) { if (company.id != null) { companies[company.id!] = company.name; } } // 창고 리스트 (드롭다운 전용) final Map warehouses = {}; for (final warehouse in data.warehouses) { if (warehouse.id != null) { warehouses[warehouse.id!] = warehouse.name; } } // 대분류 리스트 (드롭다운 + 직접입력용) final List category1List = data.equipmentCategories .map((item) => item.category1) .toSet() .toList() ..sort(); return Right({ 'manufacturers': manufacturers, 'equipment_names': equipmentNames, 'companies': companies, 'warehouses': warehouses, 'category1_list': category1List, 'category_combinations': data.equipmentCategories, }); }, ); } }