;; lib/kernel/tests/standard.sx — exercises the Kernel standard env.
;;
;; Phase 4 tests verify that the standard env is rich enough to run
;; classic Kernel programs: factorial via recursion, list operations,
;; first-class environment manipulation. Each test starts from a fresh
;; standard env via `(kernel-standard-env)`.

(define ks-suite (refl-make-test-suite))
(define ks-test  (fn (n a e) (refl-test ks-suite n a e)))

(define
  ks-eval
  (fn (src) (kernel-eval (kernel-parse src) (kernel-standard-env))))

(define ks-eval-in (fn (src env) (kernel-eval (kernel-parse src) env)))

(define
  ks-eval-all
  (fn (src env) (kernel-eval-program (kernel-parse-all src) env)))

;; ── $if ──────────────────────────────────────────────────────────
(ks-test "if: true branch" (ks-eval "($if #t 1 2)") 1)
(ks-test "if: false branch" (ks-eval "($if #f 1 2)") 2)
(ks-test "if: predicate"
  (ks-eval "($if (<=? 1 2) ($quote yes) ($quote no))") "yes")
(ks-test
  "if: untaken branch not evaluated"
  (ks-eval "($if #t 42 nope)")
  42)

;; ── $define! + arithmetic ───────────────────────────────────────
(ks-test
  "define!: returns value"
  (let ((env (kernel-standard-env))) (ks-eval-in "($define! x 5)" env))
  5)

(ks-test
  "define!: bound in env"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in "($define! x 5)" env)
    (ks-eval-in "x" env))
  5)

(ks-test "arith: +" (ks-eval "(+ 2 3)") 5)
(ks-test "arith: -" (ks-eval "(- 10 4)") 6)
(ks-test "arith: *" (ks-eval "(* 6 7)") 42)
(ks-test "arith: /" (ks-eval "(/ 20 5)") 4)
(ks-test "cmp: <  true" (ks-eval "(< 1 2)") true)
(ks-test "cmp: <  false" (ks-eval "(< 2 1)") false)
(ks-test "cmp: >=" (ks-eval "(>=? 2 2)") true)
(ks-test "cmp: <=" (ks-eval "(<=? 2 3)") true)
(ks-test "cmp: =" (ks-eval "(=? 7 7)") true)

;; ── $sequence ────────────────────────────────────────────────────
(ks-test "sequence: empty" (ks-eval "($sequence)") nil)
(ks-test "sequence: single" (ks-eval "($sequence 99)") 99)
(ks-test
  "sequence: multi-effect"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in "($sequence ($define! a 1) ($define! b 2) (+ a b))" env))
  3)

;; ── list primitives ──────────────────────────────────────────────
(ks-test
  "list: builds"
  (ks-eval "(list 1 2 3)")
  (list 1 2 3))
(ks-test "list: empty" (ks-eval "(list)") (list))
(ks-test
  "cons: prepend"
  (ks-eval "(cons 0 (list 1 2 3))")
  (list 0 1 2 3))
(ks-test "car: head" (ks-eval "(car (list 10 20 30))") 10)
(ks-test
  "cdr: tail"
  (ks-eval "(cdr (list 10 20 30))")
  (list 20 30))
(ks-test "length: 3" (ks-eval "(length (list 1 2 3))") 3)
(ks-test "length: 0" (ks-eval "(length (list))") 0)
(ks-test "null?: empty" (ks-eval "(null? (list))") true)
(ks-test "null?: nonempty" (ks-eval "(null? (list 1))") false)
(ks-test "pair?: empty" (ks-eval "(pair? (list))") false)
(ks-test "pair?: nonempty" (ks-eval "(pair? (list 1))") true)

;; ── $quote ───────────────────────────────────────────────────────
(ks-test "quote: symbol" (ks-eval "($quote foo)") "foo")
(ks-test
  "quote: list"
  (ks-eval "($quote (+ 1 2))")
  (list "+" 1 2))

;; ── boolean / not ────────────────────────────────────────────────
(ks-test "not: true" (ks-eval "(not #t)") false)
(ks-test "not: false" (ks-eval "(not #f)") true)

;; ── factorial ────────────────────────────────────────────────────
(ks-test
  "factorial: 5!"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
      env)
    (ks-eval-in "(factorial 5)" env))
  120)

(ks-test
  "factorial: 0! = 1"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
      env)
    (ks-eval-in "(factorial 0)" env))
  1)

