(define
  vm-eval
  (fn
    (expr)
    (let
      ((code (compile expr)))
      (vm-execute-module (code-from-value code) {}))))

(define
  vm-eval-with
  (fn
    (expr globals)
    (let
      ((code (compile expr)))
      (vm-execute-module (code-from-value code) globals))))

(defsuite
  "vm-constants"
  (deftest "number constant" (assert-equal 42 (vm-eval 42)))
  (deftest "string constant" (assert-equal "hello" (vm-eval "hello")))
  (deftest "boolean true" (assert-equal true (vm-eval true)))
  (deftest "boolean false" (assert-equal false (vm-eval false)))
  (deftest "nil constant" (assert-nil (vm-eval nil)))
  (deftest "negative number" (assert-equal -7 (vm-eval -7)))
  (deftest "float constant" (assert-equal 3.14 (vm-eval 3.14))))

(defsuite
  "vm-arithmetic"
  (deftest "addition" (assert-equal 5 (vm-eval (quote (+ 2 3)))))
  (deftest "subtraction" (assert-equal 7 (vm-eval (quote (- 10 3)))))
  (deftest "multiplication" (assert-equal 24 (vm-eval (quote (* 6 4)))))
  (deftest "division" (assert-equal 5 (vm-eval (quote (/ 10 2)))))
  (deftest
    "nested arithmetic"
    (assert-equal 14 (vm-eval (quote (+ (* 3 4) 2)))))
  (deftest
    "three-arg addition"
    (assert-equal 15 (vm-eval (quote (+ 5 4 6))))))

(defsuite
  "vm-comparison"
  (deftest "equal numbers" (assert-equal true (vm-eval (quote (= 1 1)))))
  (deftest
    "unequal numbers"
    (assert-equal false (vm-eval (quote (= 1 2)))))
  (deftest "less than" (assert-equal true (vm-eval (quote (< 1 2)))))
  (deftest "greater than" (assert-equal true (vm-eval (quote (> 5 3)))))
  (deftest "not" (assert-equal true (vm-eval (quote (not false)))))
  (deftest "not truthy" (assert-equal false (vm-eval (quote (not 42))))))

(defsuite
  "vm-control-flow"
  (deftest
    "if true branch"
    (assert-equal 1 (vm-eval (quote (if true 1 2)))))
  (deftest
    "if false branch"
    (assert-equal 2 (vm-eval (quote (if false 1 2)))))
  (deftest
    "if without else returns nil"
    (assert-nil (vm-eval (quote (if false 1)))))
  (deftest
    "when true evaluates body"
    (assert-equal 42 (vm-eval (quote (when true 42)))))
  (deftest
    "when false returns nil"
    (assert-nil (vm-eval (quote (when false 42)))))
  (deftest
    "and short-circuits on false"
    (assert-equal false (vm-eval (quote (and true false 42)))))
  (deftest
    "and returns last truthy"
    (assert-equal 3 (vm-eval (quote (and 1 2 3)))))
  (deftest
    "or short-circuits on true"
    (assert-equal 1 (vm-eval (quote (or 1 false 2)))))
  (deftest
    "or returns false when all falsy"
    (assert-equal false (vm-eval (quote (or false false false)))))
  (deftest
    "cond first match"
    (assert-equal
      "one"
      (vm-eval (quote (cond (= 1 1) "one" (= 2 2) "two")))))
  (deftest
    "cond else clause"
    (assert-equal
      "none"
      (vm-eval (quote (cond (= 1 2) "one" :else "none")))))
  (deftest
    "case match"
    (assert-equal
      "two"
      (vm-eval (quote (case 2 1 "one" 2 "two" :else "other")))))
  (deftest
    "case else"
    (assert-equal
      "other"
      (vm-eval (quote (case 99 1 "one" 2 "two" :else "other"))))))

(defsuite
  "vm-let"
  (deftest
    "single binding"
    (assert-equal 10 (vm-eval (quote (let ((x 10)) x)))))
  (deftest
    "multiple bindings"
    (assert-equal 30 (vm-eval (quote (let ((x 10) (y 20)) (+ x y))))))
  (deftest
    "bindings are sequential"
    (assert-equal 11 (vm-eval (quote (let ((x 10) (y (+ x 1))) y)))))
  (deftest
    "nested let"
    (assert-equal
      3
      (vm-eval (quote (let ((x 1)) (let ((y 2)) (+ x y)))))))
  (deftest
    "inner let shadows outer"
    (assert-equal 99 (vm-eval (quote (let ((x 1)) (let ((x 99)) x))))))
  (deftest
    "let body returns last expression"
    (assert-equal 3 (vm-eval (quote (let ((x 1)) 1 2 3))))))

