;; ==========================================================================
;; adapter-dom.sx — DOM rendering adapter
;;
;; Renders SX expressions to live DOM nodes. Browser-only.
;; Mirrors the render-to-html adapter but produces Element/Text/Fragment
;; nodes instead of HTML strings.
;;
;; Depends on:
;;   render.sx  — HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, definition-form?
;;   eval.sx    — eval-expr, trampoline, call-component, expand-macro
;; ==========================================================================

(define SVG_NS "http://www.w3.org/2000/svg")
(define MATH_NS "http://www.w3.org/1998/Math/MathML")


;; --------------------------------------------------------------------------
;; render-to-dom — main entry point
;; --------------------------------------------------------------------------

(define render-to-dom
  (fn (expr env ns)
    (case (type-of expr)
      ;; nil / boolean false / boolean true → empty fragment
      "nil"       (create-fragment)
      "boolean"   (create-fragment)

      ;; Pre-rendered raw HTML → parse into fragment
      "raw-html"  (dom-parse-html (raw-html-content expr))

      ;; String → text node
      "string"    (create-text-node expr)

      ;; Number → text node
      "number"    (create-text-node (str expr))

      ;; Symbol → evaluate then render
      "symbol"    (render-to-dom (trampoline (eval-expr expr env)) env ns)

      ;; Keyword → text
      "keyword"   (create-text-node (keyword-name expr))

      ;; Pre-rendered DOM node → pass through
      "dom-node"  expr

      ;; Dict → empty
      "dict"      (create-fragment)

      ;; List → dispatch
      "list"
        (if (empty? expr)
          (create-fragment)
          (render-dom-list expr env ns))

      ;; Style value → text of class name
      "style-value" (create-text-node (style-value-class expr))

      ;; Fallback
      :else (create-text-node (str expr)))))


;; --------------------------------------------------------------------------
;; render-dom-list — dispatch on list head
;; --------------------------------------------------------------------------

(define render-dom-list
  (fn (expr env ns)
    (let ((head (first expr)))
      (cond
        ;; Symbol head — dispatch on name
        (= (type-of head) "symbol")
          (let ((name (symbol-name head))
                (args (rest expr)))
            (cond
              ;; raw! → insert unescaped HTML
              (= name "raw!")
                (render-dom-raw args env)

              ;; <> → fragment
              (= name "<>")
                (render-dom-fragment args env ns)

              ;; html: prefix → force element rendering
              (starts-with? name "html:")
                (render-dom-element (slice name 5) args env ns)

              ;; Render-aware special forms
              (render-dom-form? name)
                (if (and (contains? HTML_TAGS name)
                         (or (and (> (len args) 0)
                                  (= (type-of (first args)) "keyword"))
                             ns))
                  ;; Ambiguous: tag name that's also a form — treat as tag
                  ;; when keyword arg or namespace present
                  (render-dom-element name args env ns)
                  (dispatch-render-form name expr env ns))

              ;; Macro expansion
              (and (env-has? env name) (macro? (env-get env name)))
                (render-to-dom
                  (expand-macro (env-get env name) args env)
                  env ns)

              ;; HTML tag
              (contains? HTML_TAGS name)
                (render-dom-element name args env ns)

              ;; Component (~name)
              (starts-with? name "~")
                (let ((comp (env-get env name)))
                  (if (component? comp)
                    (render-dom-component comp args env ns)
                    (render-dom-unknown-component name)))

              ;; Custom element (hyphenated with keyword attrs)
              (and (> (index-of name "-") 0)
                   (> (len args) 0)
                   (= (type-of (first args)) "keyword"))
                (render-dom-element name args env ns)

              ;; Inside SVG/MathML namespace — treat as element
              ns
                (render-dom-element name args env ns)

              ;; Fallback — evaluate then render
              :else
                (render-to-dom (trampoline (eval-expr expr env)) env ns)))

        ;; Lambda or list head → evaluate
        (or (lambda? head) (= (type-of head) "list"))
          (render-to-dom (trampoline (eval-expr expr env)) env ns)

        ;; Data list
        :else
          (let ((frag (create-fragment)))
            (for-each (fn (x) (dom-append frag (render-to-dom x env ns))) expr)
            frag)))))


