import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/data/core_repository.dart'; import 'package:flux/core/layout/app_shell.dart'; import 'package:flux/core/routes/routes.dart'; import 'package:flux/core/widgets/image_upload/blocs/image_upload_cubit.dart'; import 'package:flux/core/widgets/image_upload/ui/image_upload_screen.dart'; import 'package:flux/core/widgets/set_password_screen.dart'; import 'package:flux/core/widgets/image_upload/ui/upload_success_screen.dart'; import 'package:flux/features/auth/ui/auth_screen.dart'; import 'package:flux/features/company/bloc/company_settings_cubit.dart'; import 'package:flux/features/company/ui/company_settings_screen.dart'; import 'package:flux/features/customers/blocs/customer_form_cubit.dart'; import 'package:flux/features/customers/blocs/customers_list_cubit.dart'; import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/ui/customer_detail_screen.dart'; import 'package:flux/features/customers/ui/customer_form_screen.dart'; import 'package:flux/features/customers/ui/customers_list_screen.dart'; import 'package:flux/features/home/ui/home_screen.dart'; import 'package:flux/features/master_data/master_data_hub_content.dart'; import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; import 'package:flux/features/master_data/products/ui/products_screen.dart'; import 'package:flux/features/master_data/providers/blocs/provider_form_cubit.dart'; import 'package:flux/features/master_data/providers/blocs/provider_list_cubit.dart'; import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/master_data/providers/ui/provider_form_screen.dart'; import 'package:flux/features/master_data/providers/ui/provider_list_screen.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; import 'package:flux/features/master_data/staff/ui/staff_screen.dart'; import 'package:flux/features/master_data/store/ui/stores_screen.dart'; import 'package:flux/features/notes/models/note_model.dart'; import 'package:flux/features/notes/ui/notes_form_screen.dart'; import 'package:flux/features/notes/ui/notes_list_screen.dart'; import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart'; import 'package:flux/features/onboarding/ui/onboarding_screen.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/operations/blocs/operation_form_cubit.dart'; import 'package:flux/features/operations/models/operation_model.dart'; import 'package:flux/features/operations/ui/operation_form_screen.dart'; import 'package:flux/features/operations/ui/operation_list_screen.dart'; import 'package:flux/features/settings/settings_screen.dart'; import 'package:flux/features/settings/theme_settings_view.dart'; import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/ui/ticket_form_screen.dart'; import 'package:flux/features/tickets/ui/ticket_list_screen.dart'; import 'package:flux/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart'; import 'package:flux/features/tracking/blocs/tracking_cubit.dart'; import 'package:flux/features/tracking/models/tracking_model.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; class AppRouter { static GoRouter createRouter(SessionCubit sessionCubit) { return GoRouter( initialLocation: '/', refreshListenable: GoRouterRefreshStream(sessionCubit.stream), redirect: (context, state) { final sessionState = sessionCubit.state; final isGoingToLogin = state.matchedLocation == '/login'; final isGoingToOnboarding = state.matchedLocation == '/onboarding'; final isGoingToSetPassword = state.matchedLocation == '/set-password'; // 1. LA PASSATOIA VIP (DEVE ESSERE IN CIMA) // Usiamo state.uri.path perché state.matchedLocation a volte fa i capricci coi deep link iniziali final isPublicRoute = state.uri.path.startsWith('/upload'); if (isPublicRoute) { // Ritorna null esplicitamente per dire al router "Rimani qui e non fare altri controlli" return null; } // 2. CONTROLLO INIZIALE // Se la sessione sta ancora caricando la primissima volta (es. splash screen logico) if (sessionState.status == SessionStatus.initial) return null; // 3. UTENTE NON LOGGATO (Ma ci arriva solo se non è su /upload) if (sessionState.status == SessionStatus.unauthenticated) { // Se sta già andando alle uniche altre pagine pubbliche, lascialo andare if (isGoingToLogin || isGoingToSetPassword) return null; // Altrimenti bloccalo e mandalo al login return '/login'; } // 4. UTENTE LOGGATO MA DEVE COMPLETARE L'ONBOARDING if (sessionState.status == SessionStatus.onboardingRequired) { return isGoingToOnboarding ? null : '/onboarding'; } // 5. UTENTE PERFETTAMENTE LOGGATO E OPERATIVO if (sessionState.status == SessionStatus.authenticated) { // Se per sbaglio cerca di tornare al login o all'onboarding, ributtalo in dashboard if (isGoingToLogin || isGoingToOnboarding) return '/'; return null; } return null; }, routes: [ // --- ROTTE DI SERVIZIO (FUORI DALLA SHELL) --- GoRoute( path: '/login', name: Routes.login, builder: (context, state) => const AuthScreen(), ), GoRoute( path: '/set-password', name: Routes.setPassword, builder: (context, state) => const SetPasswordScreen(), ), GoRoute( path: '/onboarding', name: Routes.onboarding, builder: (context, state) => BlocProvider( create: (context) => OnboardingCubit( GetIt.I.get(), GetIt.I.get(), ), child: const OnboardingScreen(), ), ), // --- CORE APP (DENTRO LA SHELL CON NAVIGATION BAR/RAIL) --- ShellRoute( builder: (context, state, child) => AppShell(child: child), routes: [ // 1. DASHBOARD GoRoute( path: '/', name: Routes.home, builder: (context, state) => const HomeScreen(), ), // 2. HUB ANAGRAFICHE E SOTTO-ROTTE GoRoute( path: '/master-data', name: Routes.masterData, builder: (context, state) => const MasterDataHubScreen(), routes: [ GoRoute( path: 'products', // Diventa /master-data/products name: Routes.products, builder: (context, state) { context.read().refreshCubit(); return const ProductsScreen(); }, ), GoRoute( path: 'company-settings', name: Routes.companySettings, builder: (context, state) => BlocProvider( create: (context) => CompanySettingsCubit(), child: const CompanySettingsScreen(), ), ), GoRoute( path: 'staff', name: Routes.staff, // Diventa /master-data/staff builder: (context, state) => const StaffScreen(), ), GoRoute( path: Routes.stores, name: 'stores', // Diventa /master-data/stores builder: (context, state) => const StoresScreen(), ), GoRoute( path: '/providers', name: Routes.providers, builder: (context, state) => const ProviderListScreen(), ), ], ), // 3. IMPOSTAZIONI GoRoute( path: '/settings', name: Routes.settings, builder: (context, state) => const SettingsScreen(), routes: [ GoRoute( path: 'themeSettings', name: Routes.themeSettings, builder: (context, state) => const ThemeSettingsView(), ), ], ), GoRoute( path: '/operations', name: Routes.operations, builder: (context, state) => const OperationListScreen(), ), GoRoute( path: '/customers', name: Routes.customers, builder: (context, state) => const CustomersListScreen(), // O come si chiama il tuo widget della lista! ), GoRoute( path: '/tickets', name: Routes.tickets, builder: (context, state) => const TicketListScreen(), ), GoRoute( path: '/notes', name: Routes.notes, builder: (context, state) => const NotesListScreen(), ), ], ), // --- DETTAGLI E OPERATIVITÀ (FUORI DALLA SHELL - TUTTO SCHERMO) --- GoRoute( path: '/providers/form', name: Routes.providerForm, builder: (context, state) { // Estraiamo il fornitore (se stiamo modificando e non creando) final existingProvider = state.extra as ProviderModel?; return BlocProvider( // Inizializziamo un Cubit NUOVO ogni volta che apriamo il form create: (context) => ProviderFormCubit(), child: ProviderFormScreen(existingProvider: existingProvider), ); }, ), GoRoute( // Il path sarà es. /tickets/form/123 oppure /tickets/form/new path: '/tickets/form/:id', name: Routes.ticketForm, builder: (context, state) { // 1. Leggiamo l'ID dall'URL final String pathId = state.pathParameters['id'] ?? 'new'; // 2. CAST DA NINJA (Aggiungi i punti interrogativi!) final record = state.extra as ({StaffMemberModel? createdBy, TicketModel? ticket})?; // 3. LOGICA SOBRIA final String? realTicketId; if (pathId == 'new') { realTicketId = null; } else if (record?.ticket?.id != null) { // <-- Parentesi TONDE per la condizione, GRAFFE per il blocco! realTicketId = record!.ticket!.id; } else { realTicketId = pathId; } if (realTicketId != null) { context.read().loadTrackings( realTicketId, TrackingParentType.ticket, ); } context.read().loadCustomers(); context.read().loadModels(); context.read().loadBrands(); return MultiBlocProvider( providers: [ BlocProvider( create: (context) => AttachmentsBloc( parentType: AttachmentParentType.ticket, parentId: realTicketId, ), ), BlocProvider( create: (context) => TicketFormCubit( // Passiamo il creatore e l'eventuale ticket esistente presi dal Record! createdBy: record?.createdBy, existingTicket: record?.ticket, ), ), ], child: TicketFormScreen( ticketId: realTicketId, existingTicket: record?.ticket, ), ); }, ), GoRoute( path: '/tickets/workspace/:id', name: Routes.ticketWorkspace, builder: (context, state) { // 1. Recuperiamo il Cubit vivo dall'extra final formCubit = state.extra as TicketFormCubit?; // 2. Controllo di sicurezza (fondamentale per Flutter Web) if (formCubit != null) { return BlocProvider.value( value: formCubit, child: const TicketWorkspaceScreen(), ); } else { // SCENARIO REFRESH WEB: // Se l'utente preme F5 del browser mentre è nel banco da lavoro, // l'extra viene distrutto e diventa null. // In questo caso, gli diciamo elegantemente che la sessione è persa // e lo invitiamo a tornare indietro, oppure restituisci direttamente // un blocco di redirect! return const Scaffold( body: Center( child: Text( 'Sessione di lavoro scaduta. Torna alla lista e riapri il ticket.', ), ), ); } }, ), GoRoute( path: '/upload-success', name: Routes.uploadSuccess, builder: (context, state) => const UploadSuccessScreen(), ), GoRoute( path: '/customer/details/:id', name: Routes.customerDetails, builder: (context, state) { final customer = state.extra as CustomerModel; return BlocProvider( create: (context) => AttachmentsBloc( parentType: AttachmentParentType.customer, parentId: customer.id, ), child: CustomerDetailScreen(customer: customer), ); }, ), GoRoute( path: '/customer/form/:id', name: Routes.customerForm, builder: (context, state) { final String pathId = state.pathParameters['id'] ?? 'new'; final String? realCustomerId; if (pathId == 'new') { realCustomerId = null; } else { realCustomerId = pathId; } final customer = state.extra as CustomerModel?; return BlocProvider( create: (context) => CustomerFormCubit( existingCustomer: customer, customerId: realCustomerId, ), child: CustomerFormScreen( customer: customer, customerId: realCustomerId, ), ); }, ), GoRoute( path: '/operations/form/:id', name: Routes.operationForm, builder: (context, state) { final String pathId = state.pathParameters['id'] ?? 'new'; final record = state.extra as ({ StaffMemberModel? createdBy, OperationModel? operation, })?; final String? realOperationId; if (pathId == 'new') { realOperationId = null; } else if (record?.operation?.id != null) { realOperationId = record!.operation!.id; } else { realOperationId = pathId; } final currentStoreId = GetIt.I .get() .state .currentStore! .id!; context.read().loadCustomers(); context.read().loadProviders(currentStoreId); context.read().loadModels(); context.read().loadBrands(); return MultiBlocProvider( providers: [ BlocProvider( create: (context) => AttachmentsBloc( parentId: realOperationId, parentType: AttachmentParentType.operation, ), ), BlocProvider( create: (context) => OperationFormCubit( createdBy: record?.createdBy, existingOperation: record?.operation, ), ), ], child: OperationFormScreen( operationId: realOperationId, existingOperation: record?.operation, ), ); }, ), GoRoute( path: '/upload/:type/:id', name: Routes.upload, builder: (context, state) { final typeString = state.pathParameters['type']!; final id = state.pathParameters['id']!; final companyId = state.uri.queryParameters['companyId']!; // Trasformiamo la stringa dell'URL nel nostro amato Enum! final parentType = AttachmentParentType.values.firstWhere( (e) => e.name == typeString, orElse: () => AttachmentParentType.ticket, // Fallback di sicurezza ); // Creiamo il BLoC "al volo" solo per questa schermata return MultiBlocProvider( providers: [ BlocProvider( create: (context) => AttachmentsBloc(parentId: id, parentType: parentType), ), BlocProvider(create: (context) => ImageUploadCubit()), ], child: ImageUploadScreen( title: 'Caricamento Rapido', companyId: companyId, ), ); }, ), GoRoute( path: '/notes/edit/:id', name: Routes.noteForm, builder: (context, state) { final id = state.pathParameters['id']!; final NoteModel note = state.extra as NoteModel; // Creiamo il BLoC "al volo" solo per questa schermata return MultiBlocProvider( providers: [ BlocProvider( create: (context) => AttachmentsBloc( parentId: id, parentType: AttachmentParentType.note, ), ), ], child: NoteFormScreen(note: note), ); }, ), ], ); } } class GoRouterRefreshStream extends ChangeNotifier { GoRouterRefreshStream(Stream stream) { notifyListeners(); _subscription = stream.asBroadcastStream().listen( (dynamic _) => notifyListeners(), ); } late final StreamSubscription _subscription; @override void dispose() { _subscription.cancel(); super.dispose(); } }