;; ==========================================================================
;; test-cek-reactive.sx — Tests for deref-as-shift reactive rendering
;;
;; Tests that (deref signal) inside a reactive-reset boundary performs
;; continuation capture: the rest of the expression becomes the subscriber.
;;
;; Requires: test-framework.sx, frames.sx, cek.sx, signals.sx loaded first.
;; ==========================================================================


;; --------------------------------------------------------------------------
;; Basic deref behavior through CEK
;; --------------------------------------------------------------------------

(defsuite "deref pass-through"
  (deftest "deref non-signal passes through"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(deref 42)")
                    (test-env))))
      (assert-equal 42 result)))

  (deftest "deref nil passes through"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(deref nil)")
                    (test-env))))
      (assert-nil result)))

  (deftest "deref string passes through"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(deref \"hello\")")
                    (test-env))))
      (assert-equal "hello" result))))


;; --------------------------------------------------------------------------
;; Deref signal without reactive-reset (no shift)
;; --------------------------------------------------------------------------

(defsuite "deref signal without reactive-reset"
  (deftest "deref signal returns current value"
    (let ((s (signal 99)))
      (env-bind! (test-env) "test-sig" s)
      (let ((result (eval-expr-cek
                      (sx-parse-one "(deref test-sig)")
                      (test-env))))
        (assert-equal 99 result))))

  (deftest "deref signal in expression returns computed value"
    (let ((s (signal 10)))
      (env-bind! (test-env) "test-sig" s)
      (let ((result (eval-expr-cek
                      (sx-parse-one "(+ 5 (deref test-sig))")
                      (test-env))))
        (assert-equal 15 result)))))


;; --------------------------------------------------------------------------
;; Reactive reset + deref: continuation capture
;; --------------------------------------------------------------------------

(defsuite "reactive-reset shift"
  (deftest "deref signal with reactive-reset captures continuation"
    (let ((s (signal 42))
          (captured-val nil))
      ;; Run CEK with a ReactiveResetFrame
      (let ((result (cek-run
                      (make-cek-state
                        (sx-parse-one "(deref test-sig)")
                        (let ((e (env-extend (test-env))))
                          (env-bind! e "test-sig" s)
                          e)
                        (list (make-reactive-reset-frame
                                (test-env)
                                (fn (v) (set! captured-val v))
                                true))))))
        ;; Initial render: returns current value, update-fn NOT called (first-render)
        (assert-equal 42 result)
        (assert-nil captured-val))))

  (deftest "signal change invokes subscriber with update-fn"
    (let ((s (signal 10))
          (update-calls (list)))
      ;; Set up reactive-reset with tracking update-fn
      (scope-push! "sx-island-scope" nil)
      (let ((e (env-extend (test-env))))
        (env-bind! e "test-sig" s)
        (cek-run
          (make-cek-state
            (sx-parse-one "(deref test-sig)")
            e
            (list (make-reactive-reset-frame
                    e
                    (fn (v) (append! update-calls v))
                    true)))))
      ;; Change signal — subscriber should fire
      (reset! s 20)
      (assert-equal 1 (len update-calls))
      (assert-equal 20 (first update-calls))
      ;; Change again
      (reset! s 30)
      (assert-equal 2 (len update-calls))
      (assert-equal 30 (nth update-calls 1))
      (scope-pop! "sx-island-scope")))

  (deftest "expression with deref captures rest as continuation"
    (let ((s (signal 5))
          (update-calls (list)))
      (scope-push! "sx-island-scope" nil)
      (let ((e (env-extend (test-env))))
        (env-bind! e "test-sig" s)
        ;; (str "val=" (deref test-sig)) — continuation captures (str "val=" [HOLE])
        (let ((result (cek-run
                        (make-cek-state
                          (sx-parse-one "(str \"val=\" (deref test-sig))")
                          e
                          (list (make-reactive-reset-frame
                                  e
                                  (fn (v) (append! update-calls v))
                                  true))))))
          (assert-equal "val=5" result)))
      ;; Change signal — should get updated string
      (reset! s 42)
      (assert-equal 1 (len update-calls))
      (assert-equal "val=42" (first update-calls))
      (scope-pop! "sx-island-scope"))))


;; --------------------------------------------------------------------------
;; Disposal and cleanup
;; --------------------------------------------------------------------------