(ks-test
  "factorial: 10!"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
      env)
    (ks-eval-in "(factorial 10)" env))
  3628800)

;; ── recursive list operations ────────────────────────────────────
(ks-test
  "sum: recursive over list"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! sum ($lambda (xs) ($if (null? xs) 0 (+ (car xs) (sum (cdr xs))))))"
      env)
    (ks-eval-in "(sum (list 1 2 3 4 5))" env))
  15)

(ks-test
  "len: recursive count"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! mylen ($lambda (xs) ($if (null? xs) 0 (+ 1 (mylen (cdr xs))))))"
      env)
    (ks-eval-in "(mylen (list 1 2 3 4))" env))
  4)

(ks-test
  "map-add1: build new list"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! add1-all ($lambda (xs) ($if (null? xs) (list) (cons (+ 1 (car xs)) (add1-all (cdr xs))))))"
      env)
    (ks-eval-in "(add1-all (list 10 20 30))" env))
  (list 11 21 31))

;; ── eval as a first-class applicative ────────────────────────────
(ks-test
  "eval: applies to constructed form"
  (ks-eval "(eval (list ($quote +) 2 3) (get-current-environment))")
  5)

(ks-test
  "eval: with a fresh make-environment"
  (guard
    (e (true :raised))
    (ks-eval "(eval ($quote (+ 1 2)) (make-environment))"))
  :raised)

(ks-test
  "eval: in extended env sees parent's bindings"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in "($define! shared 7)" env)
    (ks-eval-in
      "(eval ($quote shared) (make-environment (get-current-environment)))"
      env))
  7)

;; ── get-current-environment ──────────────────────────────────────
(ks-test
  "get-current-environment: returns env"
  (kernel-env? (ks-eval "(get-current-environment)"))
  true)

(ks-test
  "get-current-environment: contains $if"
  (let
    ((env (ks-eval "(get-current-environment)")))
    (kernel-env-has? env "$if"))
  true)

(ks-test
  "make-environment: empty"
  (let ((env (ks-eval "(make-environment)"))) (kernel-env-has? env "$if"))
  false)

(ks-test
  "make-environment: child sees parent"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in "($define! marker 123)" env)
    (let
      ((child (ks-eval-in "(make-environment (get-current-environment))" env)))
      (kernel-env-has? child "marker")))
  true)

;; ── closures and lexical scope ───────────────────────────────────
(ks-test
  "closure: captures binding"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! make-adder ($lambda (n) ($lambda (x) (+ x n))))"
      env)
    (ks-eval-in "($define! add5 (make-adder 5))" env)
    (ks-eval-in "(add5 10)" env))
  15)

(ks-test
  "closure: nested lookups"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! curry-add ($lambda (a) ($lambda (b) ($lambda (c) (+ a (+ b c))))))"
      env)
    (ks-eval-in "(((curry-add 1) 2) 3)" env))
  6)

;; ── operative defined in standard env can reach $define! ─────────
(ks-test
  "custom: define-via-vau"
  (let
    ((env (kernel-standard-env)))
    (ks-eval-in
      "($define! $let-it ($vau (name expr) e ($sequence ($define! tmp (eval expr e)) (eval (list ($quote $define!) name (list ($quote $quote) tmp)) e) tmp)))"
      env)
    (ks-eval-in "($let-it z 77)" env)
    (ks-eval-in "z" env))
  77)

;; ── quasiquote ──────────────────────────────────────────────────
(ks-test "qq: plain atom"      (ks-eval "`hello") "hello")
(ks-test "qq: plain list"      (ks-eval "`(a b c)") (list "a" "b" "c"))
(ks-test "qq: unquote splices value"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! x 42)" env)
    (ks-eval-in "`(a ,x b)" env)) (list "a" 42 "b"))
(ks-test "qq: unquote-splicing splices list"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! xs (list 1 2 3))" env)
    (ks-eval-in "`(a ,@xs b)" env)) (list "a" 1 2 3 "b"))
(ks-test "qq: unquote-splicing at end"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! xs (list 9 8))" env)
    (ks-eval-in "`(a b ,@xs)" env)) (list "a" "b" 9 8))
(ks-test "qq: unquote-splicing at start"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! xs (list 1 2))" env)
    (ks-eval-in "`(,@xs c)" env)) (list 1 2 "c"))
(ks-test "qq: nested list with unquote inside"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! x 5)" env)
    (ks-eval-in "`(a (b ,x) c)" env))
  (list "a" (list "b" 5) "c"))