;; --------------------------------------------------------------------------
;; render-dom-element — create a DOM element with attrs and children
;; --------------------------------------------------------------------------

(define render-dom-element
  (fn (tag args env ns)
    ;; Detect namespace from tag
    (let ((new-ns (cond (= tag "svg") SVG_NS
                        (= tag "math") MATH_NS
                        :else ns))
          (el (dom-create-element tag new-ns))
          (extra-class nil))

      ;; Process args: keywords → attrs, others → children
      (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)))
                ;; Keyword arg → attribute
                (let ((attr-name (keyword-name arg))
                      (attr-val (trampoline
                                  (eval-expr
                                    (nth args (inc (get state "i")))
                                    env))))
                  (cond
                    ;; nil or false → skip
                    (or (nil? attr-val) (= attr-val false))
                      nil
                    ;; :style StyleValue → convert to class
                    (and (= attr-name "style") (style-value? attr-val))
                      (set! extra-class (style-value-class attr-val))
                    ;; Boolean attr
                    (contains? BOOLEAN_ATTRS attr-name)
                      (when attr-val (dom-set-attr el attr-name ""))
                    ;; true → empty attr
                    (= attr-val true)
                      (dom-set-attr el attr-name "")
                    ;; Normal attr
                    :else
                      (dom-set-attr el attr-name (str attr-val)))
                  (assoc state "skip" true "i" (inc (get state "i"))))

                ;; Positional arg → child
                (do
                  (when (not (contains? VOID_ELEMENTS tag))
                    (dom-append el (render-to-dom arg env new-ns)))
                  (assoc state "i" (inc (get state "i"))))))))
        (dict "i" 0 "skip" false)
        args)

      ;; Merge StyleValue class
      (when extra-class
        (let ((existing (dom-get-attr el "class")))
          (dom-set-attr el "class"
            (if existing (str existing " " extra-class) extra-class))))

      el)))


;; --------------------------------------------------------------------------
;; render-dom-component — expand and render a component
;; --------------------------------------------------------------------------

(define render-dom-component
  (fn (comp args env ns)
    ;; Parse kwargs and children, bind into component env, render body.
    (let ((kwargs (dict))
          (children (list)))
      ;; Separate keyword args from positional children
      (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)))
                ;; Keyword arg — evaluate in caller's env
                (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)

      ;; Build component env: closure + caller env + params
      (let ((local (env-merge (component-closure comp) env)))
        ;; Bind params from kwargs
        (for-each
          (fn (p)
            (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
          (component-params comp))

        ;; If component accepts children, pre-render them to a fragment
        (when (component-has-children? comp)
          (let ((child-frag (create-fragment)))
            (for-each
              (fn (c) (dom-append child-frag (render-to-dom c env ns)))
              children)
            (env-set! local "children" child-frag)))

        (render-to-dom (component-body comp) local ns)))))


;; --------------------------------------------------------------------------
;; render-dom-fragment — render children into a DocumentFragment
;; --------------------------------------------------------------------------

(define render-dom-fragment
  (fn (args env ns)
    (let ((frag (create-fragment)))
      (for-each
        (fn (x) (dom-append frag (render-to-dom x env ns)))
        args)
      frag)))


;; --------------------------------------------------------------------------
;; render-dom-raw — insert unescaped content
;; --------------------------------------------------------------------------

(define render-dom-raw
  (fn (args env)
    (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)))


;; --------------------------------------------------------------------------
;; render-dom-unknown-component — visible warning element
;; --------------------------------------------------------------------------

(define render-dom-unknown-component
  (fn (name)
    (let ((el (dom-create-element "div" nil)))
      (dom-set-attr el "style"
        "background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;padding:4px 8px;margin:2px;border-radius:4px;font-size:12px;font-family:monospace")
      (dom-append el (create-text-node (str "Unknown component: " name)))
      el)))


