;; ==========================================================================
;; test-vm-closures.sx — Tests for inner closure recursion patterns
;;
;; Requires: test-framework.sx loaded first.
;;
;; These tests exercise patterns where inner closures recurse deeply
;; while sharing mutable state via upvalues. This is the sx-parse
;; pattern: many inner functions close over a mutable cursor variable.
;; Without proper VM closure support, each recursive call would
;; allocate a fresh VM — blowing the stack or hanging.
;; ==========================================================================


;; --------------------------------------------------------------------------
;; Inner closure recursion with mutable upvalues
;; --------------------------------------------------------------------------

(defsuite "inner-closure-recursion"
  (deftest "self-recursive inner closure with set! on captured variable"
    ;; Pattern: closure mutates captured var on each recursive call.
    ;; This is the core pattern in skip-ws, read-str-loop, etc.
    (let ((counter 0))
      (define count-up
        (fn (n)
          (when (> n 0)
            (set! counter (+ counter 1))
            (count-up (- n 1)))))
      (count-up 100)
      (assert-equal 100 counter)))

  (deftest "deep inner closure recursion (500 iterations)"
    ;; Stress test: 500 recursive calls through an inner closure
    ;; mutating a shared upvalue. Would stack-overflow without TCO.
    (let ((acc 0))
      (define sum-up
        (fn (n)
          (if (<= n 0)
            acc
            (do (set! acc (+ acc n))
                (sum-up (- n 1))))))
      (assert-equal 125250 (sum-up 500))))

  (deftest "inner closure reading captured variable updated by another"
    ;; Two closures: one writes, one reads, sharing the same binding.
    (let ((pos 0))
      (define advance! (fn () (set! pos (+ pos 1))))
      (define current  (fn () pos))
      (advance!)
      (advance!)
      (advance!)
      (assert-equal 3 (current))))

  (deftest "recursive closure with multiple mutable upvalues"
    ;; Like sx-parse: multiple cursor variables mutated during recursion.
    (let ((pos 0)
          (count 0))
      (define scan
        (fn (source)
          (when (< pos (len source))
            (set! count (+ count 1))
            (set! pos (+ pos 1))
            (scan source))))
      (scan "hello world")
      (assert-equal 11 pos)
      (assert-equal 11 count))))


;; --------------------------------------------------------------------------
;; Mutual recursion between inner closures
;; --------------------------------------------------------------------------

(defsuite "mutual-inner-closures"
  (deftest "two inner closures calling each other"
    ;; Pattern: read-expr calls read-list, read-list calls read-expr.
    (let ((result (list)))
      (define process-a
        (fn (items)
          (when (not (empty? items))
            (append! result (str "a:" (first items)))
            (process-b (rest items)))))
      (define process-b
        (fn (items)
          (when (not (empty? items))
            (append! result (str "b:" (first items)))
            (process-a (rest items)))))
      (process-a (list 1 2 3 4))
      (assert-equal 4 (len result))
      (assert-equal "a:1" (nth result 0))
      (assert-equal "b:2" (nth result 1))
      (assert-equal "a:3" (nth result 2))
      (assert-equal "b:4" (nth result 3))))

  (deftest "mutual recursion with shared mutable state"
    ;; Both closures read and write the same captured variable.
    (let ((pos 0)
          (source "aAbBcC"))
      (define skip-lower
        (fn ()
          (when (and (< pos (len source))
                     (>= (nth source pos) "a")
                     (<= (nth source pos) "z"))
            (set! pos (+ pos 1))
            (skip-upper))))
      (define skip-upper
        (fn ()
          (when (and (< pos (len source))
                     (>= (nth source pos) "A")
                     (<= (nth source pos) "Z"))
            (set! pos (+ pos 1))
            (skip-lower))))
      (skip-lower)
      (assert-equal 6 pos)))

  (deftest "three-way mutual recursion"
    (let ((n 30)
          (result nil))
      (define step-a
        (fn (i)
          (if (>= i n)
            (set! result "done")
            (step-b (+ i 1)))))
      (define step-b
        (fn (i)
          (step-c (+ i 1))))
      (define step-c
        (fn (i)
          (step-a (+ i 1))))
      (step-a 0)
      (assert-equal "done" result))))