(defsuite "disposal"
  (deftest "scope cleanup unsubscribes continuation"
    (let ((s (signal 1))
          (update-calls (list))
          (disposers (list)))
      ;; Create island scope with collector that accumulates disposers
      (scope-push! "sx-island-scope" (fn (d) (append! disposers d)))
      (let ((e (env-extend (test-env))))
        (env-bind! e "test-sig" s)
        (cek-run
          (make-cek-state
            (sx-parse-one "(deref test-sig)")
            e
            (list (make-reactive-reset-frame
                    e
                    (fn (v) (append! update-calls v))
                    true)))))
      ;; Pop scope — call all disposers
      (scope-pop! "sx-island-scope")
      (for-each (fn (d) (cek-call d nil)) disposers)
      ;; Change signal — no update should fire
      (reset! s 999)
      (assert-equal 0 (len update-calls)))))


;; --------------------------------------------------------------------------
;; cek-call integration — computed/effect use cek-call dispatch
;; --------------------------------------------------------------------------

(defsuite "cek-call dispatch"
  (deftest "cek-call invokes native function"
    (let ((log (list)))
      (cek-call (fn (x) (append! log x)) (list 42))
      (assert-equal (list 42) log)))

  (deftest "cek-call invokes zero-arg lambda"
    (let ((result (cek-call (fn () (+ 1 2)) nil)))
      (assert-equal 3 result)))

  (deftest "cek-call with nil function returns nil"
    (assert-nil (cek-call nil nil)))

  (deftest "computed tracks deps via cek-call"
    (let ((s (signal 10)))
      (let ((c (computed (fn () (* 2 (deref s))))))
        (assert-equal 20 (deref c))
        (reset! s 5)
        (assert-equal 10 (deref c)))))

  (deftest "effect runs and re-runs via cek-call"
    (let ((s (signal "a"))
          (log (list)))
      (effect (fn () (append! log (deref s))))
      (assert-equal (list "a") log)
      (reset! s "b")
      (assert-equal (list "a" "b") log)))

  (deftest "effect cleanup runs on re-trigger"
    (let ((s (signal 0))
          (log (list)))
      (effect (fn ()
        (let ((val (deref s)))
          (append! log (str "run:" val))
          ;; Return cleanup function
          (fn () (append! log (str "clean:" val))))))
      (assert-equal (list "run:0") log)
      (reset! s 1)
      (assert-equal (list "run:0" "clean:0" "run:1") log)))

  (deftest "batch coalesces via cek-call"
    (let ((s (signal 0))
          (count (signal 0)))
      (effect (fn () (do (deref s) (swap! count inc))))
      (assert-equal 1 (deref count))
      (batch (fn ()
        (reset! s 1)
        (reset! s 2)
        (reset! s 3)))
      ;; batch should coalesce — effect runs once, not three times
      (assert-equal 2 (deref count)))))


;; --------------------------------------------------------------------------
;; CEK-native higher-order forms
;; --------------------------------------------------------------------------

(defsuite "CEK higher-order forms"
  (deftest "map through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(map (fn (x) (* x 2)) (list 1 2 3))")
                    (test-env))))
      (assert-equal (list 2 4 6) result)))

  (deftest "map-indexed through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(map-indexed (fn (i x) (+ i x)) (list 10 20 30))")
                    (test-env))))
      (assert-equal (list 10 21 32) result)))

  (deftest "filter through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(filter (fn (x) (> x 2)) (list 1 2 3 4 5))")
                    (test-env))))
      (assert-equal (list 3 4 5) result)))

  (deftest "reduce through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3))")
                    (test-env))))
      (assert-equal 6 result)))

  (deftest "some through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(some (fn (x) (> x 3)) (list 1 2 3 4 5))")
                    (test-env))))
      (assert-true result)))

  (deftest "some returns false when none match"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(some (fn (x) (> x 10)) (list 1 2 3))")
                    (test-env))))
      (assert-false result)))

  (deftest "every? through CEK"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(every? (fn (x) (> x 0)) (list 1 2 3))")
                    (test-env))))
      (assert-true result)))

  (deftest "every? returns false on first falsy"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(every? (fn (x) (> x 2)) (list 1 2 3))")
                    (test-env))))
      (assert-false result)))

  (deftest "for-each through CEK"
    (let ((log (list)))
      (env-bind! (test-env) "test-log" log)
      (eval-expr-cek
        (sx-parse-one "(for-each (fn (x) (append! test-log x)) (list 1 2 3))")
        (test-env))
      (assert-equal (list 1 2 3) log)))

  (deftest "map on empty list"
    (let ((result (eval-expr-cek
                    (sx-parse-one "(map (fn (x) x) (list))")
                    (test-env))))
      (assert-equal (list) result))))
