;; lib/commerce/window.sx — time-windowed promotions.
;;
;; A promo's validity window is kept SEPARATE from the promo tuple (so promo.sx
;; is untouched): a windowed promo is (list promo from until) with inclusive
;; integer timestamps (same time model as the ledger `at`). nil from = no lower
;; bound; nil until = open-ended.
;;
;; `active-ruleset` filters a windowed ruleset to the plain promos live at a
;; given time, which feeds straight into promo/stack/quote — so a datetime-aware
;; quote is just the existing pipeline over the active set. Deterministic: the
;; quote is a pure function of (ctx, cart, windowed-ruleset, exclusions, at).

(define windowed-promo (fn (promo from until) (list promo from until)))

(define wp-promo (fn (wp) (nth wp 0)))
(define wp-from (fn (wp) (nth wp 1)))
(define wp-until (fn (wp) (nth wp 2)))

(define
  wp-active?
  (fn
    (wp at)
    (let
      ((from (wp-from wp)) (until (wp-until wp)))
      (and (or (nil? from) (>= at from)) (or (nil? until) (<= at until))))))

;; Plain promo tuples live at time `at` — feed into cart-quote / best-promo-*.
(define
  active-ruleset
  (fn
    (windowed at)
    (map wp-promo (filter (fn (wp) (wp-active? wp at)) windowed))))

;; Relation: which promo codes are active at `at`? (backward query)
(define
  active-promoo
  (fn
    (windowed at code)
    (fresh
      (wp)
      (membero wp windowed)
      (project
        (wp)
        (if (wp-active? wp at) (== code (promo-code (wp-promo wp))) fail)))))

(define
  active-codes
  (fn (windowed at) (run* code (active-promoo windowed at code))))

;; Datetime-aware quote: the existing pipeline over the time-active ruleset.
(define
  windowed-quote
  (fn
    (ctx cart windowed exclusions at)
    (cart-quote ctx cart (active-ruleset windowed at) exclusions)))
