;; lib/commerce/federation.sx — cross-instance catalog (federated marketplace).
;;
;; STUB: instances are registered in-process; there is no real network or
;; ActivityPub transport here (that lives in the federation service). The point
;; is the relational model: a federated catalog is just the UNION of each
;; instance's product facts, tagged with origin, so the same miniKanren
;; relations answer cross-instance questions — "which instances sell this sku?",
;; "which is cheapest?" — as backward queries, no new query engine.

(define federation-stub? true)

(define make-federation (fn (instance cat) {:instances (list (list instance cat))}))

(define
  federation-add
  (fn
    (fed instance cat)
    (assoc
      fed
      :instances (append (get fed :instances) (list (list instance cat))))))

(define federation-instances (fn (fed) (map first (get fed :instances))))

;; Flatten to (instance sku price class) origin-tagged tuples.
(define
  fed-products
  (fn
    (fed)
    (reduce
      (fn
        (acc pair)
        (let
          ((instance (first pair)) (cat (nth pair 1)))
          (append
            acc
            (map (fn (p) (cons instance p)) (get cat :products)))))
      (list)
      (get fed :instances))))

;; --- relations over the federated catalog (multidirectional) ---

(define
  fed-producto
  (fn
    (fed instance sku price class)
    (membero (list instance sku price class) (fed-products fed))))

(define
  fed-priceo
  (fn
    (fed instance sku price)
    (fresh (c) (fed-producto fed instance sku price c))))

;; --- query helpers ---

;; Which instances carry a sku? (backward query)
(define
  instances-with-sku
  (fn (fed sku) (run* inst (fresh (p c) (fed-producto fed inst sku p c)))))

;; All (price instance) offers for a sku, in federation order.
(define
  sku-offers
  (fn
    (fed sku)
    (run*
      pair
      (fresh
        (inst p c)
        (fed-producto fed inst sku p c)
        (== pair (list p inst))))))

;; Cheapest (price instance) for a sku — the deterministic selection layer.
(define
  cheapest-offer
  (fn
    (fed sku)
    (let
      ((offers (sku-offers fed sku)))
      (if
        (empty? offers)
        nil
        (reduce
          (fn (best x) (if (< (first x) (first best)) x best))
          (first offers)
          offers)))))
