export type Recurrence = { type: 'daily' | 'weekly' | 'monthly'; days?: number[]; dayOfMonth?: number; }; const startOfDay = (date: Date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()); const addDays = (date: Date, days: number) => { const d = new Date(date); d.setDate(d.getDate() + days); return d; }; const getNextDaily = (completedAt: Date) => addDays(startOfDay(completedAt), 1); const getNextWeekly = (completedAt: Date, days: number[]) => { if (!days || days.length === 0) return null; const base = startOfDay(completedAt); for (let offset = 1; offset <= 7; offset++) { const candidate = addDays(base, offset); if (days.includes(candidate.getDay())) return candidate; } return null; }; const getNextMonthly = (completedAt: Date, dayOfMonth?: number) => { if (!dayOfMonth || dayOfMonth < 1 || dayOfMonth > 31) return null; const base = startOfDay(completedAt); const baseDay = base.getDate(); const startOffset = dayOfMonth > baseDay ? 0 : 1; for (let offset = startOffset; offset <= 12; offset++) { const year = base.getFullYear(); const monthIndex = base.getMonth() + offset; const candidate = new Date(year, monthIndex, dayOfMonth); if (candidate.getMonth() === (monthIndex % 12 + 12) % 12) { if (candidate.getTime() > completedAt.getTime()) { return startOfDay(candidate); } } } return null; }; export const getNextRecurrenceDate = (recurrence: Recurrence, completedAt: Date) => { if (recurrence.type === 'daily') return getNextDaily(completedAt); if (recurrence.type === 'weekly') return getNextWeekly(completedAt, recurrence.days || []); if (recurrence.type === 'monthly') return getNextMonthly(completedAt, recurrence.dayOfMonth); return null; }; export const getRecurrenceProgress = ( recurrence: Recurrence | null | undefined, completedAtIso: string | null | undefined, nowMs: number ) => { if (!recurrence || !completedAtIso) return null; const completedAt = new Date(completedAtIso); if (isNaN(completedAt.getTime())) return null; const next = getNextRecurrenceDate(recurrence, completedAt); if (!next) return null; const total = next.getTime() - completedAt.getTime(); if (total <= 0) return null; const progress = (nowMs - completedAt.getTime()) / total; return Math.max(0, Math.min(1, progress)); };