(defsuite
  "vm-lambda"
  (deftest
    "lambda call"
    (assert-equal
      7
      (vm-eval (quote (let ((f (fn (x) (+ x 2)))) (f 5))))))
  (deftest
    "lambda with multiple params"
    (assert-equal
      11
      (vm-eval (quote (let ((add (fn (a b) (+ a b)))) (add 5 6))))))
  (deftest
    "higher-order: pass lambda to lambda"
    (assert-equal
      10
      (vm-eval
        (quote
          (let
            ((apply-fn (fn (f x) (f x))) (double (fn (n) (* n 2))))
            (apply-fn double 5))))))
  (deftest
    "lambda returns lambda"
    (assert-equal
      15
      (vm-eval
        (quote
          (let
            ((make-adder (fn (n) (fn (x) (+ n x)))))
            (let ((add10 (make-adder 10))) (add10 5)))))))
  (deftest
    "immediately invoked lambda"
    (assert-equal 42 (vm-eval (quote ((fn (x) (* x 2)) 21))))))

(defsuite
  "vm-closures"
  (deftest
    "closure captures local"
    (assert-equal
      10
      (vm-eval (quote (let ((x 10)) (let ((f (fn () x))) (f)))))))
  (deftest
    "closure captures through two levels"
    (assert-equal
      30
      (vm-eval
        (quote
          (let
            ((x 10))
            (let ((y 20)) (let ((f (fn () (+ x y)))) (f))))))))
  (deftest
    "two closures share upvalue"
    (assert-equal
      42
      (vm-eval
        (quote
          (let
            ((x 0))
            (let
              ((set-x (fn (v) (set! x v))) (get-x (fn () x)))
              (set-x 42)
              (get-x)))))))
  (deftest
    "closure mutation visible to sibling"
    (assert-equal
      3
      (vm-eval
        (quote
          (let
            ((counter 0))
            (let
              ((inc! (fn () (set! counter (+ counter 1)))))
              (inc!)
              (inc!)
              (inc!)
              counter)))))))

(defsuite
  "vm-tco"
  (deftest
    "tail-recursive loop doesn't overflow"
    (assert-equal
      0
      (vm-eval
        (quote
          (let
            ((loop (fn (n) (if (<= n 0) 0 (loop (- n 1))))))
            (loop 10000))))))
  (deftest
    "tail-recursive accumulator"
    (assert-equal
      5050
      (vm-eval
        (quote
          (let
            ((sum (fn (n acc) (if (<= n 0) acc (sum (- n 1) (+ acc n))))))
            (sum 100 0)))))))

(defsuite
  "vm-collections"
  (deftest
    "list construction"
    (assert-equal (list 1 2 3) (vm-eval (quote (list 1 2 3)))))
  (deftest "empty list" (assert-equal (list) (vm-eval (quote (list)))))
  (deftest
    "dict construction"
    (let
      ((d (vm-eval (quote {:b 2 :a 1}))))
      (assert-equal 1 (get d "a"))
      (assert-equal 2 (get d "b"))))
  (deftest
    "list operations"
    (assert-equal 1 (vm-eval (quote (first (list 1 2 3)))))
    (assert-equal 3 (vm-eval (quote (len (list 1 2 3))))))
  (deftest
    "nested list"
    (assert-equal
      (list 1 (list 2 3))
      (vm-eval (quote (list 1 (list 2 3)))))))

(defsuite
  "vm-strings"
  (deftest
    "str concat"
    (assert-equal "hello world" (vm-eval (quote (str "hello" " " "world")))))
  (deftest
    "string-length"
    (assert-equal 5 (vm-eval (quote (string-length "hello")))))
  (deftest
    "str coerces numbers"
    (assert-equal "42" (vm-eval (quote (str 42))))))

(defsuite
  "vm-define"
  (deftest
    "top-level define"
    (assert-equal 42 (vm-eval (quote (do (define x 42) x)))))
  (deftest
    "define function then call"
    (assert-equal
      10
      (vm-eval (quote (do (define double (fn (n) (* n 2))) (double 5))))))
  (deftest
    "local define inside fn"
    (assert-equal
      30
      (vm-eval
        (quote (let ((f (fn (x) (define y (* x 2)) (+ x y)))) (f 10))))))
  (deftest
    "define with forward reference"
    (assert-equal
      120
      (vm-eval
        (quote
          (do
            (define fact (fn (n) (if (<= n 1) 1 (* n (fact (- n 1))))))
            (fact 5)))))))

