;; ==========================================================================
;; test-closures.sx — Comprehensive tests for closures and lexical scoping
;;
;; Requires: test-framework.sx loaded first.
;; Modules tested: eval.sx (lambda, let, define, set!)
;; ==========================================================================


;; --------------------------------------------------------------------------
;; Closure basics
;; --------------------------------------------------------------------------

(defsuite "closure-basics"
  (deftest "lambda captures variable from enclosing scope"
    (let ((x 10))
      (let ((f (fn () x)))
        (assert-equal 10 (f)))))

  (deftest "lambda captures multiple variables"
    (let ((a 3) (b 4))
      (let ((hyp (fn () (+ (* a a) (* b b)))))
        (assert-equal 25 (hyp)))))

  (deftest "returned lambda retains captured values"
    (define make-greeter
      (fn (greeting)
        (fn (name) (str greeting ", " name "!"))))
    (let ((hello (make-greeter "Hello")))
      (assert-equal "Hello, Alice!" (hello "Alice"))
      (assert-equal "Hello, Bob!" (hello "Bob"))))

  (deftest "factory function returns independent closures"
    (define make-adder
      (fn (n) (fn (x) (+ n x))))
    (let ((add5 (make-adder 5))
          (add10 (make-adder 10)))
      (assert-equal 8 (add5 3))
      (assert-equal 13 (add10 3))
      (assert-equal 15 (add5 10))))

  (deftest "counter via closure"
    (define make-counter
      (fn ()
        (let ((count 0))
          (fn ()
            (set! count (+ count 1))
            count))))
    (let ((counter (make-counter)))
      (assert-equal 1 (counter))
      (assert-equal 2 (counter))
      (assert-equal 3 (counter))))

  (deftest "closure captures value at time of creation"
    ;; Create closure when x=1, then rebind x to 99.
    ;; The closure should still see 1, not 99.
    (let ((x 1))
      (let ((f (fn () x)))
        (let ((x 99))
          (assert-equal 1 (f)))))))


;; --------------------------------------------------------------------------
;; Lexical scope
;; --------------------------------------------------------------------------

(defsuite "lexical-scope"
  (deftest "inner binding shadows outer"
    (let ((x 1))
      (let ((x 2))
        (assert-equal 2 x))))

  (deftest "shadow does not affect outer scope"
    (let ((x 1))
      (let ((x 2))
        (assert-equal 2 x))
      (assert-equal 1 x)))

  (deftest "nested let scoping"
    (let ((x 1) (y 10))
      (let ((x 2) (z 100))
        (assert-equal 2 x)
        (assert-equal 10 y)
        (assert-equal 100 z))
      (assert-equal 1 x)))

  (deftest "lambda body sees its own let bindings"
    (let ((f (fn (x)
               (let ((y (* x 2)))
                 (+ x y)))))
      (assert-equal 9 (f 3))
      (assert-equal 15 (f 5))))

  (deftest "deeply nested scope chain"
    (let ((a 1))
      (let ((b 2))
        (let ((c 3))
          (let ((d 4))
            (assert-equal 10 (+ a b c d)))))))

  (deftest "lambda param shadows enclosing binding"
    (let ((x 99))
      (let ((f (fn (x) (* x 2))))
        (assert-equal 10 (f 5))
        ;; outer x still visible after call
        (assert-equal 99 x))))

  (deftest "sibling let bindings are independent"
    ;; Bindings in the same let do not see each other.
    (let ((a 1) (b 2))
      (assert-equal 1 a)
      (assert-equal 2 b))))


;; --------------------------------------------------------------------------
;; Closure mutation
;; --------------------------------------------------------------------------

(defsuite "closure-mutation"
  (deftest "set! inside closure affects closed-over variable"
    (let ((x 0))
      (let ((inc-x (fn () (set! x (+ x 1)))))
        (inc-x)
        (inc-x)
        (assert-equal 2 x))))

  (deftest "multiple closures sharing same mutable variable"
    (let ((count 0))
      (let ((inc! (fn () (set! count (+ count 1))))
            (dec! (fn () (set! count (- count 1))))
            (get  (fn () count)))
        (inc!)
        (inc!)
        (inc!)
        (dec!)
        (assert-equal 2 (get)))))

  (deftest "set! in let binding visible to later expressions"
    (let ((x 1))
      (set! x 42)
      (assert-equal 42 x)))

  (deftest "set! visible across multiple later expressions"
    (let ((result 0))
      (set! result 5)
      (set! result (* result 2))
      (assert-equal 10 result)))

  (deftest "map creates closures each seeing its own iteration value"
    ;; Each fn passed to map closes over x for that invocation.
    ;; The resulting list of thunks should each return the value they
    ;; were called with at map time.
    (let ((thunks (map (fn (x) (fn () x)) (list 1 2 3 4 5))))
      (assert-equal 1 ((nth thunks 0)))
      (assert-equal 2 ((nth thunks 1)))
      (assert-equal 3 ((nth thunks 2)))
      (assert-equal 4 ((nth thunks 3)))
      (assert-equal 5 ((nth thunks 4))))))


;; --------------------------------------------------------------------------
;; Higher-order closures
;; --------------------------------------------------------------------------

(defsuite "higher-order-closures"
  (deftest "compose two functions"
    (define compose
      (fn (f g) (fn (x) (f (g x)))))
    (let ((double (fn (x) (* x 2)))
          (inc    (fn (x) (+ x 1))))
      (let ((double-then-inc (compose inc double))
            (inc-then-double (compose double inc)))
        (assert-equal 7  (double-then-inc 3))
        (assert-equal 8  (inc-then-double 3)))))

  (deftest "partial application via closure"
    ;; Manual partial — captures first arg, returns fn taking second
    (define partial2
      (fn (f a)
        (fn (b) (f a b))))
    (let ((add (fn (a b) (+ a b)))
          (mul (fn (a b) (* a b))))
      (let ((add10 (partial2 add 10))
            (triple (partial2 mul 3)))
        (assert-equal 15 (add10 5))
        (assert-equal 21 (triple 7)))))

  (deftest "map with closure that captures outer variable"
    (let ((offset 100))
      (let ((result (map (fn (x) (+ x offset)) (list 1 2 3))))
        (assert-equal (list 101 102 103) result))))

  (deftest "reduce with closure"
    (let ((multiplier 3))
      (let ((result (reduce (fn (acc x) (+ acc (* x multiplier))) 0 (list 1 2 3 4))))
        ;; (1*3 + 2*3 + 3*3 + 4*3) = 30
        (assert-equal 30 result))))

  (deftest "filter with closure over threshold"
    (let ((threshold 5))
      (let ((big (filter (fn (x) (> x threshold)) (list 3 5 7 9 1 6))))
        (assert-equal (list 7 9 6) big))))

  (deftest "closure returned from higher-order function composes correctly"
    (define make-multiplier
      (fn (factor) (fn (x) (* x factor))))
    (define pipeline
      (fn (fns x)
        (reduce (fn (acc f) (f acc)) x fns)))
    (let ((double (make-multiplier 2))
          (triple (make-multiplier 3)))
      ;; 5 -> *2 -> 10 -> *3 -> 30
      (assert-equal 30 (pipeline (list double triple) 5)))))