(ks-test "qq: error on bare unquote-splicing into non-list"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! x 42)" env)
    (guard (e (true :raised))
      (ks-eval-in "`(a ,@x b)" env)))
  :raised)

;; ── $cond / $when / $unless ─────────────────────────────────────
(ks-test "cond: first match"
  (ks-eval "($cond (#f 1) (#t 2) (#t 3))") 2)
(ks-test "cond: else fallback"
  (ks-eval "($cond (#f 1) (else 99))") 99)
(ks-test "cond: no match returns nil"
  (ks-eval "($cond (#f 1) (#f 2))") nil)
(ks-test "cond: empty clauses returns nil"
  (ks-eval "($cond)") nil)
(ks-test "cond: multi-expr body"
  (ks-eval "($cond (#t 1 2 3))") 3)
(ks-test "cond: doesn't evaluate untaken clauses"
  ;; If the second clause's test were evaluated, the unbound `nope` would error.
  (ks-eval "($cond (#t 7) (nope ignored))") 7)
(ks-test "cond: predicate evaluation"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! n 5)" env)
    (ks-eval-in "($cond ((< n 0) ($quote negative)) ((= n 0) ($quote zero)) (else ($quote positive)))" env))
  "positive")

(ks-test "when: true runs body"
  (ks-eval "($when #t 1 2 3)") 3)
(ks-test "when: false returns nil"
  (ks-eval "($when #f 1 2 3)") nil)
(ks-test "when: skips body when false"
  (ks-eval "($when #f nope)") nil)

(ks-test "unless: false runs body"
  (ks-eval "($unless #f 99)") 99)
(ks-test "unless: true returns nil"
  (ks-eval "($unless #t 99)") nil)
(ks-test "unless: skips body when true"
  (ks-eval "($unless #t nope)") nil)

;; ── $and? / $or? short-circuit ──────────────────────────────────
(ks-test "and: empty returns true"   (ks-eval "($and?)") true)
(ks-test "and: single returns value" (ks-eval "($and? 42)") 42)
(ks-test "and: all true returns last"
  (ks-eval "($and? 1 2 3)") 3)
(ks-test "and: first false short-circuits"
  (ks-eval "($and? #f nope)") false)
(ks-test "and: false in middle short-circuits"
  (ks-eval "($and? 1 #f nope)") false)
(ks-test "or: empty returns false"  (ks-eval "($or?)") false)
(ks-test "or: single returns value" (ks-eval "($or? 42)") 42)
(ks-test "or: first truthy short-circuits"
  (ks-eval "($or? 99 nope)") 99)
(ks-test "or: all false returns last"
  (ks-eval "($or? #f #f #f)") false)
(ks-test "or: middle truthy"
  (ks-eval "($or? #f 42 nope)") 42)

;; ── variadic arithmetic ─────────────────────────────────────────
(ks-test "+: zero args = 0"    (ks-eval "(+)") 0)
(ks-test "+: one arg = arg"    (ks-eval "(+ 7)") 7)
(ks-test "+: two args"         (ks-eval "(+ 3 4)") 7)
(ks-test "+: five args"        (ks-eval "(+ 1 2 3 4 5)") 15)

(ks-test "*: zero args = 1"    (ks-eval "(*)") 1)
(ks-test "*: one arg"          (ks-eval "(* 7)") 7)
(ks-test "*: four args"        (ks-eval "(* 1 2 3 4)") 24)

(ks-test "-: one arg negates"  (ks-eval "(- 10)") -10)
(ks-test "-: two args"         (ks-eval "(- 10 3)") 7)
(ks-test "-: four args fold"   (ks-eval "(- 100 1 2 3)") 94)

(ks-test "/: two args"         (ks-eval "(/ 20 5)") 4)
(ks-test "/: three args fold"  (ks-eval "(/ 100 2 5)") 10)

;; ── variadic chained comparison ─────────────────────────────────
(ks-test "<: chained ascending"  (ks-eval "(< 1 2 3 4 5)") true)
(ks-test "<: not strict"          (ks-eval "(< 1 2 2 3)") false)
(ks-test "<: anti-monotonic"      (ks-eval "(< 5 3)") false)
(ks-test ">: chained descending" (ks-eval "(> 5 4 3 2 1)") true)
(ks-test "<=? ascending equals"   (ks-eval "(<=? 1 1 2 3 3)") true)
(ks-test "<=? violation"          (ks-eval "(<=? 1 2 1)") false)
(ks-test ">=? descending equals" (ks-eval "(>=? 3 3 2 1)") true)

