///|
/// Create a reminder plan for one calculated deadline.
pub fn ReminderPlan::new(
  result : DeadlineResult,
  calendar : Calendar,
) -> ReminderPlan {
  { result, calendar, rules: [] }
}

///|
/// Create a calendar-day reminder rule.
pub fn ReminderRule::calendar_days_before(
  count : Int,
) -> Result[ReminderRule, ReminderError] {
  if count < 0 {
    Err(ReminderNegativeOffset)
  } else {
    Ok(CalendarDaysBefore(count))
  }
}

///|
/// Create a business-day reminder rule.
pub fn ReminderRule::business_days_before(
  count : Int,
) -> Result[ReminderRule, ReminderError] {
  if count < 0 {
    Err(ReminderNegativeOffset)
  } else {
    Ok(BusinessDaysBefore(count))
  }
}

///|
/// Add a unique offset rule.
pub fn ReminderPlan::add(
  self : ReminderPlan,
  rule : ReminderRule,
) -> Result[ReminderPlan, ReminderError] {
  for existing in self.rules {
    if existing == rule {
      return Err(ReminderDuplicateOffset)
    }
  }
  let rules = self.rules.copy()
  rules.push(rule)
  Ok({ ..self, rules, })
}

///|
/// Calculate reminders in configured order.
pub fn ReminderPlan::calculate(
  self : ReminderPlan,
) -> Result[Array[Date], ReminderError] {
  let reminders = Array::new()
  for rule in self.rules {
    match rule {
      CalendarDaysBefore(count) =>
        match self.result.adjusted_date().try_add_days(-count) {
          Ok(date) => reminders.push(date)
          Err(_) => return Err(ReminderDateFailure)
        }
      BusinessDaysBefore(count) => {
        let mut current = self.result.adjusted_date()
        let mut remaining = count
        while remaining > 0 {
          current = match current.try_add_days(-1) {
            Ok(date) => date
            Err(_) => return Err(ReminderDateFailure)
          }
          if self.calendar.is_business_day(current) {
            remaining = remaining - 1
          }
        }
        reminders.push(current)
      }
    }
  }
  Ok(reminders)
}