(defsuite
  "vm-letrec"
  (deftest
    "letrec self-recursion"
    (assert-equal
      55
      (vm-eval
        (quote
          (letrec
            ((sum-to (fn (n) (if (<= n 0) 0 (+ n (sum-to (- n 1)))))))
            (sum-to 10))))))
  (deftest
    "letrec mutual recursion"
    (assert-equal
      true
      (vm-eval
        (quote
          (letrec
            ((my-even? (fn (n) (if (= n 0) true (my-odd? (- n 1)))))
              (my-odd? (fn (n) (if (= n 0) false (my-even? (- n 1))))))
            (my-even? 10)))))))

(defsuite
  "vm-quasiquote"
  (deftest
    "simple quasiquote"
    (assert-equal
      (list 1 2 3)
      (vm-eval (quote (let ((x 2)) (quasiquote (1 (unquote x) 3)))))))
  (deftest
    "quasiquote with splice"
    (assert-equal
      (list 1 2 3 4)
      (vm-eval
        (quote
          (let ((xs (list 2 3))) (quasiquote (1 (splice-unquote xs) 4))))))))

(defsuite
  "vm-threading"
  (deftest "thread-first" (assert-equal 7 (vm-eval (quote (-> 5 (+ 2))))))
  (deftest
    "thread-first chain"
    (assert-equal 12 (vm-eval (quote (-> 10 (+ 5) (- 3)))))))

(defsuite
  "vm-integration"
  (deftest
    "fibonacci"
    (assert-equal
      55
      (vm-eval
        (quote
          (do
            (define
              fib
              (fn (n) (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2))))))
            (fib 10))))))
  (deftest
    "map via recursive define"
    (assert-equal
      (list 2 4 6)
      (vm-eval
        (quote
          (do
            (define
              my-map
              (fn
                (f lst)
                (if
                  (empty? lst)
                  (list)
                  (cons (f (first lst)) (my-map f (rest lst))))))
            (my-map (fn (x) (* x 2)) (list 1 2 3)))))))
  (deftest
    "filter via recursive define"
    (assert-equal
      (list 2 4)
      (vm-eval
        (quote
          (do
            (define
              my-filter
              (fn
                (pred lst)
                (if
                  (empty? lst)
                  (list)
                  (if
                    (pred (first lst))
                    (cons (first lst) (my-filter pred (rest lst)))
                    (my-filter pred (rest lst))))))
            (my-filter (fn (x) (even? x)) (list 1 2 3 4 5)))))))
  (deftest
    "reduce via recursive define"
    (assert-equal
      15
      (vm-eval
        (quote
          (do
            (define
              my-reduce
              (fn
                (f acc lst)
                (if
                  (empty? lst)
                  acc
                  (my-reduce f (f acc (first lst)) (rest lst)))))
            (my-reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3 4 5)))))))
  (deftest
    "nested function calls"
    (assert-equal
      42
      (vm-eval
        (quote
          (do
            (define compose (fn (f g) (fn (x) (f (g x)))))
            (define inc (fn (x) (+ x 1)))
            (define double (fn (x) (* x 2)))
            (let
              ((inc-then-double (compose double inc)))
              (inc-then-double 20))))))))