;; ── list combinators ────────────────────────────────────────────
(ks-test "map: square"
  (ks-eval "(map ($lambda (x) (* x x)) (list 1 2 3 4))")
  (list 1 4 9 16))
(ks-test "map: empty list"
  (ks-eval "(map ($lambda (x) x) (list))") (list))
(ks-test "map: identity preserves"
  (ks-eval "(map ($lambda (x) x) (list 1 2 3))") (list 1 2 3))
(ks-test "map: with closure over outer"
  (let ((env (kernel-standard-env)))
    (ks-eval-in "($define! k 10)" env)
    (ks-eval-in "(map ($lambda (x) (+ x k)) (list 1 2 3))" env))
  (list 11 12 13))

(ks-test "filter: positives"
  (ks-eval "(filter ($lambda (x) (< 0 x)) (list -2 -1 0 1 2))")
  (list 1 2))
(ks-test "filter: empty result"
  (ks-eval "(filter ($lambda (x) #f) (list 1 2 3))") (list))
(ks-test "filter: all match"
  (ks-eval "(filter ($lambda (x) #t) (list 1 2 3))") (list 1 2 3))

(ks-test "reduce: sum"
  (ks-eval "(reduce ($lambda (a b) (+ a b)) 0 (list 1 2 3 4 5))") 15)
(ks-test "reduce: product"
  (ks-eval "(reduce ($lambda (a b) (* a b)) 1 (list 1 2 3 4))") 24)
(ks-test "reduce: empty returns init"
  (ks-eval "(reduce ($lambda (a b) (+ a b)) 42 (list))") 42)
(ks-test "reduce: build list"
  (ks-eval "(reduce ($lambda (acc x) (cons x acc)) () (list 1 2 3))")
  (list 3 2 1))

;; ── apply ────────────────────────────────────────────────────────
(ks-test "apply: + over list"
  (ks-eval "(apply + (list 1 2 3 4 5))") 15)
(ks-test "apply: lambda"
  (ks-eval "(apply ($lambda (a b c) (* a (+ b c))) (list 2 3 4))") 14)
(ks-test "apply: list identity"
  (ks-eval "(apply list (list 1 2 3))") (list 1 2 3))
(ks-test "apply: empty args list"
  (ks-eval "(apply + (list))") 0)
(ks-test "apply: single arg list"
  (ks-eval "(apply ($lambda (x) (* x 10)) (list 7))") 70)
(ks-test "apply: built via map+apply"
  ;; (apply + (map ($lambda (x) (* x x)) (list 1 2 3))) → 1+4+9 = 14
  (ks-eval
    "(apply + (map ($lambda (x) (* x x)) (list 1 2 3)))") 14)
(ks-test "apply: error on non-list args"
  (guard (e (true :raised))
    (ks-eval "(apply + 5)"))
  :raised)

;; ── append / reverse ────────────────────────────────────────────
(ks-test "append: two lists"
  (ks-eval "(append (list 1 2) (list 3 4))") (list 1 2 3 4))
(ks-test "append: three lists"
  (ks-eval "(append (list 1) (list 2) (list 3))") (list 1 2 3))
(ks-test "append: empty list"
  (ks-eval "(append)") (list))
(ks-test "append: one list"
  (ks-eval "(append (list 1 2 3))") (list 1 2 3))
(ks-test "append: empty + nonempty"
  (ks-eval "(append (list) (list 1 2))") (list 1 2))
(ks-test "append: nonempty + empty"
  (ks-eval "(append (list 1 2) (list))") (list 1 2))
(ks-test "append: error on non-list"
  (guard (e (true :raised))
    (ks-eval "(append (list 1) 5)"))
  :raised)

(ks-test "reverse: four elements"
  (ks-eval "(reverse (list 1 2 3 4))") (list 4 3 2 1))
(ks-test "reverse: empty"
  (ks-eval "(reverse (list))") (list))
(ks-test "reverse: single"
  (ks-eval "(reverse (list 99))") (list 99))
(ks-test "reverse: double reverse is identity"
  (ks-eval "(reverse (reverse (list 1 2 3)))") (list 1 2 3))

(define ks-tests-run! (fn () (refl-test-report ks-suite)))
