import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import '../brick/cache_helpers.dart'; import '../models/programmer_project.model.dart'; import 'profile_provider.dart'; import 'realtime_controller.dart'; import 'stream_recovery.dart'; import 'supabase_provider.dart'; void _sortProjects(List projects) { projects.sort((a, b) { // Active before archived, then alphabetical. if (a.isArchived != b.isArchived) return a.isArchived ? 1 : -1; return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }); } /// All programmer projects (offline-readable via Brick cache). final programmerProjectsProvider = StreamProvider>((ref) { final userId = ref.watch(currentUserIdProvider); if (userId == null) return const Stream.empty(); final client = ref.watch(supabaseClientProvider); final wrapper = StreamRecoveryWrapper( stream: client .from('programmer_projects') .stream(primaryKey: ['id']).order('created_at', ascending: false), onPollData: () async { final data = await client .from('programmer_projects') .select() .order('created_at', ascending: false); return data.map(ProgrammerProject.fromMap).toList(); }, fromMap: ProgrammerProject.fromMap, channelName: 'programmer_projects', onStatusChanged: ref.read(realtimeControllerProvider).handleChannelStatus, onOfflineData: () async { final all = await cachedListFromBrick(); _sortProjects(all); return all; }, onCacheMirror: (rows) => mirrorBatchToBrick(rows, tag: 'programmer_projects'), ); ref.onDispose(wrapper.dispose); return wrapper.stream.map((result) { final list = List.from(result.data); _sortProjects(list); return list; }); }); /// Active projects only — used by the task form's project picker. final activeProgrammerProjectsProvider = Provider>((ref) { final all = ref.watch(programmerProjectsProvider).valueOrNull ?? const []; return all.where((p) => !p.isArchived).toList(); }); final programmerProjectByIdProvider = Provider.family((ref, id) { final projects = ref.watch(programmerProjectsProvider).valueOrNull; if (projects == null) return null; try { return projects.firstWhere((p) => p.id == id); } catch (_) { return null; } }); final programmerProjectsControllerProvider = Provider((ref) { return ProgrammerProjectsController(ref.watch(supabaseClientProvider)); }); /// Projects are created/edited online (inline from the task form or the /// projects screen). Reads fall back to the Brick cache when offline. class ProgrammerProjectsController { ProgrammerProjectsController(this._client); final dynamic _client; Future createProject({ required String name, String? description, }) async { final userId = _client.auth.currentUser?.id; if (userId == null) throw Exception('Not authenticated'); final id = const Uuid().v4(); await _client.from('programmer_projects').insert({ 'id': id, 'name': name, 'description': ?description, 'creator_id': userId, }); return id; } Future updateProject({ required String projectId, String? name, String? description, }) async { final updates = {}; if (name != null) updates['name'] = name; if (description != null) updates['description'] = description; if (updates.isEmpty) return; await _client.from('programmer_projects').update(updates).eq('id', projectId); } Future setArchived({ required String projectId, required bool archived, }) async { await _client.from('programmer_projects').update({ 'status': archived ? ProgrammerProjectStatus.archived : ProgrammerProjectStatus.active, }).eq('id', projectId); } }