(defsuite
  "vm-recursive-mutation"
  (deftest
    "recursive append! to shared list"
    (assert-equal
      3
      (vm-eval
        (quote
          (do
            (define
              walk
              (fn
                (items result)
                (when
                  (not (empty? items))
                  (append! result (first items))
                  (walk (rest items) result))))
            (let
              ((result (list)))
              (walk (list "a" "b" "c") result)
              (len result)))))))
  (deftest
    "recursive tree walk with append!"
    (assert-equal
      7
      (vm-eval
        (quote
          (do
            (define
              walk-children
              (fn
                (items result walk-fn)
                (when
                  (not (empty? items))
                  (walk-fn (first items) result)
                  (walk-children (rest items) result walk-fn))))
            (define
              walk
              (fn
                (expr result)
                (cond
                  (not (list? expr))
                  (append! result "leaf")
                  (empty? expr)
                  nil
                  :else (do
                    (append! result "open")
                    (walk-children (rest expr) result walk)
                    (append! result "close")))))
            (let
              ((tree (first (sx-parse "(div \"a\" (span \"b\") \"c\")")))
                (result (list)))
              (walk tree result)
              (len result)))))))
  (deftest
    "recursive walk matching stepper split-tag pattern"
    (assert-equal
      16
      (vm-eval
        (quote
          (do
            (define
              walk-each
              (fn
                (items result walk-fn)
                (when
                  (not (empty? items))
                  (walk-fn (first items) result)
                  (walk-each (rest items) result walk-fn))))
            (define
              collect-children
              (fn
                (items cch)
                (when
                  (not (empty? items))
                  (let
                    ((a (first items)))
                    (if
                      (and
                        (list? a)
                        (not (empty? a))
                        (= (type-of (first a)) "symbol")
                        (starts-with? (symbol-name (first a)) "~"))
                      nil
                      (append! cch a))
                    (collect-children (rest items) cch)))))
            (define
              split-tag
              (fn
                (expr result)
                (cond
                  (not (list? expr))
                  (append! result "leaf")
                  (empty? expr)
                  nil
                  (not (= (type-of (first expr)) "symbol"))
                  (append! result "leaf")
                  (is-html-tag? (symbol-name (first expr)))
                  (let
                    ((cch (list)))
                    (collect-children (rest expr) cch)
                    (append! result "open")
                    (walk-each cch result split-tag)
                    (append! result "close"))
                  :else (append! result "expr"))))
            (let
              ((parsed (sx-parse "(div (~cssx/tw :tokens \"text-center\")\n  (h1 (~cssx/tw :tokens \"text-3xl font-bold mb-2\")\n    (span (~cssx/tw :tokens \"text-rose-500\") \"the \")\n    (span (~cssx/tw :tokens \"text-amber-500\") \"joy \")\n    (span (~cssx/tw :tokens \"text-emerald-500\") \"of \")\n    (span (~cssx/tw :tokens \"text-violet-600 text-4xl\") \"sx\")))"))
                (result (list)))
              (split-tag (first parsed) result)
              (len result))))))))

(defsuite
  "vm-nested-calls"
  (deftest
    "function call then continue"
    (assert-equal
      30
      (vm-eval
        (quote
          (let
            ((double (fn (x) (* x 2))))
            (let ((a (double 5))) (+ a (double 10))))))))
  (deftest
    "three sequential function calls"
    (assert-equal
      60
      (vm-eval
        (quote (let ((f (fn (x) (* x 10)))) (+ (f 1) (f 2) (f 3)))))))
  (deftest
    "nested call: fn calls fn"
    (assert-equal
      20
      (vm-eval
        (quote
          (let
            ((add1 (fn (x) (+ x 1))) (double (fn (x) (* x 2))))
            (double (add1 9)))))))
  (deftest
    "three-deep nested call"
    (assert-equal
      22
      (vm-eval
        (quote
          (let
            ((add1 (fn (x) (+ x 1)))
              (double (fn (x) (* x 2)))
              (inc10 (fn (x) (+ x 10))))
            (inc10 (double (add1 5))))))))
  (deftest
    "side effect after function call"
    (assert-equal
      42
      (vm-eval
        (quote (let ((x 0) (f (fn (n) (* n 2)))) (set! x (f 21)) x)))))
  (deftest
    "multiple side effects after nested calls"
    (assert-equal
      35
      (vm-eval
        (quote
          (let
            ((a 0) (b 0) (f (fn (x) (+ x 10))))
            (set! a (f 5))
            (set! b (f 10))
            (+ a b))))))
  (deftest
    "when body continues after function call"
    (assert-equal
      100
      (vm-eval
        (quote
          (let
            ((x 0) (f (fn (n) (* n 10))))
            (when true (f 1) (set! x (f 10)))
            x)))))
  (deftest
    "function returning closure then call"
    (assert-equal
      15
      (vm-eval
        (quote
          (let
            ((make-adder (fn (n) (fn (x) (+ n x)))))
            (let
              ((add5 (make-adder 5)) (add10 (make-adder 10)))
              (add5 (add10 0))))))))
  (deftest
    "for-each with side effects"
    (assert-equal
      6
      (vm-eval
        (quote
          (let
            ((sum 0))
            (for-each (fn (x) (set! sum (+ sum x))) (list 1 2 3))
            sum)))))
  (deftest
    "map then use result"
    (assert-equal
      20
      (vm-eval
        (quote
          (let
            ((doubled (map (fn (x) (* x 2)) (list 1 2 3 4))))
            (reduce + 0 doubled))))))
  (deftest
    "function call in let binding then body"
    (assert-equal
      25
      (vm-eval
        (quote
          (let
            ((square (fn (x) (* x x))))
            (let ((a (square 3)) (b (square 4))) (+ a b)))))))
  (deftest
    "callback from higher-order function"
    (assert-equal
      3
      (vm-eval
        (quote
          (let
            ((apply-twice (fn (f x) (f (f x)))))
            (apply-twice (fn (n) (+ n 1)) 1))))))
  (deftest
    "do block with interleaved calls"
    (assert-equal
      30
      (vm-eval
        (quote
          (let
            ((x 0) (f (fn (n) (* n 5))))
            (do
              (set! x (+ x (f 1)))
              (set! x (+ x (f 2)))
              (set! x (+ x (f 3))))
            x))))))

