(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))))

(define
  try-eval
  (fn
    (expr)
    (try-catch (fn () (vm-eval expr)) (fn (err) (str "Error: " err)))))

(defsuite
  "vm-primitive-availability"
  (deftest
    "length is available"
    (assert-equal 3 (vm-eval (quote (length (list 1 2 3))))))
  (deftest
    "len is available"
    (assert-equal 3 (vm-eval (quote (len (list 1 2 3))))))
  (deftest
    "first is available"
    (assert-equal 1 (vm-eval (quote (first (list 1 2 3))))))
  (deftest
    "rest is available"
    (assert-equal (list 2 3) (vm-eval (quote (rest (list 1 2 3))))))
  (deftest
    "nth is available"
    (assert-equal 2 (vm-eval (quote (nth (list 1 2 3) 1)))))
  (deftest
    "get is available"
    (assert-equal "v" (vm-eval (quote (get {:k "v"} :k)))))
  (deftest
    "type-of is available"
    (assert-equal "number" (vm-eval (quote (type-of 42)))))
  (deftest
    "str concatenation works"
    (assert-equal "ab" (vm-eval (quote (str "a" "b")))))
  (deftest
    "empty? is available"
    (assert-equal true (vm-eval (quote (empty? (list))))))
  (deftest
    "not is available"
    (assert-equal true (vm-eval (quote (not false)))))
  (deftest
    "append is available"
    (assert-equal
      (list 1 2 3)
      (vm-eval (quote (append (list 1 2) (list 3))))))
  (deftest
    "assoc is available"
    (assert-equal "b" (vm-eval (quote (get (assoc {:a "a"} :a "b") :a)))))
  (deftest
    "keys is available"
    (assert-equal 1 (vm-eval (quote (len (keys {:a 1}))))))
  (deftest
    "contains? is available"
    (assert-equal true (vm-eval (quote (contains? "hello" "ell")))))
  (deftest
    "starts-with? is available"
    (assert-equal true (vm-eval (quote (starts-with? "hello" "hel")))))
  (deftest
    "split is available"
    (assert-equal 3 (vm-eval (quote (len (split "a,b,c" ","))))))
  (deftest
    "join is available"
    (assert-equal "a,b" (vm-eval (quote (join "," (list "a" "b"))))))
  (deftest
    "trim is available"
    (assert-equal "x" (vm-eval (quote (trim "  x  ")))))
  (deftest
    "upper is available"
    (assert-equal "ABC" (vm-eval (quote (upper "abc"))))))

(defsuite
  "vm-cross-module-symbols"
  (deftest
    "module B sees module A definition"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define helper (fn (x) (+ x 100)))) g)
      (vm-eval-with (quote (define user (fn (x) (helper x)))) g)
      (assert-equal 142 (vm-eval-with (quote (user 42)) g))))
  (deftest
    "module C sees definitions from both A and B"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define a (fn (x) (* x 2)))) g)
      (vm-eval-with (quote (define b (fn (x) (+ (a x) 1)))) g)
      (vm-eval-with (quote (define c (fn (x) (b (a x))))) g)
      (assert-equal 21 (vm-eval-with (quote (c 5)) g))))
  (deftest
    "late-defined symbol visible to earlier closure"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define caller (fn () (callee 10)))) g)
      (vm-eval-with (quote (define callee (fn (x) (* x 3)))) g)
      (assert-equal 30 (vm-eval-with (quote (caller)) g)))))

(defsuite
  "vm-callback-dispatch"
  (deftest
    "simple closure callback"
    (let
      ((g (dict)))
      (vm-eval-with
        (quote (define make-fn (fn (n) (fn (x) (+ n x)))))
        g)
      (let
        ((result (vm-eval-with (quote (let ((f (make-fn 10))) (f 5))) g)))
        (assert-equal 15 result))))
  (deftest
    "closure capturing multiple vars"
    (let
      ((g (dict)))
      (vm-eval-with
        (quote (define make-adder (fn (a b) (fn (x) (+ a b x)))))
        g)
      (assert-equal 60 (vm-eval-with (quote ((make-adder 10 20) 30)) g))))
  (deftest
    "nested closure callback"
    (let
      ((g (dict)))
      (vm-eval-with
        (quote (define wrap (fn (f) (fn (x) (f (f x))))))
        g)
      (vm-eval-with (quote (define inc (fn (x) (+ x 1)))) g)
      (assert-equal 12 (vm-eval-with (quote ((wrap inc) 10)) g)))))

(defsuite
  "vm-error-surfacing"
  (deftest
    "undefined symbol in compiled fn raises error"
    (let
      ((g (dict)) (ok false))
      (vm-eval-with (quote (define good-fn (fn () 42))) g)
      (set! ok (= 42 (vm-eval-with (quote (good-fn)) g)))
      (assert-equal true ok)))
  (deftest
    "compiled fn calling another compiled fn works"
    (let
      ((g (dict)))
      (vm-eval-with (quote (define a (fn (x) (+ x 1)))) g)
      (vm-eval-with (quote (define b (fn (x) (a x)))) g)
      (assert-equal 6 (vm-eval-with (quote (b 5)) g))))
  (deftest
    "when block executes all body forms"
    (assert-equal
      30
      (vm-eval
        (quote
          (let ((x 0)) (when true (set! x 10) (set! x (+ x 20))) x)))))
  (deftest
    "when block: function call then side effect"
    (assert-equal
      50
      (vm-eval
        (quote
          (let
            ((x 0) (f (fn (n) (* n 10))))
            (when true (f 1) (set! x (f 5)))
            x))))))
