;; lib/relations/explain.sx — the connecting path: relations' answer to acl's
;; proof tree.
;;
;; A `reach(K,a,b)` derivation is a chain of one-hop `rel` facts a→…→b. The path
;; IS that derivation read off as the node sequence. lib/datalog/ records derived
;; facts but not provenance, so we re-derive the chain over the saturated edge
;; set — but breadth-first, so the path returned is a SHORTEST derivation (fewest
;; hops). Every consecutive pair in the result is a real rel(x,y,kind) fact; no
;; edges are invented. Cycles are handled by a visited set, so cyclic data
;; terminates rather than looping.
;;
;;   (relations-path db a b kind)  → (a … b) | nil
;;   (relations-distance db a b k) → hop count | nil

(define relations-last (fn (xs) (nth xs (- (len xs) 1))))

(define
  relations-filter-unseen
  (fn (xs seen) (filter (fn (x) (not (relations-member? x seen))) xs)))

;; Breadth-first over the kind's edge set. `queue` is a list of partial paths
;; (each a node list ending at its frontier node); `visited` is every node ever
;; enqueued, so each node is expanded once and the first path to reach b is a
;; shortest one.
(define
  relations-path-bfs
  (fn
    (db b kind queue visited)
    (if
      (= (len queue) 0)
      nil
      (let
        ((path (first queue)))
        (let
          ((node (relations-last path)))
          (if
            (= node b)
            path
            (let
              ((succs (relations-filter-unseen (relations-children-of db node kind) visited)))
              (relations-path-bfs
                db
                b
                kind
                (append
                  (rest queue)
                  (map (fn (s) (append path (list s))) succs))
                (append visited succs)))))))))

;; The connecting chain a→…→b under kind (shortest), or nil if unreachable.
;; a = b returns the trivial one-node path.
(define
  relations-path
  (fn
    (db a b kind)
    (if
      (= a b)
      (list a)
      (relations-path-bfs db b kind (list (list a)) (list a)))))

;; Hop count of the shortest path (0 for a=b), or nil if unreachable.
(define
  relations-distance
  (fn
    (db a b kind)
    (let
      ((p (relations-path db a b kind)))
      (if (= p nil) nil (- (len p) 1)))))

;; --- current-db convenience layer ---

(define
  relations-ap-dfs
  (fn
    (db b kind path node)
    (if
      (= node b)
      (list path)
      (relations-concat-map
        (fn
          (nbr)
          (if
            (relations-eng-member? nbr path)
            (list)
            (relations-ap-dfs db b kind (append path (list nbr)) nbr)))
        (relations-children-of db node kind)))))

(define
  relations-all-paths
  (fn
    (db a b kind)
    (if (= a b) (list (list a)) (relations-ap-dfs db b kind (list a) a))))

(define
  relations/path
  (fn (a b kind) (relations-path (relations-ensure-db!) a b kind)))

(define
  relations/distance
  (fn (a b kind) (relations-distance (relations-ensure-db!) a b kind)))

(define
  relations/descendants-any
  (fn (node) (relations-descendants-any (relations-ensure-db!) node)))

(define
  relations/reachable-any?
  (fn (a b) (relations-reachable-any? (relations-ensure-db!) a b)))

(define
  relations/all-paths
  (fn (a b kind) (relations-all-paths (relations-ensure-db!) a b kind)))