;; --------------------------------------------------------------------------
;; Render-aware special forms for DOM output
;; --------------------------------------------------------------------------
;; These forms need special handling in DOM rendering because they
;; produce DOM nodes rather than evaluated values.

(define RENDER_DOM_FORMS
  (list "if" "when" "cond" "case" "let" "let*" "begin" "do"
        "define" "defcomp" "defmacro" "defstyle" "defkeyframes" "defhandler"
        "map" "map-indexed" "filter" "for-each"))

(define render-dom-form?
  (fn (name)
    (contains? RENDER_DOM_FORMS name)))

(define dispatch-render-form
  (fn (name expr env ns)
    (cond
      ;; if
      (= name "if")
        (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))))

      ;; when
      (= name "when")
        (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))

      ;; cond
      (= name "cond")
        (let ((branch (eval-cond (rest expr) env)))
          (if branch
            (render-to-dom branch env ns)
            (create-fragment)))

      ;; case
      (= name "case")
        (render-to-dom (trampoline (eval-expr expr env)) env ns)

      ;; let / let*
      (or (= name "let") (= name "let*"))
        (let ((local (process-bindings (nth expr 1) env))
              (frag (create-fragment)))
          (for-each
            (fn (i)
              (dom-append frag (render-to-dom (nth expr i) local ns)))
            (range 2 (len expr)))
          frag)

      ;; begin / do
      (or (= name "begin") (= name "do"))
        (let ((frag (create-fragment)))
          (for-each
            (fn (i)
              (dom-append frag (render-to-dom (nth expr i) env ns)))
            (range 1 (len expr)))
          frag)

      ;; Definition forms — eval for side effects
      (definition-form? name)
        (do (trampoline (eval-expr expr env)) (create-fragment))

      ;; map
      (= name "map")
        (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)

      ;; map-indexed
      (= 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)

      ;; filter — evaluate fully then render
      (= name "filter")
        (render-to-dom (trampoline (eval-expr expr env)) env ns)

      ;; for-each (render variant)
      (= 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)

      ;; Fallback
      :else
        (render-to-dom (trampoline (eval-expr expr env)) env ns))))


;; --------------------------------------------------------------------------
;; render-lambda-dom — render a lambda body in DOM context
;; --------------------------------------------------------------------------

(define render-lambda-dom
  (fn (f args env ns)
    ;; Bind lambda params and render body as DOM
    (let ((local (env-merge (lambda-closure f) env)))
      (for-each-indexed
        (fn (i p)
          (env-set! local p (nth args i)))
        (lambda-params f))
      (render-to-dom (lambda-body f) local ns))))


;; --------------------------------------------------------------------------
;; Platform interface — DOM adapter
;; --------------------------------------------------------------------------
;;
;; Element creation:
;;   (dom-create-element tag ns)  → Element (ns=nil for HTML, string for SVG/MathML)
;;   (create-text-node s)         → Text node
;;   (create-fragment)            → DocumentFragment
;;
;; Tree mutation:
;;   (dom-append parent child)    → void (appendChild)
;;   (dom-set-attr el name val)   → void (setAttribute)
;;   (dom-get-attr el name)       → string or nil (getAttribute)
;;
;; Content parsing:
;;   (dom-parse-html s)           → DocumentFragment from HTML string
;;   (dom-clone node)             → deep clone of a DOM node
;;
;; Type checking:
;;   DOM nodes have type-of → "dom-node"
;;
;; From render.sx:
;;   HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, definition-form?
;;   style-value?, style-value-class
;;
;; From eval.sx:
;;   eval-expr, trampoline, expand-macro, process-bindings, eval-cond
;;   env-has?, env-get, env-set!, env-merge
;;   lambda?, component?, macro?
;;   lambda-closure, lambda-params, lambda-body
;;   component-params, component-body, component-closure,
;;   component-has-children?, component-name
;;
;; Iteration:
;;   (for-each-indexed fn coll) → call fn(index, item) for each element
;; --------------------------------------------------------------------------
