(define
  test-set-attr-sep
  (fn (el name val) (effect (fn () (dom-set-attr el name val)))))

(define
  test-set-attr-mod
  (fn (el name val) (effect (fn () (dom-set-attr el name val)))))

(define test-reactive-sig (signal "before"))

(defsuite
  "wasm-basic"
  (deftest "arithmetic" (assert-equal 3 (+ 1 2)))
  (deftest
    "div preserves keywords"
    (assert-equal
      "(div :class \"test\" \"hello\")"
      (inspect (div :class "test" "hello"))))
  (deftest
    "render div+class"
    (assert-equal
      "<div class=\"card\">content</div>"
      (render-to-html (div :class "card" "content")))))

(defsuite
  "wasm-dom-rendering"
  (deftest
    "dom class attr"
    (assert-equal
      "test"
      (dom-get-attr
        (render-to-dom (div :class "test" "hello") (global-env) nil)
        "class"))))

(defsuite
  "wasm-scoped"
  (deftest
    "static class in island scope"
    (assert-equal
      "scoped"
      (dom-get-attr
        (let
          ((d (list)))
          (with-island-scope
            (fn (x) (append! d x))
            (fn
              ()
              (render-to-dom (div :class "scoped" "text") (global-env) nil))))
        "class")))
  (deftest
    "signal attr initial value"
    (assert-equal
      "active"
      (dom-get-attr
        (let
          ((s (signal "active")) (d (list)))
          (with-island-scope
            (fn (x) (append! d x))
            (fn
              ()
              (render-to-dom
                (div :class (deref s) "content")
                (global-env)
                nil))))
        "class")))
  (deftest
    "signal text in scope"
    (assert-true
      (contains?
        (host-get
          (let
            ((s (signal 42)) (d (list)))
            (with-island-scope
              (fn (x) (append! d x))
              (fn () (render-to-dom (div (deref s)) (global-env) nil))))
          "outerHTML")
        "42"))))

(defsuite
  "wasm-define-effect"
  (deftest
    "define+effect+host-obj (same eval)"
    (assert-equal
      "from-define"
      (do
        (define
          test-set-attr-inline
          (fn (el name val) (effect (fn () (dom-set-attr el name val)))))
        (let
          ((el (dom-create-element "div" nil)))
          (test-set-attr-inline el "class" "from-define")
          (dom-get-attr el "class")))))
  (deftest
    "define+effect body executes"
    (assert-equal
      "1:ran"
      (do
        (define
          test-fx-log-inline
          (fn
            (el log)
            (effect
              (fn () (append! log "ran") (dom-set-attr el "class" "fx")))))
        (let
          ((el (dom-create-element "div" nil)) (log (list)))
          (test-fx-log-inline el log)
          (str (len log) ":" (first log))))))
  (deftest
    "let+effect+host-obj"
    (assert-equal
      "from-let"
      (let
        ((test-set-attr-let (fn (el name val) (effect (fn () (dom-set-attr el name val))))))
        (let
          ((el (dom-create-element "div" nil)))
          (test-set-attr-let el "class" "from-let")
          (dom-get-attr el "class"))))))

(defsuite
  "wasm-module-loaded"
  (deftest
    "define+effect+host-obj (separate eval)"
    (assert-equal
      "from-sep-define"
      (let
        ((el (dom-create-element "div" nil)))
        (test-set-attr-sep el "class" "from-sep-define")
        (dom-get-attr el "class"))))
  (deftest
    "define+effect+host-obj (module-loaded)"
    (assert-equal
      "from-mod"
      (let
        ((el (dom-create-element "div" nil)))
        (test-set-attr-mod el "class" "from-mod")
        (dom-get-attr el "class")))))

(defsuite
  "wasm-reactive"
  (deftest
    "reactive-spread from module"
    (assert-equal
      "sx-text-center"
      (let
        ((el (dom-create-element "div" nil)) (d (list)))
        (with-island-scope
          (fn (x) (append! d x))
          (fn () (reactive-spread el (fn () (~tw :tokens "text-center")))))
        (dom-get-attr el "class"))))
  (deftest
    "render-to-dom CSSX in island scope"
    (assert-true
      (contains?
        (host-get
          (let
            ((d (list)))
            (with-island-scope
              (fn (x) (append! d x))
              (fn
                ()
                (render-to-dom
                  (quote (div (~tw :tokens "text-center font-bold") "hello"))
                  (global-env)
                  nil))))
          "outerHTML")
        "sx-text-center"))))