(defsuite
  "vm-cek-bridge"
  (deftest
    "compiled fn calls another compiled fn"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define double (fn (x) (* x 2)))) g)
      (vm-eval-with
        (quote (define use-double (fn (n) (+ 1 (double n)))))
        g)
      (assert-equal 11 (vm-eval-with (quote (use-double 5)) g))))
  (deftest
    "compiled fn calls compiled fn then continues"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define helper (fn (x) (+ x 10)))) g)
      (vm-eval-with
        (quote (define main (fn () (let ((a (helper 5))) (+ a 100)))))
        g)
      (assert-equal 115 (vm-eval-with (quote (main)) g))))
  (deftest
    "compiled fn with side effect after nested call"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define f (fn (x) (* x 3)))) g)
      (vm-eval-with
        (quote
          (define run (fn () (let ((x 0)) (set! x (f 7)) (+ x 1)))))
        g)
      (assert-equal 22 (vm-eval-with (quote (run)) g))))
  (deftest
    "three-deep compiled call chain"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define a (fn (x) (+ x 1)))) g)
      (vm-eval-with (quote (define b (fn (x) (* (a x) 2)))) g)
      (vm-eval-with (quote (define c (fn (x) (+ (b x) 100)))) g)
      (assert-equal 112 (vm-eval-with (quote (c 5)) g))))
  (deftest
    "compiled fn with when block multiple body forms"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define f (fn (x) (* x 10)))) g)
      (vm-eval-with
        (quote
          (define
            run
            (fn
              ()
              (let
                ((result 0))
                (when true (f 1) (set! result (f 5)))
                result))))
        g)
      (assert-equal 50 (vm-eval-with (quote (run)) g))))
  (deftest
    "compiled fn for-each calling compiled fn"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define process (fn (x) (* x 2)))) g)
      (vm-eval-with
        (quote
          (define
            run
            (fn
              ()
              (let
                ((sum 0))
                (for-each
                  (fn (x) (set! sum (+ sum (process x))))
                  (list 1 2 3))
                sum))))
        g)
      (assert-equal 12 (vm-eval-with (quote (run)) g))))
  (deftest
    "compiled fn returns closure then caller continues"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define wrap (fn (x) (fn () x)))) g)
      (vm-eval-with
        (quote (define run (fn () (let ((f (wrap 42))) (+ (f) 8)))))
        g)
      (assert-equal 50 (vm-eval-with (quote (run)) g))))
  (deftest
    "multiple sequential calls from one compiled fn"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define inc (fn (x) (+ x 1)))) g)
      (vm-eval-with
        (quote (define run (fn () (+ (inc 0) (inc 10) (inc 20)))))
        g)
      (assert-equal 33 (vm-eval-with (quote (run)) g)))))

(defsuite
  "vm-ho-forms"
  (deftest
    "map in vm-eval"
    (assert-equal
      (list 2 4 6)
      (vm-eval (quote (map (fn (x) (* x 2)) (list 1 2 3))))))
  (deftest
    "filter in vm-eval"
    (assert-equal
      (list 2 3 4)
      (vm-eval (quote (filter (fn (x) (> x 1)) (list 1 2 3 4))))))
  (deftest
    "reduce in vm-eval"
    (assert-equal
      10
      (vm-eval (quote (reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3 4))))))
  (deftest
    "for-each with set! in vm-eval"
    (assert-equal
      6
      (vm-eval
        (quote
          (let
            ((sum 0))
            (for-each (fn (x) (set! sum (+ sum x))) (list 1 2 3))
            sum)))))
  (deftest
    "some in vm-eval"
    (assert-true
      (vm-eval (quote (some (fn (x) (> x 3)) (list 1 2 3 4))))))
  (deftest
    "every? in vm-eval"
    (assert-false
      (vm-eval (quote (every? (fn (x) (> x 0)) (list 1 2 -1))))))
  (deftest
    "nested map in vm-eval"
    (assert-equal
      (list 1 4 9)
      (vm-eval (quote (map (fn (x) (* x x)) (list 1 2 3))))))
  (deftest
    "map with closure over local"
    (assert-equal
      (list 11 12 13)
      (vm-eval
        (quote
          (let ((offset 10)) (map (fn (x) (+ x offset)) (list 1 2 3))))))))
