///|
pub(all) struct RenewalPolicy {
  renew_before_seconds : Int64
  minimum_lifetime_seconds : Int64
} derive(Eq, Debug)

///|
pub fn RenewalPolicy::default() -> RenewalPolicy {
  {
    renew_before_seconds: 30L * 24L * 60L * 60L,
    minimum_lifetime_seconds: 60L * 60L,
  }
}

///|
/// Optional renewal window returned by an ACME Renewal Information endpoint.
pub(all) struct RenewalWindow {
  start : Int64
  end : Int64
} derive(Eq, Debug)

///|
pub(all) enum RenewalDecision {
  NotYetValid
  RenewNow(String)
  ScheduleAt(Int64)
  InvalidCertificateWindow
  InvalidRenewalWindow
} derive(Eq, Debug)

///|
/// Plan renewal from caller-provided Unix seconds. The function reads no clock,
/// so applications can test expiry boundaries and inject their own time source.
pub fn plan_renewal(
  now : Int64,
  not_before : Int64,
  not_after : Int64,
  policy : RenewalPolicy,
  suggested? : RenewalWindow,
) -> RenewalDecision {
  if not_after <= not_before ||
    not_after - not_before < policy.minimum_lifetime_seconds {
    return InvalidCertificateWindow
  }
  if now < not_before {
    return NotYetValid
  }
  if now >= not_after {
    return RenewNow("certificate expired")
  }
  match suggested {
    Some(window) => {
      if window.end <= window.start || window.start >= not_after {
        return InvalidRenewalWindow
      }
      if now >= window.start {
        return RenewNow("inside server suggested renewal window")
      }
      ScheduleAt(window.start)
    }
    None => {
      let scheduled = not_after - policy.renew_before_seconds
      if now >= scheduled {
        RenewNow("inside local renewal window")
      } else {
        ScheduleAt(scheduled)
      }
    }
  }
}