import 'dart:async'; import 'package:flutter/foundation.dart'; import '../providers/kandang_provider.dart'; import '../providers/panen_provider.dart'; import '../providers/penjadwalan_provider.dart'; import 'panen_auto_capture_service.dart'; /// Scheduler ringan di level aplikasi untuk memicu panen otomatis pagi/sore. /// /// Catatan: ini berjalan selama proses app masih hidup (foreground/background). /// Jika proses dibunuh OS, trigger akan berhenti sampai app dibuka lagi. class PanenRuntimeSchedulerService { PanenRuntimeSchedulerService._(); static Timer? _timer; static bool _isRunning = false; static bool _isProcessing = false; static String? _lastPagiTriggerDate; static final Map _lastSoreTriggerDateByIndex = {}; // Track per jadwal sore static Future start({ required PanenProvider panenProvider, required KandangProvider kandangProvider, required PenjadwalanProvider penjadwalanProvider, }) async { stop(); _isRunning = true; _timer = Timer.periodic(const Duration(seconds: 30), (_) async { await _tick( panenProvider: panenProvider, kandangProvider: kandangProvider, penjadwalanProvider: penjadwalanProvider, ); }); await _tick( panenProvider: panenProvider, kandangProvider: kandangProvider, penjadwalanProvider: penjadwalanProvider, ); if (kDebugMode) { print('✅ PanenRuntimeSchedulerService started (30s tick)'); } } static void stop() { _isRunning = false; _timer?.cancel(); _timer = null; } static Future _tick({ required PanenProvider panenProvider, required KandangProvider kandangProvider, required PenjadwalanProvider penjadwalanProvider, }) async { if (!_isRunning || _isProcessing) return; _isProcessing = true; try { final now = DateTime.now(); final dateKey = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}'; final activeSchedules = penjadwalanProvider.penjadwalans.where((j) => j.aktif).toList(); // Sort by ID urutan untuk memastikan penjadwalan1, penjadwalan2, dst activeSchedules.sort((a, b) { int numA = int.tryParse(a.id.replaceAll('penjadwalan', '')) ?? 999; int numB = int.tryParse(b.id.replaceAll('penjadwalan', '')) ?? 999; return numA.compareTo(numB); }); int? morningHour; int? morningMinute; final afternoonSchedules = <(int index, int hour, int minute)>[]; // Jadwal pertama (penjadwalan1) = PAGI // Jadwal ke-2+ (penjadwalan2, penjadwalan3, dst) = SORE for (int i = 0; i < activeSchedules.length; i++) { final schedule = activeSchedules[i]; final parsed = _parseJam(schedule.jam); if (parsed == null) continue; final hour = parsed.$1; final minute = parsed.$2; if (i == 0) { // Jadwal pertama = PAGI morningHour = hour; morningMinute = minute; } else { // Jadwal ke-2+ = SORE (simpan semua jadwal sore) afternoonSchedules.add((i, hour, minute)); } } // Trigger pagi hanya jika ada jadwal pagi yang aktif if (morningHour != null && morningMinute != null) { final morningStart = DateTime( now.year, now.month, now.day, morningHour, morningMinute, ); final morningEnd = morningStart.add(const Duration(minutes: 10)); final isMorningWindow = !now.isBefore(morningStart) && !now.isAfter(morningEnd); if (isMorningWindow && _lastPagiTriggerDate != dateKey) { if (kDebugMode) { print( '🕘 Trigger runtime panen pagi ($dateKey @ $morningHour:${morningMinute.toString().padLeft(2, '0')})'); } await PanenAutoCaptureService.triggerMorningCapture( panenProvider, kandangProvider, ); _lastPagiTriggerDate = dateKey; } } // Trigger semua jadwal sore yang waktunya match for (int schedIdx = 0; schedIdx < afternoonSchedules.length; schedIdx++) { final (index, afternoonHour, afternoonMinute) = afternoonSchedules[schedIdx]; final isLastSchedule = schedIdx == afternoonSchedules.length - 1; final afternoonStart = DateTime( now.year, now.month, now.day, afternoonHour, afternoonMinute, ); final afternoonEnd = afternoonStart.add(const Duration(minutes: 10)); final isAfternoonWindow = !now.isBefore(afternoonStart) && !now.isAfter(afternoonEnd); final lastTriggerDate = _lastSoreTriggerDateByIndex[index]; final shouldTrigger = isAfternoonWindow && (lastTriggerDate == null || lastTriggerDate != dateKey); if (shouldTrigger) { if (kDebugMode) { print( '🕒 Trigger runtime panen sore #${index} ($dateKey @ $afternoonHour:${afternoonMinute.toString().padLeft(2, '0')})'); } await PanenAutoCaptureService.triggerAfternoonCapture( panenProvider, kandangProvider, isLastSchedule: isLastSchedule, ); _lastSoreTriggerDateByIndex[index] = dateKey; } } } catch (e) { if (kDebugMode) { print('❌ PanenRuntimeSchedulerService tick error: $e'); } } finally { _isProcessing = false; } } static (int, int)? _parseJam(String jam) { final parts = jam.split(':'); if (parts.length < 2) return null; final hour = int.tryParse(parts[0]); final minute = int.tryParse(parts[1]); if (hour == null || minute == null) return null; if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null; return (hour, minute); } }