;; lib/prolog/tests/term_inspect.sx — copy_term/2, functor/3, arg/3.

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

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

(define
  pl-tt-goal
  (fn
    (src env)
    (pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))

(define pl-tt-db (pl-mk-db))

;; ── copy_term/2 ──

(pl-tt-test!
  "copy_term ground compound succeeds + copy = original"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "copy_term(foo(a, b), X), X = foo(a, b)" {})
    (pl-mk-trail))
  true)

(pl-tt-test!
  "copy_term preserves var aliasing in source"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "copy_term(p(Y, Y), p(A, B)), A = 5, B = 5" {})
    (pl-mk-trail))
  true)

(pl-tt-test!
  "copy_term distinct vars stay distinct"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "copy_term(p(Y, Y), p(A, B)), A = 5, B = 6" {})
    (pl-mk-trail))
  false)

(define pl-tt-env-1 {})
(pl-solve-once!
  pl-tt-db
  (pl-tt-goal "copy_term(X, Y), Y = 5" pl-tt-env-1)
  (pl-mk-trail))
(pl-tt-test!
  "copy_term: binding the copy doesn't bind the source"
  (pl-var-bound? (dict-get pl-tt-env-1 "X"))
  false)

;; ── functor/3 ──

(define pl-tt-env-2 {})
(pl-solve-once!
  pl-tt-db
  (pl-tt-goal "functor(foo(a, b, c), F, N)" pl-tt-env-2)
  (pl-mk-trail))
(pl-tt-test!
  "functor of compound: F = foo"
  (pl-atom-name (pl-walk-deep (dict-get pl-tt-env-2 "F")))
  "foo")
(pl-tt-test!
  "functor of compound: N = 3"
  (pl-num-val (pl-walk-deep (dict-get pl-tt-env-2 "N")))
  3)

(define pl-tt-env-3 {})
(pl-solve-once!
  pl-tt-db
  (pl-tt-goal "functor(hello, F, N)" pl-tt-env-3)
  (pl-mk-trail))
(pl-tt-test!
  "functor of atom: F = hello"
  (pl-atom-name (pl-walk-deep (dict-get pl-tt-env-3 "F")))
  "hello")
(pl-tt-test!
  "functor of atom: N = 0"
  (pl-num-val (pl-walk-deep (dict-get pl-tt-env-3 "N")))
  0)

(pl-tt-test!
  "functor construct compound: T unifies with foo(a, b)"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "functor(T, foo, 2), T = foo(a, b)" {})
    (pl-mk-trail))
  true)

(pl-tt-test!
  "functor construct atom: T = hello"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "functor(T, hello, 0), T = hello" {})
    (pl-mk-trail))
  true)

;; ── arg/3 ──

(pl-tt-test!
  "arg(1, foo(a, b, c), a)"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "arg(1, foo(a, b, c), a)" {})
    (pl-mk-trail))
  true)

(pl-tt-test!
  "arg(2, foo(a, b, c), X) → X = b"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "arg(2, foo(a, b, c), X), X = b" {})
    (pl-mk-trail))
  true)

(pl-tt-test!
  "arg out-of-range high fails"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "arg(4, foo(a, b, c), X)" {})
    (pl-mk-trail))
  false)

(pl-tt-test!
  "arg(0, ...) fails (1-indexed)"
  (pl-solve-once!
    pl-tt-db
    (pl-tt-goal "arg(0, foo(a), X)" {})
    (pl-mk-trail))
  false)

(define pl-term-inspect-tests-run! (fn () {:failed pl-tt-test-fail :passed pl-tt-test-pass :total pl-tt-test-count :failures pl-tt-test-failures}))
