;; lib/events/booking-notify.sx — derive lifecycle notifications from the
;; booking stream, for delivery via notify.sx.
;;
;; Walking the append-only booking stream yields one notification per state
;; change, in order, classified by kind:
;;
;;   :booked      a confirmed booking
;;   :promoted    a booking for an actor who was on the waitlist (auto-promote)
;;   :held        a provisional hold (pending payment)
;;   :confirmed   a held seat became confirmed (payment succeeded)
;;   :released    a held seat was released (payment failed/expired)
;;   :cancelled   a seat was given up
;;   :waitlisted  an actor joined the waitlist
;;
;; Promotion is detected by folding the waitlist as we walk: a :booking for an
;; actor currently on the waitlist is a promotion, not a fresh booking.
;;
;; Each notification's id is occ-key/seq (the stream seq is unique and stable),
;; so re-deriving and re-delivering is idempotent — the notify transport dedups
;; on this id and never double-pings.

(define
  ev-bn-kind
  (fn
    (typ promoted?)
    (cond
      ((= typ :hold) :held)
      ((= typ :booking) (if promoted? :promoted :booked))
      ((= typ :confirm) :confirmed)
      ((= typ :cancel) :cancelled)
      ((= typ :release) :released)
      ((= typ :waitlist) :waitlisted)
      (else nil))))

(define
  ev-bn-update-waiting
  (fn
    (typ actor waiting)
    (cond
      ((= typ :waitlist)
        (if
          (ev-bk-member? actor waiting)
          waiting
          (ev-bk-append waiting actor)))
      ((= typ :unwaitlist) (ev-bk-remove waiting actor))
      ((= typ :booking) (ev-bk-remove waiting actor))
      ((= typ :hold) (ev-bk-remove waiting actor))
      (else waiting))))

(define ev-bn-mk (fn (occ-key label actor kind seq) {:id (str occ-key "/" seq) :event label :kind kind :recipient actor :seq seq}))

(define
  ev-bn-step
  (fn
    (occ-key label events waiting)
    (if
      (empty? events)
      (list)
      (let
        ((e (first events)))
        (let
          ((typ (persist/event-type e))
            (actor (get (persist/event-data e) :actor))
            (seq (persist/event-seq e)))
          (let
            ((promoted? (and (= typ :booking) (ev-bk-member? actor waiting))))
            (let
              ((kind (ev-bn-kind typ promoted?))
                (waiting2 (ev-bn-update-waiting typ actor waiting)))
              (if
                (nil? kind)
                (ev-bn-step occ-key label (rest events) waiting2)
                (cons
                  (ev-bn-mk occ-key label actor kind seq)
                  (ev-bn-step occ-key label (rest events) waiting2))))))))))

;; The ordered lifecycle notifications for an occurrence's bookings. `label` is
;; a human-facing event id carried on each notification.
(define
  ev/booking-notifications
  (fn
    (b occ-key label)
    (ev-bn-step
      occ-key
      label
      (persist/read b (ev-booking-stream occ-key))
      (list))))

;; Filter notifications to a single kind.
(define
  ev/notify-of-kind
  (fn (notifs kind) (filter (fn (n) (= (get n :kind) kind)) notifs)))

;; Project a notification to notify.sx's (id recipient body) wire shape.
(define
  ev/booking-notify->msg
  (fn
    (n)
    (list
      (get n :id)
      (get n :recipient)
      (list :booking-event (get n :kind) (get n :event)))))
