import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import '../../domain/entities/restaurant.dart'; import '../providers/di_providers.dart'; import '../providers/restaurant_provider.dart'; /// 식당 추가 화면의 상태 모델 class AddRestaurantState { final bool isLoading; final bool isSearching; final String? errorMessage; final Restaurant? fetchedRestaurantData; final RestaurantFormData formData; final List searchResults; const AddRestaurantState({ this.isLoading = false, this.isSearching = false, this.errorMessage, this.fetchedRestaurantData, required this.formData, this.searchResults = const [], }); AddRestaurantState copyWith({ bool? isLoading, bool? isSearching, String? errorMessage, Restaurant? fetchedRestaurantData, RestaurantFormData? formData, List? searchResults, bool clearFetchedRestaurant = false, bool clearError = false, }) { return AddRestaurantState( isLoading: isLoading ?? this.isLoading, isSearching: isSearching ?? this.isSearching, errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), fetchedRestaurantData: clearFetchedRestaurant ? null : (fetchedRestaurantData ?? this.fetchedRestaurantData), formData: formData ?? this.formData, searchResults: searchResults ?? this.searchResults, ); } } /// 식당 폼 데이터 모델 class RestaurantFormData { final String name; final String category; final String subCategory; final String description; final String phoneNumber; final String roadAddress; final String jibunAddress; final String latitude; final String longitude; final String naverUrl; const RestaurantFormData({ this.name = '', this.category = '', this.subCategory = '', this.description = '', this.phoneNumber = '', this.roadAddress = '', this.jibunAddress = '', this.latitude = '', this.longitude = '', this.naverUrl = '', }); RestaurantFormData copyWith({ String? name, String? category, String? subCategory, String? description, String? phoneNumber, String? roadAddress, String? jibunAddress, String? latitude, String? longitude, String? naverUrl, }) { return RestaurantFormData( name: name ?? this.name, category: category ?? this.category, subCategory: subCategory ?? this.subCategory, description: description ?? this.description, phoneNumber: phoneNumber ?? this.phoneNumber, roadAddress: roadAddress ?? this.roadAddress, jibunAddress: jibunAddress ?? this.jibunAddress, latitude: latitude ?? this.latitude, longitude: longitude ?? this.longitude, naverUrl: naverUrl ?? this.naverUrl, ); } /// TextEditingController로부터 폼 데이터 생성 factory RestaurantFormData.fromControllers({ required TextEditingController nameController, required TextEditingController categoryController, required TextEditingController subCategoryController, required TextEditingController descriptionController, required TextEditingController phoneController, required TextEditingController roadAddressController, required TextEditingController jibunAddressController, required TextEditingController latitudeController, required TextEditingController longitudeController, required TextEditingController naverUrlController, }) { return RestaurantFormData( name: nameController.text.trim(), category: categoryController.text.trim(), subCategory: subCategoryController.text.trim(), description: descriptionController.text.trim(), phoneNumber: phoneController.text.trim(), roadAddress: roadAddressController.text.trim(), jibunAddress: jibunAddressController.text.trim(), latitude: latitudeController.text.trim(), longitude: longitudeController.text.trim(), naverUrl: naverUrlController.text.trim(), ); } /// Restaurant 엔티티로부터 폼 데이터 생성 factory RestaurantFormData.fromRestaurant(Restaurant restaurant) { return RestaurantFormData( name: restaurant.name, category: restaurant.category, subCategory: restaurant.subCategory, description: restaurant.description ?? '', phoneNumber: restaurant.phoneNumber ?? '', roadAddress: restaurant.roadAddress, jibunAddress: restaurant.jibunAddress, latitude: restaurant.latitude.toString(), longitude: restaurant.longitude.toString(), naverUrl: restaurant.naverUrl ?? '', ); } /// Restaurant 엔티티로 변환 Restaurant toRestaurant() { final uuid = const Uuid(); return Restaurant( id: uuid.v4(), name: name, category: category, subCategory: subCategory.isEmpty ? category : subCategory, description: description.isEmpty ? null : description, phoneNumber: phoneNumber.isEmpty ? null : phoneNumber, roadAddress: roadAddress, jibunAddress: jibunAddress.isEmpty ? roadAddress : jibunAddress, latitude: double.tryParse(latitude) ?? 37.5665, longitude: double.tryParse(longitude) ?? 126.9780, naverUrl: naverUrl.isEmpty ? null : naverUrl, source: naverUrl.isNotEmpty ? DataSource.NAVER : DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); } } /// 식당 추가 화면의 ViewModel class AddRestaurantViewModel extends StateNotifier { final Ref _ref; AddRestaurantViewModel(this._ref) : super(const AddRestaurantState(formData: RestaurantFormData())); /// 상태 초기화 void reset() { state = const AddRestaurantState(formData: RestaurantFormData()); } /// 네이버 URL로부터 식당 정보 가져오기 Future fetchFromNaverUrl(String url) async { if (url.trim().isEmpty) { state = state.copyWith(errorMessage: 'URL을 입력해주세요.'); return; } state = state.copyWith(isLoading: true, clearError: true); try { final repository = _ref.read(restaurantRepositoryProvider); final restaurant = await repository.previewRestaurantFromUrl(url); state = state.copyWith( isLoading: false, fetchedRestaurantData: restaurant, formData: RestaurantFormData.fromRestaurant(restaurant), ); } catch (e) { state = state.copyWith(isLoading: false, errorMessage: e.toString()); } } /// 네이버 검색으로 식당 목록 검색 Future searchRestaurants( String query, { double? latitude, double? longitude, }) async { if (query.trim().isEmpty) { state = state.copyWith( errorMessage: '검색어를 입력해주세요.', searchResults: const [], ); return; } state = state.copyWith(isSearching: true, clearError: true); try { final repository = _ref.read(restaurantRepositoryProvider); final results = await repository.searchRestaurantsFromNaver( query: query, latitude: latitude, longitude: longitude, ); state = state.copyWith(isSearching: false, searchResults: results); } catch (e) { state = state.copyWith(isSearching: false, errorMessage: e.toString()); } } /// 검색 결과 선택 void selectSearchResult(Restaurant restaurant) { state = state.copyWith( fetchedRestaurantData: restaurant, formData: RestaurantFormData.fromRestaurant(restaurant), clearError: true, ); } /// 식당 정보 저장 Future saveRestaurant() async { final notifier = _ref.read(restaurantNotifierProvider.notifier); try { state = state.copyWith(isLoading: true, clearError: true); Restaurant restaurantToSave; // 네이버에서 가져온 데이터가 있으면 업데이트 final fetchedData = state.fetchedRestaurantData; if (fetchedData != null) { restaurantToSave = fetchedData.copyWith( name: state.formData.name, category: state.formData.category, subCategory: state.formData.subCategory.isEmpty ? state.formData.category : state.formData.subCategory, description: state.formData.description.isEmpty ? null : state.formData.description, phoneNumber: state.formData.phoneNumber.isEmpty ? null : state.formData.phoneNumber, roadAddress: state.formData.roadAddress, jibunAddress: state.formData.jibunAddress.isEmpty ? state.formData.roadAddress : state.formData.jibunAddress, latitude: double.tryParse(state.formData.latitude) ?? fetchedData.latitude, longitude: double.tryParse(state.formData.longitude) ?? fetchedData.longitude, naverUrl: state.formData.naverUrl.isEmpty ? null : state.formData.naverUrl, updatedAt: DateTime.now(), ); } else { // 직접 입력한 경우 restaurantToSave = state.formData.toRestaurant(); } await notifier.addRestaurantDirect(restaurantToSave); state = state.copyWith(isLoading: false); return true; } catch (e) { state = state.copyWith(isLoading: false, errorMessage: e.toString()); return false; } } /// 폼 데이터 업데이트 void updateFormData(RestaurantFormData formData) { state = state.copyWith(formData: formData); } /// 에러 메시지 초기화 void clearError() { state = state.copyWith(clearError: true); } } /// AddRestaurantViewModel Provider final addRestaurantViewModelProvider = StateNotifierProvider.autoDispose< AddRestaurantViewModel, AddRestaurantState >((ref) => AddRestaurantViewModel(ref));