import '../models/habit.dart'; class CheckInContext { final String? location; final String? condition; const CheckInContext({this.location, this.condition}); } enum RecommendReason { exactMatch, partial, fallbackMinimum, fallbackFirst } class VariantPick { final HabitDoseVariantModel variant; final int score; final RecommendReason reason; const VariantPick(this.variant, this.score, this.reason); } /// fn-recommend-variant: returns best variant for current context. /// O(N). Pure function. Returns null if habit has no variants. VariantPick? recommendVariant(HabitModel habit, CheckInContext ctx) { final variants = habit.doseVariants; if (variants.isEmpty) return null; final scored = variants .map((v) => MapEntry(v, _scoreVariant(v, ctx))) .toList(); // Stable sort: score desc, then sortOrder asc. scored.sort((a, b) { final byScore = b.value.compareTo(a.value); if (byScore != 0) return byScore; return a.key.sortOrder.compareTo(b.key.sortOrder); }); final best = scored.first; if (best.value > 0) { final reason = best.value >= 4 ? RecommendReason.exactMatch : RecommendReason.partial; return VariantPick(best.key, best.value, reason); } // Fallback: first is_minimum variant (by sortOrder). final minimum = variants .where((v) => v.isMinimum) .fold(null, (a, v) => a == null || v.sortOrder < a.sortOrder ? v : a); if (minimum != null) { return VariantPick(minimum, 0, RecommendReason.fallbackMinimum); } // No is_minimum — fall back to first by sortOrder. final first = [...variants]..sort((a, b) => a.sortOrder.compareTo(b.sortOrder)); return VariantPick(first.first, 0, RecommendReason.fallbackFirst); } int _scoreVariant(HabitDoseVariantModel v, CheckInContext ctx) { var s = 0; if (ctx.location != null && v.contextTags.contains(ctx.location)) { s += 2; } if (ctx.condition != null && v.conditionTags.contains(ctx.condition)) { s += 2; } if (v.isMinimum && ctx.condition == '나쁨') { s += 1; } return s; }