import type { RoutineRecurrence } from '@mento/shared'; import { ROUTINE_WEEKDAY_LABELS } from '@mento/shared'; import { addDays, dayDiff, jalaliDayOfMonth, persianWeekday, toFaDigits, weekStartKey, } from './routine-dates'; export type RecurrenceRule = { recurrence: RoutineRecurrence; startDate: string; endDate: string | null; weekdays: number[]; intervalDays: number | null; monthDay: number | null; specificDates: string[]; }; function weeklyDays(rule: RecurrenceRule): number[] { return rule.weekdays.length ? rule.weekdays : [persianWeekday(rule.startDate)]; } export function occursOn(rule: RecurrenceRule, day: string): boolean { if (day < rule.startDate) return false; if (rule.endDate && day > rule.endDate) return false; switch (rule.recurrence) { case 'daily': return true; case 'weekdays': case 'weekly': return weeklyDays(rule).includes(persianWeekday(day)); case 'biweekly': { if (!weeklyDays(rule).includes(persianWeekday(day))) return false; const weeks = Math.floor( dayDiff(weekStartKey(rule.startDate), weekStartKey(day)) / 7, ); return weeks % 2 === 0; } case 'monthly': { const target = rule.monthDay ?? jalaliDayOfMonth(rule.startDate); const current = jalaliDayOfMonth(day); if (current === target) return true; // ماه‌هایی که روز هدف را ندارند (مثلاً ۳۱ در مهر): آخرین روز ماه const isLastDay = jalaliDayOfMonth(addDays(day, 1)) === 1; return isLastDay && target > current; } case 'every_n_days': { const n = Math.max(1, rule.intervalDays ?? 1); return dayDiff(rule.startDate, day) % n === 0; } case 'specific_dates': return rule.specificDates.includes(day); default: return false; } } function joinFa(items: string[]): string { if (items.length <= 1) return items.join(''); return `${items.slice(0, -1).join('، ')} و ${items[items.length - 1]}`; } function describeWeekdays(days: number[]): string { const sorted = [...new Set(days)].sort((a, b) => a - b); if (sorted.length === 7) return 'هر روز'; const contiguous = sorted.length >= 3 && sorted.every((d, i) => i === 0 || d === sorted[i - 1]! + 1); if (contiguous) { return `${ROUTINE_WEEKDAY_LABELS[sorted[0]!]} تا ${ROUTINE_WEEKDAY_LABELS[sorted[sorted.length - 1]!]}`; } return `هر ${joinFa(sorted.map((d) => ROUTINE_WEEKDAY_LABELS[d]!))}`; } export function describeRecurrence(rule: RecurrenceRule): string { switch (rule.recurrence) { case 'daily': return 'هر روز'; case 'weekdays': case 'weekly': return describeWeekdays(weeklyDays(rule)); case 'biweekly': return `هر دو هفته، ${joinFa(weeklyDays(rule).map((d) => ROUTINE_WEEKDAY_LABELS[d]!))}`; case 'monthly': { const day = rule.monthDay ?? jalaliDayOfMonth(rule.startDate); return day === 1 ? 'اول هر ماه' : `روز ${toFaDigits(day)} هر ماه`; } case 'every_n_days': { const n = Math.max(1, rule.intervalDays ?? 1); return n === 1 ? 'هر روز' : `هر ${toFaDigits(n)} روز یک‌بار`; } case 'specific_dates': return `${toFaDigits(rule.specificDates.length)} تاریخ مشخص`; default: return ''; } }