(defsuite
  "wasm-signals-core"
  (deftest "signal create and deref" (assert-equal 42 (deref (signal 42))))
  (deftest
    "reset! changes value"
    (let
      ((s (signal "old")))
      (reset! s "new")
      (assert-equal "new" (deref s))))
  (deftest
    "swap! applies function"
    (let ((s (signal 10))) (swap! s + 5) (assert-equal 15 (deref s))))
  (deftest
    "computed derives from signal"
    (let
      ((s (signal 3)))
      (let
        ((c (computed (fn () (* (deref s) 2)))))
        (assert-equal 6 (deref c)))))
  (deftest
    "effect runs immediately"
    (let
      ((log (list)))
      (effect (fn () (append! log "ran")))
      (assert-equal 1 (len log))))
  (deftest
    "effect re-runs on signal change"
    (let
      ((log (list)) (s (signal "a")))
      (effect (fn () (append! log (deref s))))
      (reset! s "b")
      (assert-equal 2 (len log))
      (assert-equal "b" (nth log 1))))
  (deftest
    "effect cleanup called on re-run"
    (let
      ((log (list)) (s (signal 1)))
      (effect
        (fn
          ()
          (let
            ((v (deref s)))
            (append! log (str "run:" v))
            (fn () (append! log (str "cleanup:" v))))))
      (reset! s 2)
      (assert-true (contains? log "cleanup:1"))))
  (deftest
    "batch delays notification"
    (let
      ((count 0) (s (signal 0)))
      (effect (fn () (deref s) (set! count (+ count 1))))
      (batch (fn () (reset! s 1) (reset! s 2) (reset! s 3)))
      (assert-equal 2 count))))

(defsuite
  "wasm-conditional-rendering"
  (deftest
    "if renders then branch"
    (assert-true
      (contains?
        (host-get
          (let
            ((d (list)))
            (with-island-scope
              (fn (x) (append! d x))
              (fn
                ()
                (render-to-dom
                  (quote (if true (div "yes") (div "no")))
                  (global-env)
                  nil))))
          "outerHTML")
        "yes")))
  (deftest
    "when renders body on true"
    (assert-true
      (contains?
        (host-get
          (let
            ((d (list)))
            (with-island-scope
              (fn (x) (append! d x))
              (fn
                ()
                (render-to-dom
                  (quote (when true (div "visible")))
                  (global-env)
                  nil))))
          "outerHTML")
        "visible")))
  (deftest
    "cond selects correct branch"
    (assert-true
      (contains?
        (host-get
          (render-to-dom
            (quote (cond false (div "a") true (div "b") :else (div "c")))
            (global-env)
            nil)
          "outerHTML")
        "b")))
  (deftest
    "render-to-dom with let bindings"
    (assert-true
      (contains?
        (host-get
          (render-to-dom
            (quote (let ((x "hello")) (div x)))
            (global-env)
            nil)
          "outerHTML")
        "hello")))
  (deftest
    "render-to-dom with begin"
    (assert-true
      (contains?
        (host-get
          (render-to-dom (quote (do (div "only-this"))) (global-env) nil)
          "outerHTML")
        "only-this"))))

(defsuite
  "wasm-signal-propagation"
  (deftest
    "reactive attr update"
    (do
      (set! test-reactive-sig (signal "before"))
      (assert-equal
        "after"
        (let
          ((d (list)))
          (let
            ((el (with-island-scope (fn (x) (append! d x)) (fn () (render-to-dom (quote (div :class (deref test-reactive-sig) "content")) (global-env) nil)))))
            (reset! test-reactive-sig "after")
            (dom-get-attr el "class")))))))

(defsuite
  "wasm-scope-context"
  (deftest
    "scope-push + context reads value"
    (scope-push! "test-scope" "hello")
    (let
      ((v (context "test-scope" nil)))
      (scope-pop! "test-scope")
      (assert-equal "hello" v)))
  (deftest
    "context returns default when not pushed"
    (assert-equal "default" (context "nonexistent-scope" "default")))
  (deftest
    "nested scope-push shadows outer"
    (scope-push! "test-nest" "outer")
    (scope-push! "test-nest" "inner")
    (let
      ((v (context "test-nest" nil)))
      (scope-pop! "test-nest")
      (scope-pop! "test-nest")
      (assert-equal "inner" v)))
  (deftest
    "scope-pop reveals outer"
    (scope-push! "test-reveal" "outer")
    (scope-push! "test-reveal" "inner")
    (scope-pop! "test-reveal")
    (let
      ((v (context "test-reveal" nil)))
      (scope-pop! "test-reveal")
      (assert-equal "outer" v))))
