;; Letrec + perform/resume — tree-walk paths regression
;;
;; Before the fix: cek-run raised "IO suspension in non-IO context" when
;; a perform fired inside any tree-walked eval_expr path:
;;   - sf_letrec init exprs / non-last body exprs (the original bug)
;;   - sf_dynamic_wind, sf_scope, sf_provide bodies
;;   - expand_macro body, qq_expand unquote
;;
;; After the fix: cek-run invokes _cek_io_suspend_hook which (in this test
;; runner) resolves the IO and returns the result, allowing evaluation to
;; complete successfully. End-to-end: the expression returns a value
;; instead of throwing the swallowed-suspension error.
;;
;; The test runner's hook resolves IO requests via resolve_io (mock).
;; In production browser, the hook raises VmSuspended → JS resume callback.

(defsuite
  "letrec-resume-treewalk-init"
  (deftest
    "perform in letrec init expr — evaluates without error"
    (let
      ((result (eval-expr-cek (quote (letrec ((x (perform {:op "request-arg" :args (list "k")}))) (or x "default"))) (make-env))))
      (assert (or (string? result) (nil? result)))))
  (deftest
    "perform in letrec non-last body — evaluates without error"
    (let
      ((result (eval-expr-cek (quote (letrec ((x 42)) (perform {:op "request-arg" :args (list "k")}) (+ x 1))) (make-env))))
      (assert= result 43)))
  (deftest
    "letrec init perform result reachable — no nil sibling"
    (let
      ((result (eval-expr-cek (quote (letrec ((x 99) (y (fn () x))) (perform {:op "request-arg" :args (list "k")}) (y))) (make-env))))
      (assert= result 99))))


(defsuite
  "letrec-resume-treewalk-bodies"
  (deftest
    "perform inside scope body — evaluates without error"
    (let
      ((result (eval-expr-cek (quote (scope "test-scope" :value 1 (perform {:op "request-arg" :args (list "k")}) "scope-done")) (make-env))))
      (assert= result "scope-done")))
  (deftest
    "perform inside provide body — evaluates without error"
    (let
      ((result (eval-expr-cek (quote (provide "test-key" 7 (perform {:op "request-arg" :args (list "k")}) "provide-done")) (make-env))))
      (assert= result "provide-done"))))


(defsuite
  "letrec-resume-treewalk-recursive"
  (deftest
    "letrec sibling fn called after perform/resume — sibling not nil"
    (let
      ((result (eval-expr-cek (quote (letrec ((sib "from-sibling") (suspending (fn () (do (perform {:op "request-arg" :args (list "k")}) sib)))) (suspending))) (make-env))))
      (assert= result "from-sibling")))
  (deftest
    "letrec — suspending fn calls sibling fn after own perform"
    (let
      ((result (eval-expr-cek (quote (letrec ((wait-and-call (fn () (do (perform {:op "request-arg" :args (list "k")}) (other)))) (other (fn () "called-after-resume"))) (wait-and-call))) (make-env))))
      (assert= result "called-after-resume"))))
