;; lib/commerce/attribution.sx — line-level discount attribution.
;;
;; The briefing's marquee backward query: "which line item triggered this
;; discount?". promo.sx computes discount amounts at the class/order level;
;; this layer answers the *scope* question relationally and in both directions:
;;   forward  — which lines does code C touch?   (lines-for-code)
;;   backward — which codes touch this line?     (codes-for-line)
;; Both are the same relation promo-toucheso run with different vars bound.
;;
;; A :fixed promo is order-level (touches no single line); query those with
;; order-level-codes. Only promos that actually apply (amount > 0) touch lines.

;; Lines whose sku is in product-class `cls`.
(define
  class-lines
  (fn
    (ctx cart cls)
    (filter
      (fn (l) (= (catalog-class (ctx-catalog ctx) (line-sku l)) cls))
      cart)))

;; The lines a promo applies to (its scope). :fixed is order-level → no lines.
(define
  promo-lines
  (fn
    (ctx cart p)
    (let
      ((k (promo-kind p)))
      (cond
        ((= k :percent) (class-lines ctx cart (nth p 2)))
        ((= k :member)
          (if
            (= (get ctx :customer) :member)
            (class-lines ctx cart (nth p 2))
            (list)))
        ((= k :bundle)
          (filter (fn (l) (= (line-sku l) (nth p 2))) cart))
        (:else (list))))))

;; Relation: promo `code` touches `line`. Only applying promos (amount > 0)
;; touch anything, so an inapplicable promo contributes no pairs.
(define
  promo-toucheso
  (fn
    (ctx cart ruleset code line)
    (fresh
      (p)
      (membero p ruleset)
      (project
        (p)
        (if
          (> (promo-amount ctx cart p) 0)
          (mk-conj
            (== code (promo-code p))
            (membero line (promo-lines ctx cart p)))
          fail)))))

;; --- query helpers ---

(define
  lines-for-code
  (fn
    (ctx cart ruleset code)
    (run* line (promo-toucheso ctx cart ruleset code line))))

(define
  codes-for-line
  (fn
    (ctx cart ruleset line)
    (run* code (promo-toucheso ctx cart ruleset code line))))

(define
  line-touched-by?
  (fn
    (ctx cart ruleset code line)
    (not
      (empty?
        (run
          1
          c
          (mk-conj (promo-toucheso ctx cart ruleset code line) (== c true)))))))

;; Applying order-level (:fixed) promos — discounts with no single line.
(define
  order-level-codes
  (fn
    (ctx cart ruleset)
    (run*
      code
      (fresh
        (p)
        (membero p ruleset)
        (project
          (p)
          (if
            (and
              (> (promo-amount ctx cart p) 0)
              (= (promo-kind p) :fixed))
            (== code (promo-code p))
            fail))))))
