;; lib/prolog/tests/query_api.sx — tests for pl-load/pl-query-all/pl-query-one/pl-query

(define pl-qa-test-count 0)
(define pl-qa-test-pass 0)
(define pl-qa-test-fail 0)
(define pl-qa-test-failures (list))

(define
  pl-qa-test!
  (fn
    (name got expected)
    (begin
      (set! pl-qa-test-count (+ pl-qa-test-count 1))
      (if
        (= got expected)
        (set! pl-qa-test-pass (+ pl-qa-test-pass 1))
        (begin
          (set! pl-qa-test-fail (+ pl-qa-test-fail 1))
          (append!
            pl-qa-test-failures
            (str name "\n    expected: " expected "\n    got:      " got)))))))

(define
  pl-qa-src
  "parent(tom, bob). parent(tom, liz). parent(bob, ann). ancestor(X, Y) :- parent(X, Y). ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).")

(define pl-qa-db (pl-load pl-qa-src))

;; ── pl-load ──

(pl-qa-test!
  "pl-load returns a usable DB (pl-query-all non-nil)"
  (not (nil? pl-qa-db))
  true)

;; ── pl-query-all: basic fact lookup ──

(pl-qa-test!
  "query-all parent(tom, X): 2 solutions"
  (len (pl-query-all pl-qa-db "parent(tom, X)"))
  2)

(pl-qa-test!
  "query-all parent(tom, X): first solution X=bob"
  (dict-get (first (pl-query-all pl-qa-db "parent(tom, X)")) "X")
  "bob")

(pl-qa-test!
  "query-all parent(tom, X): second solution X=liz"
  (dict-get (nth (pl-query-all pl-qa-db "parent(tom, X)") 1) "X")
  "liz")

;; ── pl-query-all: no solutions ──

(pl-qa-test!
  "query-all no solutions returns empty list"
  (pl-query-all pl-qa-db "parent(liz, X)")
  (list))

;; ── pl-query-all: boolean query (no vars) ──

(pl-qa-test!
  "boolean success: 1 solution (empty dict)"
  (len (pl-query-all pl-qa-db "parent(tom, bob)"))
  1)

(pl-qa-test!
  "boolean success: solution has no bindings"
  (empty? (keys (first (pl-query-all pl-qa-db "parent(tom, bob)"))))
  true)

(pl-qa-test!
  "boolean fail: 0 solutions"
  (len (pl-query-all pl-qa-db "parent(bob, tom)"))
  0)

;; ── pl-query-all: multi-var ──

(pl-qa-test!
  "query-all parent(X, Y): 3 solutions total"
  (len (pl-query-all pl-qa-db "parent(X, Y)"))
  3)

;; ── pl-query-all: rule-based (ancestor/2) ──

(pl-qa-test!
  "query-all ancestor(tom, X): 3 descendants (bob, liz, ann)"
  (len (pl-query-all pl-qa-db "ancestor(tom, X)"))
  3)

;; ── pl-query-all: built-in in query ──

(pl-qa-test!
  "query with is/2 built-in"
  (dict-get (first (pl-query-all pl-qa-db "X is 2 + 3")) "X")
  "5")

;; ── pl-query-one ──

(pl-qa-test!
  "query-one returns first solution"
  (dict-get (pl-query-one pl-qa-db "parent(tom, X)") "X")
  "bob")

(pl-qa-test!
  "query-one returns nil for no solutions"
  (pl-query-one pl-qa-db "parent(liz, X)")
  nil)

;; ── pl-query convenience ──

(pl-qa-test!
  "pl-query convenience: count solutions"
  (len (pl-query "likes(alice, bob). likes(alice, carol)." "likes(alice, X)"))
  2)

(pl-qa-test!
  "pl-query convenience: first solution"
  (dict-get (first (pl-query "likes(alice, bob). likes(alice, carol)." "likes(alice, X)")) "X")
  "bob")

(pl-qa-test!
  "pl-query with empty source (built-ins only)"
  (dict-get (first (pl-query "" "X is 6 * 7")) "X")
  "42")

(define pl-query-api-tests-run! (fn () {:failed pl-qa-test-fail :passed pl-qa-test-pass :total pl-qa-test-count :failures pl-qa-test-failures}))
