(import (sx dom))
(import (sx render))

(define-library
  (web adapter-dom)
  (export
    SVG_NS
    MATH_NS
    island-scope?
    hydrating?
    contains-deref?
    dom-on
    render-to-dom
    render-dom-list
    render-dom-element
    render-dom-component
    render-dom-fragment
    render-dom-raw
    render-dom-unknown-component
    RENDER_DOM_FORMS
    render-dom-form?
    dispatch-render-form
    render-lambda-dom
    render-dom-island
    render-dom-lake
    render-dom-marsh
    reactive-text
    reactive-attr
    reactive-spread
    reactive-fragment
    render-list-item
    extract-key
    reactive-list
    bind-input
    *use-cek-reactive*
    enable-cek-reactive!
    cek-reactive-text
    cek-reactive-attr
    render-dom-portal
    render-dom-error-boundary)
  (begin
    (define SVG_NS "http://www.w3.org/2000/svg")
    (define MATH_NS "http://www.w3.org/1998/Math/MathML")
    (define
      island-scope?
      (fn () (not (nil? (scope-peek "sx-island-scope")))))
    (define hydrating? (fn () (not (nil? (scope-peek "sx-hydrating")))))
    (define
      hydrate-next-node
      (fn
        ()
        (let
          ((cursor (scope-peek "sx-hydrating")))
          (when
            cursor
            (let
              ((parent (dict-get cursor "parent"))
                (idx (dict-get cursor "index")))
              (let
                ((child (host-call (host-get parent "childNodes") "item" idx)))
                (dict-set! cursor "index" (+ idx 1))
                child))))))
    (define
      hydrate-enter-element
      (fn (el) (scope-push! "sx-hydrating" (dict "parent" el "index" 0))))
    (define hydrate-exit-element (fn () (scope-pop! "sx-hydrating")))
    (begin
      (define *memo-cache* (dict))
      (define *cyst-counter* 0)
      (define
        next-cyst-id
        (fn
          ()
          (set! *cyst-counter* (+ *cyst-counter* 1))
          (str "sx-cyst-" *cyst-counter*))))
    (define
      contains-deref?
      (fn
        (expr)
        (if
          (not (list? expr))
          false
          (if
            (empty? expr)
            false
            (if
              (and
                (= (type-of (first expr)) "symbol")
                (= (symbol-name (first expr)) "deref"))
              true
              (some contains-deref? expr))))))
    (define
      dom-on
      :effects (io)
      (fn
        (el name handler)
        (dom-listen
          el
          name
          (if
            (lambda? handler)
            (if
              (= 0 (len (lambda-params handler)))
              (fn
                (_e)
                (trampoline (call-lambda handler (list)))
                (run-post-render-hooks))
              (fn
                (e)
                (trampoline (call-lambda handler (list e)))
                (run-post-render-hooks)))
            handler))))
    (define
      render-to-dom
      :effects (render)
      (fn
        (expr (env :as dict) (ns :as string))
        (set-render-active! true)
        (case
          (type-of expr)
          "nil"
          (create-fragment)
          "boolean"
          (create-fragment)
          "raw-html"
          (dom-parse-html (raw-html-content expr))
          "string"
          (if
            (hydrating?)
            (or (hydrate-next-node) (create-text-node expr))
            (create-text-node expr))
          "number"
          (if
            (hydrating?)
            (or (hydrate-next-node) (create-text-node (str expr)))
            (create-text-node (str expr)))
          "symbol"
          (render-to-dom (trampoline (eval-expr expr env)) env ns)
          "keyword"
          (if
            (hydrating?)
            (or (hydrate-next-node) (create-text-node (keyword-name expr)))
            (create-text-node (keyword-name expr)))
          "dom-node"
          expr
          "spread"
          (do
            (when
              (not (island-scope?))
              (scope-emit! "element-attrs" (spread-attrs expr)))
            expr)
          "dict"
          (if (has-key? expr "__host_handle") expr (create-fragment))
          "list"
          (if (empty? expr) (create-fragment) (render-dom-list expr env ns))
          :else (if
            (signal? expr)
            (if
              (island-scope?)
              (reactive-text expr)
              (create-text-node (str (deref expr))))
            (create-text-node (str expr))))))
    (define
      render-dom-list
      :effects (render)
      (fn
        (expr (env :as dict) (ns :as string))
        (let
          ((head (first expr)))
          (cond
            (= (type-of head) "symbol")
            (let
              ((name (symbol-name head)) (args (rest expr)))
              (cond
                (= name "raw!")
                (render-dom-raw args env)
                (= name "<>")
                (render-dom-fragment args env ns)
                (= name "lake")
                (render-dom-lake args env ns)
                (= name "marsh")
                (render-dom-marsh args env ns)
                (starts-with? name "html:")
                (render-dom-element (slice name 5) args env ns)
                (render-dom-form? name)
                (if
                  (and
                    (contains? HTML_TAGS name)
                    (or
                      (and
                        (> (len args) 0)
                        (= (type-of (first args)) "keyword"))
                      ns))
                  (render-dom-element name args env ns)
                  (dispatch-render-form name expr env ns))
                (and (env-has? env name) (macro? (env-get env name)))
                (render-to-dom
                  (expand-macro (env-get env name) args env)
                  env
                  ns)
                (contains? HTML_TAGS name)
                (render-dom-element name args env ns)
                (and
                  (starts-with? name "~")
                  (env-has? env name)
                  (island? (env-get env name)))
                (if
                  (scope-peek "sx-render-markers")
                  (let
                    ((island (env-get env name))
                      (marker (dom-create-element "span" nil))
                      (kw-state (dict)))
                    (reduce
                      (fn
                        (state arg)
                        (let
                          ((skip (get state "skip")))
                          (if
                            skip
                            (assoc
                              state
                              "skip"
                              false
                              "i"
                              (inc (get state "i")))
                            (if
                              (and
                                (= (type-of arg) "keyword")
                                (< (inc (get state "i")) (len args)))
                              (let
                                ((kname (keyword-name arg))
                                  (kval
                                    (trampoline
                                      (eval-expr
                                        (nth args (inc (get state "i")))
                                        env))))
                                (dict-set! kw-state kname kval)
                                (assoc
                                  state
                                  "skip"
                                  true
                                  "i"
                                  (inc (get state "i"))))
                              (assoc state "i" (inc (get state "i")))))))
                      (dict "i" 0 "skip" false)
                      args)
                    (dom-set-attr
                      marker
                      "data-sx-island"
                      (component-name island))
                    (when
                      (not (empty-dict? kw-state))
                      (dom-set-attr
                        marker
                        "data-sx-state"
                        (sx-serialize kw-state)))
                    marker)
                  (render-dom-island (env-get env name) args env ns))
                (starts-with? name "~")
                (let
                  ((comp (env-get env name)))
                  (if
                    (component? comp)
                    (render-dom-component comp args env ns)
                    (render-dom-unknown-component name)))
                (and
                  (> (index-of name "-") 0)
                  (> (len args) 0)
                  (= (type-of (first args)) "keyword"))
                (render-dom-element name args env ns)
                ns
                (render-dom-element name args env ns)
                (and (= name "deref") (island-scope?))
                (let
                  ((sig-or-val (trampoline (eval-expr (first args) env))))
                  (if
                    (signal? sig-or-val)
                    (reactive-text sig-or-val)
                    (create-text-node (str (deref sig-or-val)))))
                (and (island-scope?) (contains-deref? expr))
                (reactive-text
                  (computed (fn () (trampoline (eval-expr expr env)))))
                :else (render-to-dom (trampoline (eval-expr expr env)) env ns)))
            (or (lambda? head) (= (type-of head) "list"))
            (render-to-dom (trampoline (eval-expr expr env)) env ns)
            :else (let
              ((frag (create-fragment)))
              (for-each
                (fn
                  (x)
                  (let
                    ((result (render-to-dom x env ns)))
                    (when (not (spread? result)) (dom-append frag result))))
                expr)
              frag)))))
    (define
      render-dom-element
      :effects (render)
      (fn
        ((tag :as string) (args :as list) (env :as dict) (ns :as string))
        (let
          ((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS :else ns))
            (el
              (if
                (hydrating?)
                (let
                  ((existing (hydrate-next-node)))
                  (if
                    (and
                      existing
                      (= (lower (or (host-get existing "tagName") "")) tag))
                    (do (hydrate-enter-element existing) existing)
                    (error (str "hydrate-mismatch:" tag))))
                (dom-create-element tag new-ns))))
          (scope-push! "element-attrs" nil)
          (reduce
            (fn
              (state arg)
              (let
                ((skip (get state "skip")))
                (if
                  skip
                  (assoc state "skip" false "i" (inc (get state "i")))
                  (if
                    (and
                      (= (type-of arg) "keyword")
                      (< (inc (get state "i")) (len args)))
                    (let
                      ((attr-name (keyword-name arg))
                        (attr-expr (nth args (inc (get state "i")))))
                      (cond
                        (starts-with? attr-name "on-")
                        (let
                          ((attr-val (trampoline (eval-expr attr-expr env))))
                          (when
                            (callable? attr-val)
                            (dom-on el (slice attr-name 3) attr-val)))
                        (= attr-name "bind")
                        (let
                          ((attr-val (trampoline (eval-expr attr-expr env))))
                          (when (signal? attr-val) (bind-input el attr-val)))
                        (= attr-name "ref")
                        (let
                          ((attr-val (trampoline (eval-expr attr-expr env))))
                          (if (callable? attr-val) (attr-val el) (dict-set! attr-val "current" el)))
                        (= attr-name "key")
                        (let
                          ((attr-val (trampoline (eval-expr attr-expr env))))
                          (dom-set-attr el "key" (str attr-val)))
                        (island-scope?)
                        (reactive-attr
                          el
                          attr-name
                          (fn () (trampoline (eval-expr attr-expr env))))
                        :else (let
                          ((attr-val (trampoline (eval-expr attr-expr env))))
                          (cond
                            (or (nil? attr-val) (= attr-val false))
                            nil
                            (contains? BOOLEAN_ATTRS attr-name)
                            (when attr-val (dom-set-attr el attr-name ""))
                            (= attr-val true)
                            (dom-set-attr el attr-name "")
                            :else (dom-set-attr el attr-name (str attr-val)))))
                      (assoc state "skip" true "i" (inc (get state "i"))))
                    (do
                      (when
                        (not (contains? VOID_ELEMENTS tag))
                        (let
                          ((child (render-to-dom arg env new-ns)))
                          (cond
                            (and (spread? child) (island-scope?))
                            (reactive-spread
                              el
                              (fn () (render-to-dom arg env new-ns)))
                            (spread? child)
                            nil
                            :else (when
                              (or
                                (not (hydrating?))
                                (nil? (dom-parent child)))
                              (dom-append el child)))))
                      (assoc state "i" (inc (get state "i"))))))))
            (dict "i" 0 "skip" false)
            args)
          (for-each
            (fn
              (spread-dict)
              (for-each
                (fn
                  ((key :as string))
                  (let
                    ((val (dict-get spread-dict key)))
                    (if
                      (= key "class")
                      (let
                        ((existing (dom-get-attr el "class")))
                        (dom-set-attr
                          el
                          "class"
                          (if
                            (and existing (not (= existing "")))
                            (str existing " " val)
                            val)))
                      (if
                        (= key "style")
                        (let
                          ((existing (dom-get-attr el "style")))
                          (dom-set-attr
                            el
                            "style"
                            (if
                              (and existing (not (= existing "")))
                              (str existing ";" val)
                              val)))
                        (dom-set-attr el key (str val))))))
                (keys spread-dict)))
            (scope-emitted "element-attrs"))
          (scope-pop! "element-attrs")
          (do (when (hydrating?) (hydrate-exit-element)) el))))
    (define
      render-dom-component
      :effects (render)
      (fn
        ((comp :as component)
          (args :as list)
          (env :as dict)
          (ns :as string))
        (let
          ((kwargs (dict)) (children (list)))
          (reduce
            (fn
              (state arg)
              (let
                ((skip (get state "skip")))
                (if
                  skip
                  (assoc state "skip" false "i" (inc (get state "i")))
                  (if
                    (and
                      (= (type-of arg) "keyword")
                      (< (inc (get state "i")) (len args)))
                    (let
                      ((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
                      (dict-set! kwargs (keyword-name arg) val)
                      (assoc state "skip" true "i" (inc (get state "i"))))
                    (do
                      (append! children arg)
                      (assoc state "i" (inc (get state "i"))))))))
            (dict "i" 0 "skip" false)
            args)
          (let
            ((local (env-merge (component-closure comp) env)))
            (for-each
              (fn
                (p)
                (env-bind!
                  local
                  p
                  (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
              (component-params comp))
            (when
              (component-has-children? comp)
              (let
                ((child-frag (create-fragment)))
                (for-each
                  (fn
                    (c)
                    (let
                      ((result (render-to-dom c env ns)))
                      (when
                        (not (spread? result))
                        (dom-append child-frag result))))
                  children)
                (env-bind! local "children" child-frag)))
            (render-to-dom (component-body comp) local ns)))))
    (define
      render-dom-fragment
      :effects (render)
      (fn
        ((args :as list) (env :as dict) (ns :as string))
        (if
          (hydrating?)
          (let
            ((frag (create-fragment)))
            (for-each
              (fn
                (x)
                (let
                  ((result (render-to-dom x env ns)))
                  (when
                    (and (not (spread? result)) (nil? (dom-parent result)))
                    (dom-append frag result))))
              args)
            frag)
          (let
            ((frag (create-fragment)))
            (for-each
              (fn
                (x)
                (let
                  ((result (render-to-dom x env ns)))
                  (when (not (spread? result)) (dom-append frag result))))
              args)
            frag))))
    (define
      render-dom-raw
      :effects (render)
      (fn
        ((args :as list) (env :as dict))
        (let
          ((frag (create-fragment)))
          (for-each
            (fn
              (arg)
              (let
                ((val (trampoline (eval-expr arg env))))
                (cond
                  (= (type-of val) "string")
                  (dom-append frag (dom-parse-html val))
                  (= (type-of val) "dom-node")
                  (dom-append frag (dom-clone val))
                  (not (nil? val))
                  (dom-append frag (create-text-node (str val))))))
            args)
          frag)))
    (define
      render-dom-unknown-component
      :effects (render)
      (fn ((name :as string)) (error (str "Unknown component: " name))))
    (define
      RENDER_DOM_FORMS
      (list
        "if"
        "when"
        "cond"
        "case"
        "let"
        "let*"
        "letrec"
        "begin"
        "do"
        "define"
        "defcomp"
        "defisland"
        "defmacro"
        "defstyle"
        "map"
        "map-indexed"
        "filter"
        "for-each"
        "portal"
        "error-boundary"
        "scope"
        "provide"
        "cyst"))
    (define
      render-dom-form?
      :effects ()
      (fn ((name :as string)) (contains? RENDER_DOM_FORMS name)))
    (define
      dispatch-render-form
      :effects (render)
      (fn
        ((name :as string) expr (env :as dict) (ns :as string))
        (cond
          (= name "if")
          (if
            (and (island-scope?) (not (hydrating?)))
            (let
              ((marker (create-comment "r-if"))
                (current-nodes (list))
                (initial-result nil))
              (effect
                (fn
                  ()
                  (let
                    ((result (let ((cond-val (trampoline (eval-expr (nth expr 1) env)))) (if cond-val (render-to-dom (nth expr 2) env ns) (if (> (len expr) 3) (render-to-dom (nth expr 3) env ns) (create-fragment))))))
                    (if
                      (dom-parent marker)
                      (do
                        (for-each (fn (n) (dom-remove n)) current-nodes)
                        (set!
                          current-nodes
                          (if
                            (dom-is-fragment? result)
                            (dom-child-nodes result)
                            (list result)))
                        (dom-insert-after marker result))
                      (set! initial-result result)))))
              (if
                (hydrating?)
                (do
                  (when
                    (and
                      initial-result
                      (not (nil? initial-result))
                      (not (spread? initial-result)))
                    (let
                      ((parent (dom-parent initial-result)))
                      (when
                        parent
                        (host-call parent "insertBefore" marker initial-result)
                        (set! current-nodes (list initial-result)))))
                  marker)
                (if
                  (spread? initial-result)
                  initial-result
                  (let
                    ((frag (create-fragment)))
                    (dom-append frag marker)
                    (when
                      initial-result
                      (set!
                        current-nodes
                        (if
                          (dom-is-fragment? initial-result)
                          (dom-child-nodes initial-result)
                          (list initial-result)))
                      (dom-append frag initial-result))
                    frag))))
            (let
              ((cond-val (trampoline (eval-expr (nth expr 1) env))))
              (if
                cond-val
                (render-to-dom (nth expr 2) env ns)
                (if
                  (> (len expr) 3)
                  (render-to-dom (nth expr 3) env ns)
                  (create-fragment)))))
          (= name "when")
          (if
            (and (island-scope?) (not (hydrating?)))
            (let
              ((marker (create-comment "r-when"))
                (current-nodes (list))
                (initial-result nil))
              (effect
                (fn
                  ()
                  (if
                    (dom-parent marker)
                    (do
                      (for-each (fn (n) (dom-remove n)) current-nodes)
                      (set! current-nodes (list))
                      (when
                        (trampoline (eval-expr (nth expr 1) env))
                        (let
                          ((frag (create-fragment)))
                          (for-each
                            (fn
                              (i)
                              (dom-append
                                frag
                                (render-to-dom (nth expr i) env ns)))
                            (range 2 (len expr)))
                          (set! current-nodes (dom-child-nodes frag))
                          (dom-insert-after marker frag))))
                    (when
                      (trampoline (eval-expr (nth expr 1) env))
                      (if
                        (hydrating?)
                        (let
                          ((nodes (list)))
                          (for-each
                            (fn
                              (i)
                              (let
                                ((result (render-to-dom (nth expr i) env ns)))
                                (append! nodes result)))
                            (range 2 (len expr)))
                          (set! current-nodes nodes)
                          (set! initial-result (first nodes)))
                        (let
                          ((frag (create-fragment)))
                          (for-each
                            (fn
                              (i)
                              (dom-append
                                frag
                                (render-to-dom (nth expr i) env ns)))
                            (range 2 (len expr)))
                          (set! current-nodes (dom-child-nodes frag))
                          (set! initial-result frag)))))))
              (if
                (hydrating?)
                (do
                  (when
                    (and initial-result (not (nil? initial-result)))
                    (let
                      ((parent (dom-parent initial-result)))
                      (when
                        parent
                        (host-call parent "insertBefore" marker initial-result))))
                  marker)
                (if
                  (spread? initial-result)
                  initial-result
                  (let
                    ((frag (create-fragment)))
                    (dom-append frag marker)
                    (when initial-result (dom-append frag initial-result))
                    frag))))
            (if
              (not (trampoline (eval-expr (nth expr 1) env)))
              (create-fragment)
              (let
                ((frag (create-fragment)))
                (for-each
                  (fn
                    (i)
                    (dom-append frag (render-to-dom (nth expr i) env ns)))
                  (range 2 (len expr)))
                frag)))
          (= name "cond")
          (if
            (and (island-scope?) (not (hydrating?)))
            (let
              ((marker (create-comment "r-cond"))
                (current-nodes (list))
                (initial-result nil))
              (effect
                (fn
                  ()
                  (let
                    ((branch (eval-cond (rest expr) env)))
                    (if
                      (dom-parent marker)
                      (do
                        (for-each (fn (n) (dom-remove n)) current-nodes)
                        (set! current-nodes (list))
                        (when
                          branch
                          (let
                            ((result (render-to-dom branch env ns)))
                            (set!
                              current-nodes
                              (if
                                (dom-is-fragment? result)
                                (dom-child-nodes result)
                                (list result)))
                            (dom-insert-after marker result))))
                      (when
                        branch
                        (let
                          ((result (render-to-dom branch env ns)))
                          (set!
                            current-nodes
                            (if
                              (dom-is-fragment? result)
                              (dom-child-nodes result)
                              (list result)))
                          (set! initial-result result)))))))
              (if
                (hydrating?)
                (do
                  (when
                    (and
                      initial-result
                      (not (nil? initial-result))
                      (not (spread? initial-result)))
                    (let
                      ((parent (dom-parent initial-result)))
                      (when
                        parent
                        (host-call parent "insertBefore" marker initial-result)
                        (set! current-nodes (list initial-result)))))
                  marker)
                (if
                  (spread? initial-result)
                  initial-result
                  (let
                    ((frag (create-fragment)))
                    (dom-append frag marker)
                    (when initial-result (dom-append frag initial-result))
                    frag))))
            (let
              ((branch (eval-cond (rest expr) env)))
              (if branch (render-to-dom branch env ns) (create-fragment))))
          (= name "case")
          (render-to-dom (trampoline (eval-expr expr env)) env ns)
          (or (= name "let") (= name "let*"))
          (let
            ((local (process-bindings (nth expr 1) env)))
            (if
              (= (len expr) 3)
              (render-to-dom (nth expr 2) local ns)
              (let
                ((frag (create-fragment)))
                (for-each
                  (fn
                    (i)
                    (let
                      ((result (render-to-dom (nth expr i) local ns)))
                      (when (not (spread? result)) (dom-append frag result))))
                  (range 2 (len expr)))
                frag)))
          (= name "letrec")
          (let
            ((bindings (nth expr 1))
              (body (slice expr 2))
              (local (env-extend env)))
            (for-each
              (fn
                (pair)
                (let
                  ((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair)))))
                  (env-bind! local pname nil)))
              bindings)
            (for-each
              (fn
                (pair)
                (let
                  ((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair)))))
                  (env-set!
                    local
                    pname
                    (trampoline (eval-expr (nth pair 1) local)))))
              bindings)
            (when
              (> (len body) 1)
              (for-each
                (fn (e) (trampoline (eval-expr e local)))
                (init body)))
            (render-to-dom (last body) local ns))
          (or (= name "begin") (= name "do"))
          (if
            (= (len expr) 2)
            (render-to-dom (nth expr 1) env ns)
            (let
              ((frag (create-fragment)))
              (for-each
                (fn
                  (i)
                  (let
                    ((result (render-to-dom (nth expr i) env ns)))
                    (when (not (spread? result)) (dom-append frag result))))
                (range 1 (len expr)))
              frag))
          (definition-form? name)
          (do (trampoline (eval-expr expr env)) (create-fragment))
          (= name "map")
          (let
            ((coll-expr (nth expr 2)))
            (if
              (and
                (island-scope?)
                (= (type-of coll-expr) "list")
                (> (len coll-expr) 1)
                (= (type-of (first coll-expr)) "symbol")
                (= (symbol-name (first coll-expr)) "deref"))
              (let
                ((f (trampoline (eval-expr (nth expr 1) env)))
                  (sig (trampoline (eval-expr (nth coll-expr 1) env))))
                (if
                  (signal? sig)
                  (reactive-list f sig env ns)
                  (let
                    ((coll (deref sig)) (frag (create-fragment)))
                    (for-each
                      (fn
                        (item)
                        (let
                          ((val (if (lambda? f) (render-lambda-dom f (list item) env ns) (render-to-dom (apply f (list item)) env ns))))
                          (dom-append frag val)))
                      coll)
                    frag)))
              (let
                ((f (trampoline (eval-expr (nth expr 1) env)))
                  (coll (trampoline (eval-expr (nth expr 2) env)))
                  (frag (create-fragment)))
                (for-each
                  (fn
                    (item)
                    (let
                      ((val (if (lambda? f) (render-lambda-dom f (list item) env ns) (render-to-dom (apply f (list item)) env ns))))
                      (dom-append frag val)))
                  coll)
                frag)))
          (= name "map-indexed")
          (let
            ((f (trampoline (eval-expr (nth expr 1) env)))
              (coll (trampoline (eval-expr (nth expr 2) env)))
              (frag (create-fragment)))
            (for-each-indexed
              (fn
                (i item)
                (let
                  ((val (if (lambda? f) (render-lambda-dom f (list i item) env ns) (render-to-dom (apply f (list i item)) env ns))))
                  (dom-append frag val)))
              coll)
            frag)
          (= name "filter")
          (render-to-dom (trampoline (eval-expr expr env)) env ns)
          (= name "portal")
          (render-dom-portal (rest expr) env ns)
          (= name "error-boundary")
          (render-dom-error-boundary (rest expr) env ns)
          (= name "for-each")
          (let
            ((f (trampoline (eval-expr (nth expr 1) env)))
              (coll (trampoline (eval-expr (nth expr 2) env)))
              (frag (create-fragment)))
            (for-each
              (fn
                (item)
                (let
                  ((val (if (lambda? f) (render-lambda-dom f (list item) env ns) (render-to-dom (apply f (list item)) env ns))))
                  (dom-append frag val)))
              coll)
            frag)
          (= name "scope")
          (let
            ((scope-name (trampoline (eval-expr (nth expr 1) env)))
              (rest-args (slice expr 2))
              (scope-val nil)
              (body-exprs nil)
              (frag (create-fragment)))
            (if
              (and
                (>= (len rest-args) 2)
                (= (type-of (first rest-args)) "keyword")
                (= (keyword-name (first rest-args)) "value"))
              (do
                (set!
                  scope-val
                  (trampoline (eval-expr (nth rest-args 1) env)))
                (set! body-exprs (slice rest-args 2)))
              (set! body-exprs rest-args))
            (scope-push! scope-name scope-val)
            (for-each
              (fn (e) (dom-append frag (render-to-dom e env ns)))
              body-exprs)
            (scope-pop! scope-name)
            frag)
          (= name "provide")
          (let
            ((prov-name (trampoline (eval-expr (nth expr 1) env)))
              (prov-val (trampoline (eval-expr (nth expr 2) env)))
              (frag (create-fragment)))
            (scope-push! prov-name prov-val)
            (for-each
              (fn
                (i)
                (dom-append frag (render-to-dom (nth expr i) env ns)))
              (range 3 (len expr)))
            (scope-pop! prov-name)
            frag)
          (= name "cyst")
          (let
            ((cyst-key (if (and (> (len expr) 2) (= (type-of (nth expr 1)) "keyword") (= (keyword-name (nth expr 1)) "key")) (str (trampoline (eval-expr (nth expr 2) env))) (next-cyst-id)))
              (cached (get *memo-cache* cyst-key)))
            (if
              (and cached (host-get cached "isConnected"))
              cached
              (let
                ((container (dom-create-element "div" nil))
                  (disposers (list))
                  (body-exprs
                    (if
                      (and
                        (> (len expr) 2)
                        (= (type-of (nth expr 1)) "keyword")
                        (= (keyword-name (nth expr 1)) "key"))
                      (slice expr 3)
                      (slice expr 1))))
                (dom-set-attr container "data-sx-cyst" cyst-key)
                (let
                  ((body-dom (with-island-scope (fn (d) (append! disposers d)) (fn () (let ((frag (create-fragment))) (for-each (fn (child) (dom-append frag (render-to-dom child env ns))) body-exprs) frag)))))
                  (dom-append container body-dom)
                  (dom-set-data container "sx-disposers" disposers)
                  (dict-set! *memo-cache* cyst-key container)
                  container))))
          :else (render-to-dom (trampoline (eval-expr expr env)) env ns))))
    (define
      render-lambda-dom
      :effects (render)
      (fn
        ((f :as lambda) (args :as list) (env :as dict) (ns :as string))
        (let
          ((local (env-merge (lambda-closure f) env)))
          (for-each-indexed
            (fn (i p) (env-bind! local p (nth args i)))
            (lambda-params f))
          (render-to-dom (lambda-body f) local ns))))
    (define
      render-dom-island
      :effects (render mutation)
      (fn
        ((island :as island)
          (args :as list)
          (env :as dict)
          (ns :as string))
        (let
          ((kwargs (dict)) (children (list)))
          (reduce
            (fn
              (state arg)
              (let
                ((skip (get state "skip")))
                (if
                  skip
                  (assoc state "skip" false "i" (inc (get state "i")))
                  (if
                    (and
                      (= (type-of arg) "keyword")
                      (< (inc (get state "i")) (len args)))
                    (let
                      ((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
                      (dict-set! kwargs (keyword-name arg) val)
                      (assoc state "skip" true "i" (inc (get state "i"))))
                    (do
                      (append! children arg)
                      (assoc state "i" (inc (get state "i"))))))))
            (dict "i" 0 "skip" false)
            args)
          (let
            ((local (env-merge (component-closure island) env))
              (island-name (component-name island)))
            (for-each
              (fn
                (p)
                (env-bind!
                  local
                  p
                  (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
              (component-params island))
            (when
              (component-has-children? island)
              (let
                ((child-frag (create-fragment)))
                (for-each
                  (fn (c) (dom-append child-frag (render-to-dom c env ns)))
                  children)
                (env-bind! local "children" child-frag)))
            (let
              ((container (dom-create-element "span" nil))
                (disposers (list)))
              (begin
                (dom-set-attr container "data-sx-island" island-name)
                (when
                  (not (empty-dict? kwargs))
                  (dom-set-attr
                    container
                    "data-sx-state"
                    (sx-serialize kwargs))))
              (mark-processed! container "island-hydrated")
              (let
                ((body-dom (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body island) local ns)))))
                (dom-append container body-dom)
                (dom-set-data container "sx-disposers" disposers)
                container))))))
    (define
      render-dom-lake
      :effects (render)
      (fn
        ((args :as list) (env :as dict) (ns :as string))
        (let
          ((lake-id nil) (lake-tag "div") (children (list)))
          (reduce
            (fn
              (state arg)
              (let
                ((skip (get state "skip")))
                (if
                  skip
                  (assoc state "skip" false "i" (inc (get state "i")))
                  (if
                    (and
                      (= (type-of arg) "keyword")
                      (< (inc (get state "i")) (len args)))
                    (let
                      ((kname (keyword-name arg))
                        (kval
                          (trampoline
                            (eval-expr (nth args (inc (get state "i"))) env))))
                      (cond
                        (= kname "id")
                        (set! lake-id kval)
                        (= kname "tag")
                        (set! lake-tag kval))
                      (assoc state "skip" true "i" (inc (get state "i"))))
                    (do
                      (append! children arg)
                      (assoc state "i" (inc (get state "i"))))))))
            (dict "i" 0 "skip" false)
            args)
          (let
            ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element lake-tag nil))) (dom-create-element lake-tag nil))))
            (dom-set-attr el "data-sx-lake" (or lake-id ""))
            (for-each
              (fn
                (c)
                (let
                  ((result (render-to-dom c env ns)))
                  (when
                    (or (not (hydrating?)) (nil? (dom-parent result)))
                    (dom-append el result))))
              children)
            (do (when (hydrating?) (hydrate-exit-element)) el)))))
    (define
      render-dom-marsh
      :effects (render)
      (fn
        ((args :as list) (env :as dict) (ns :as string))
        (let
          ((marsh-id nil)
            (marsh-tag "div")
            (marsh-transform nil)
            (children (list)))
          (reduce
            (fn
              (state arg)
              (let
                ((skip (get state "skip")))
                (if
                  skip
                  (assoc state "skip" false "i" (inc (get state "i")))
                  (if
                    (and
                      (= (type-of arg) "keyword")
                      (< (inc (get state "i")) (len args)))
                    (let
                      ((kname (keyword-name arg))
                        (kval
                          (trampoline
                            (eval-expr (nth args (inc (get state "i"))) env))))
                      (cond
                        (= kname "id")
                        (set! marsh-id kval)
                        (= kname "tag")
                        (set! marsh-tag kval)
                        (= kname "transform")
                        (set! marsh-transform kval))
                      (assoc state "skip" true "i" (inc (get state "i"))))
                    (do
                      (append! children arg)
                      (assoc state "i" (inc (get state "i"))))))))
            (dict "i" 0 "skip" false)
            args)
          (let
            ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element marsh-tag nil))) (dom-create-element marsh-tag nil))))
            (dom-set-attr el "data-sx-marsh" (or marsh-id ""))
            (when
              marsh-transform
              (dom-set-data el "sx-marsh-transform" marsh-transform))
            (dom-set-data el "sx-marsh-env" env)
            (for-each
              (fn
                (c)
                (let
                  ((result (render-to-dom c env ns)))
                  (when
                    (or (not (hydrating?)) (nil? (dom-parent result)))
                    (dom-append el result))))
              children)
            (do (when (hydrating?) (hydrate-exit-element)) el)))))
    (define
      reactive-text
      :effects (render mutation)
      (fn
        (sig)
        (let
          ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node (str (deref sig)))) (create-text-node (str (deref sig))))))
          (effect (fn () (dom-set-text-content node (str (deref sig)))))
          node)))
    (define
      reactive-attr
      :effects (render mutation)
      (fn
        (el (attr-name :as string) (compute-fn :as lambda))
        (let
          ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
            (updated
              (if (empty? existing) attr-name (str existing "," attr-name))))
          (dom-set-attr el "data-sx-reactive-attrs" updated))
        (effect
          (fn
            ()
            (let
              ((raw (compute-fn)))
              (let
                ((val (if (signal? raw) (deref raw) raw)))
                (cond
                  (or (nil? val) (= val false))
                  (dom-remove-attr el attr-name)
                  (= val true)
                  (dom-set-attr el attr-name "")
                  :else (dom-set-attr el attr-name (str val)))))))))
    (define
      reactive-spread
      :effects (render mutation)
      (fn
        (el (render-fn :as lambda))
        (let
          ((prev-classes (list)) (prev-extra-keys (list)))
          (let
            ((existing (or (dom-get-attr el "data-sx-reactive-attrs") "")))
            (dom-set-attr
              el
              "data-sx-reactive-attrs"
              (if (empty? existing) "_spread" (str existing ",_spread"))))
          (effect
            (fn
              ()
              (when
                (not (empty? prev-classes))
                (let
                  ((current (or (dom-get-attr el "class") ""))
                    (tokens
                      (filter (fn (c) (not (= c ""))) (split current " ")))
                    (kept
                      (filter
                        (fn
                          (c)
                          (not (some (fn (pc) (= pc c)) prev-classes)))
                        tokens)))
                  (if
                    (empty? kept)
                    (dom-remove-attr el "class")
                    (dom-set-attr el "class" (join " " kept)))))
              (for-each (fn (k) (dom-remove-attr el k)) prev-extra-keys)
              (let
                ((result (render-fn)))
                (if
                  (spread? result)
                  (let
                    ((attrs (spread-attrs result))
                      (cls-str (or (dict-get attrs "class") ""))
                      (new-classes
                        (filter
                          (fn (c) (not (= c "")))
                          (split cls-str " ")))
                      (extra-keys
                        (filter (fn (k) (not (= k "class"))) (keys attrs))))
                    (set! prev-classes new-classes)
                    (set! prev-extra-keys extra-keys)
                    (when
                      (not (empty? new-classes))
                      (let
                        ((current (or (dom-get-attr el "class") "")))
                        (dom-set-attr
                          el
                          "class"
                          (if
                            (and current (not (= current "")))
                            (str current " " cls-str)
                            cls-str))))
                    (for-each
                      (fn (k) (dom-set-attr el k (str (dict-get attrs k))))
                      extra-keys)
                    (run-post-render-hooks))
                  (do
                    (set! prev-classes (list))
                    (set! prev-extra-keys (list))))))))))
    (define
      reactive-fragment
      :effects (render mutation)
      (fn
        ((test-fn :as lambda)
          (render-fn :as lambda)
          (env :as dict)
          (ns :as string))
        (let
          ((marker (create-comment "island-fragment"))
            (current-nodes (list)))
          (effect
            (fn
              ()
              (for-each (fn (n) (dom-remove n)) current-nodes)
              (set! current-nodes (list))
              (when
                (test-fn)
                (let
                  ((frag (render-fn)))
                  (set! current-nodes (dom-child-nodes frag))
                  (dom-insert-after marker frag)))))
          marker)))
    (define
      render-list-item
      :effects (render)
      (fn
        ((map-fn :as lambda) item (env :as dict) (ns :as string))
        (if
          (lambda? map-fn)
          (render-lambda-dom map-fn (list item) env ns)
          (render-to-dom (apply map-fn (list item)) env ns))))
    (define
      extract-key
      :effects (render)
      (fn
        (node (index :as number))
        (let
          ((k (dom-get-attr node "key")))
          (if
            k
            (do (dom-remove-attr node "key") k)
            (let
              ((dk (dom-get-data node "key")))
              (if dk (str dk) (str "__idx_" index)))))))
    (define
      reactive-list
      :effects (render mutation)
      (fn
        ((map-fn :as lambda)
          (items-sig :as signal)
          (env :as dict)
          (ns :as string))
        (let
          ((container (create-fragment))
            (marker (create-comment "island-list"))
            (key-map (dict))
            (key-order (list)))
          (dom-append container marker)
          (effect
            (fn
              ()
              (let
                ((items (deref items-sig)))
                (if
                  (dom-parent marker)
                  (let
                    ((new-map (dict)) (new-keys (list)) (has-keys false))
                    (for-each-indexed
                      (fn
                        (idx item)
                        (let
                          ((rendered (render-list-item map-fn item env ns))
                            (key (extract-key rendered idx)))
                          (when
                            (and
                              (not has-keys)
                              (not (starts-with? key "__idx_")))
                            (set! has-keys true))
                          (if
                            (dict-has? key-map key)
                            (dict-set! new-map key (dict-get key-map key))
                            (dict-set! new-map key rendered))
                          (append! new-keys key)))
                      items)
                    (if
                      (not has-keys)
                      (do
                        (dom-remove-children-after marker)
                        (let
                          ((frag (create-fragment)))
                          (for-each
                            (fn (k) (dom-append frag (dict-get new-map k)))
                            new-keys)
                          (dom-insert-after marker frag)))
                      (do
                        (for-each
                          (fn
                            (old-key)
                            (when
                              (not (dict-has? new-map old-key))
                              (dom-remove (dict-get key-map old-key))))
                          key-order)
                        (let
                          ((cursor marker))
                          (for-each
                            (fn
                              (k)
                              (let
                                ((node (dict-get new-map k))
                                  (next (dom-next-sibling cursor)))
                                (when
                                  (not (identical? node next))
                                  (dom-insert-after cursor node))
                                (set! cursor node)))
                            new-keys))))
                    (set! key-map new-map)
                    (set! key-order new-keys))
                  (for-each-indexed
                    (fn
                      (idx item)
                      (let
                        ((rendered (render-list-item map-fn item env ns))
                          (key (extract-key rendered idx)))
                        (dict-set! key-map key rendered)
                        (append! key-order key)
                        (dom-append container rendered)))
                    items)))))
          container)))
    (define
      bind-input
      :effects (render mutation)
      (fn
        (el (sig :as signal))
        (let
          ((input-type (lower (or (dom-get-attr el "type") "")))
            (is-checkbox
              (or (= input-type "checkbox") (= input-type "radio"))))
          (if
            is-checkbox
            (dom-set-prop el "checked" (deref sig))
            (dom-set-prop el "value" (str (deref sig))))
          (effect
            (fn
              ()
              (if
                is-checkbox
                (dom-set-prop el "checked" (deref sig))
                (let
                  ((v (str (deref sig))))
                  (when
                    (!= (dom-get-prop el "value") v)
                    (dom-set-prop el "value" v))))))
          (dom-on
            el
            (if is-checkbox "change" "input")
            (fn
              (e)
              (if
                is-checkbox
                (reset! sig (dom-get-prop el "checked"))
                (reset! sig (dom-get-prop el "value"))))))))
    (define *use-cek-reactive* true)
    (define enable-cek-reactive! (fn () (set! *use-cek-reactive* true)))
    (define
      cek-reactive-text
      :effects (render mutation)
      (fn
        (expr env)
        (let
          ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node "")) (create-text-node "")))
            (update-fn (fn (val) (dom-set-text-content node (str val)))))
          (let
            ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
            (dom-set-text-content node (str initial))
            node))))
    (define
      cek-reactive-attr
      :effects (render mutation)
      (fn
        (el attr-name expr env)
        (let
          ((update-fn (fn (val) (cond (or (nil? val) (= val false)) (dom-remove-attr el attr-name) (= val true) (dom-set-attr el attr-name "") :else (dom-set-attr el attr-name (str val))))))
          (let
            ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
              (updated
                (if (empty? existing) attr-name (str existing "," attr-name))))
            (dom-set-attr el "data-sx-reactive-attrs" updated))
          (let
            ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
            (cek-call update-fn (list initial))))))
    (define
      render-dom-portal
      :effects (render)
      (fn
        ((args :as list) (env :as dict) (ns :as string))
        (let
          ((selector (trampoline (eval-expr (first args) env)))
            (target (or (dom-query selector) (dom-ensure-element selector))))
          (if
            (not target)
            (create-comment (str "portal: " selector " (not found)"))
            (let
              ((marker (create-comment (str "portal: " selector)))
                (frag (create-fragment)))
              (for-each
                (fn (child) (dom-append frag (render-to-dom child env ns)))
                (rest args))
              (let
                ((portal-nodes (dom-child-nodes frag)))
                (dom-append target frag)
                (register-in-scope
                  (fn () (for-each (fn (n) (dom-remove n)) portal-nodes))))
              marker)))))
    (define
      render-dom-error-boundary
      :effects (render)
      (fn
        ((args :as list) (env :as dict) (ns :as string))
        (let
          ((fallback-expr (if (> (len args) 1) (first args) nil))
            (body-exprs (if (> (len args) 1) (rest args) args))
            (container (dom-create-element "div" nil))
            (retry-version (signal 0)))
          (dom-set-attr container "data-sx-boundary" "true")
          (effect
            (fn
              ()
              (deref retry-version)
              (dom-set-prop container "innerHTML" "")
              (scope-push! "sx-island-scope" nil)
              (try-catch
                (fn
                  ()
                  (let
                    ((frag (create-fragment)))
                    (for-each
                      (fn
                        (child)
                        (dom-append frag (render-to-dom child env ns)))
                      body-exprs)
                    (dom-append container frag))
                  (scope-pop! "sx-island-scope"))
                (fn
                  (err)
                  (scope-pop! "sx-island-scope")
                  (let
                    ((fallback-fn (trampoline (eval-expr fallback-expr env)))
                      (retry-fn
                        (fn () (swap! retry-version (fn (n) (+ n 1))))))
                    (let
                      ((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns)))))
                      (dom-append container fallback-dom)))))))
          container)))))

;; Re-export to global env
(import (web adapter-dom))