;; --------------------------------------------------------------------------
;; Parser-like patterns (the sx-parse structure)
;; --------------------------------------------------------------------------

(defsuite "parser-pattern"
  (deftest "mini-parser: tokenize digits from string"
    ;; Simplified sx-parse pattern: closure over pos + source,
    ;; multiple inner functions sharing the mutable cursor.
    (let ((pos 0)
          (source "12 34 56")
          (len-src 8))

      (define skip-ws
        (fn ()
          (when (and (< pos len-src) (= (nth source pos) " "))
            (set! pos (+ pos 1))
            (skip-ws))))

      (define read-digits
        (fn ()
          (let ((start pos))
            (define digit-loop
              (fn ()
                (when (and (< pos len-src)
                           (>= (nth source pos) "0")
                           (<= (nth source pos) "9"))
                  (set! pos (+ pos 1))
                  (digit-loop))))
            (digit-loop)
            (slice source start pos))))

      (define read-all
        (fn ()
          (let ((tokens (list)))
            (define parse-loop
              (fn ()
                (skip-ws)
                (when (< pos len-src)
                  (append! tokens (read-digits))
                  (parse-loop))))
            (parse-loop)
            tokens)))

      (let ((tokens (read-all)))
        (assert-equal 3 (len tokens))
        (assert-equal "12" (nth tokens 0))
        (assert-equal "34" (nth tokens 1))
        (assert-equal "56" (nth tokens 2)))))

  (deftest "nested inner closures with upvalue chain"
    ;; Inner function defines its own inner function,
    ;; both closing over the outer mutable variable.
    (let ((total 0))
      (define outer-fn
        (fn (items)
          (for-each
            (fn (item)
              (let ((sub-total 0))
                (define inner-loop
                  (fn (n)
                    (when (> n 0)
                      (set! sub-total (+ sub-total 1))
                      (set! total (+ total 1))
                      (inner-loop (- n 1)))))
                (inner-loop item)))
            items)))
      (outer-fn (list 3 2 1))
      (assert-equal 6 total)))

  (deftest "closure returning accumulated list via append!"
    ;; Pattern from read-list: loop appends to mutable list, returns it.
    (let ((items (list)))
      (define collect
        (fn (source pos)
          (if (>= pos (len source))
            items
            (do (append! items (nth source pos))
                (collect source (+ pos 1))))))
      (let ((result (collect (list "a" "b" "c" "d") 0)))
        (assert-equal 4 (len result))
        (assert-equal "a" (first result))
        (assert-equal "d" (last result))))))


;; --------------------------------------------------------------------------
;; Closures as callbacks to higher-order functions
;; --------------------------------------------------------------------------

(defsuite "closure-ho-callbacks"
  (deftest "map with closure that mutates captured variable"
    (let ((running-total 0))
      (let ((results (map (fn (x)
                            (set! running-total (+ running-total x))
                            running-total)
                          (list 1 2 3 4))))
        (assert-equal (list 1 3 6 10) results)
        (assert-equal 10 running-total))))

  (deftest "reduce with closure over external state"
    (let ((call-count 0))
      (let ((sum (reduce (fn (acc x)
                           (set! call-count (+ call-count 1))
                           (+ acc x))
                         0
                         (list 10 20 30))))
        (assert-equal 60 sum)
        (assert-equal 3 call-count))))

  (deftest "filter with closure reading shared state"
    (let ((threshold 3))
      (let ((result (filter (fn (x) (> x threshold))
                            (list 1 2 3 4 5))))
        (assert-equal (list 4 5) result)))))
