;; ==========================================================================
;; test-render-advanced.sx — Advanced HTML rendering tests
;;
;; Requires: test-framework.sx loaded first.
;; Modules tested: render.sx, adapter-html.sx, eval.sx
;;
;; Platform functions required (beyond test framework):
;;   render-html   (sx-source)  -> HTML string
;;     Parses the sx-source string, evaluates via render-to-html in a
;;     fresh env, and returns the resulting HTML string.
;;
;; Covers advanced rendering scenarios not addressed in test-render.sx:
;;   - Deeply nested component calls
;;   - Dynamic content (let, define, cond, case)
;;   - List processing patterns (map, filter, reduce, map-indexed)
;;   - Component patterns (defaults, nil bodies, map over children)
;;   - Special element edge cases (fragments, void attrs, nil content)
;; ==========================================================================


;; --------------------------------------------------------------------------
;; Nested component rendering
;; --------------------------------------------------------------------------

(defsuite "render-nested-components"
  (deftest "component calling another component"
    ;; Inner component renders a span; outer wraps it in a div
    (let ((html (render-html
                  "(do
                     (defcomp ~inner (&key label) (span label))
                     (defcomp ~outer (&key text)  (div (~inner :label text)))
                     (~outer :text \"hello\"))")))
      (assert-true (string-contains? html "<div>"))
      (assert-true (string-contains? html "<span>hello</span>"))
      (assert-true (string-contains? html "</div>"))))

  (deftest "three levels of nesting"
    ;; A → B → C, each wrapping the next
    (let ((html (render-html
                  "(do
                     (defcomp ~c () (em \"deep\"))
                     (defcomp ~b () (strong (~c)))
                     (defcomp ~a () (p (~b)))
                     (~a))")))
      (assert-true (string-contains? html "<p>"))
      (assert-true (string-contains? html "<strong>"))
      (assert-true (string-contains? html "<em>deep</em>"))
      (assert-true (string-contains? html "</strong>"))
      (assert-true (string-contains? html "</p>"))))

  (deftest "component with children that are components"
    ;; ~badge renders as a span; ~toolbar wraps whatever children it gets
    (let ((html (render-html
                  "(do
                     (defcomp ~badge (&key text) (span :class \"badge\" text))
                     (defcomp ~toolbar (&rest children) (nav children))
                     (~toolbar (~badge :text \"Home\") (~badge :text \"About\")))")))
      (assert-true (string-contains? html "<nav>"))
      (assert-true (string-contains? html "class=\"badge\""))
      (assert-true (string-contains? html "Home"))
      (assert-true (string-contains? html "About"))
      (assert-true (string-contains? html "</nav>"))))

  (deftest "component that wraps children in a div"
    ;; Classic container pattern: keyword title + arbitrary children
    (let ((html (render-html
                  "(do
                     (defcomp ~card (&key title &rest children)
                       (div :class \"card\"
                         (h3 title)
                         children))
                     (~card :title \"My Card\"
                       (p \"First\")
                       (p \"Second\")))")))
      (assert-true (string-contains? html "class=\"card\""))
      (assert-true (string-contains? html "<h3>My Card</h3>"))
      (assert-true (string-contains? html "<p>First</p>"))
      (assert-true (string-contains? html "<p>Second</p>")))))


;; --------------------------------------------------------------------------
;; Dynamic content
;; --------------------------------------------------------------------------

(defsuite "render-dynamic-content"
  (deftest "let binding computed values"
    ;; let computes a value and uses it in the rendered output
    (assert-equal "<span>30</span>"
      (render-html "(let ((x 10) (y 20)) (span (+ x y)))")))

  (deftest "define inside do block"
    ;; Definitions accumulate across do statements
    (assert-equal "<p>hello world</p>"
      (render-html "(do
                      (define greeting \"hello\")
                      (define target \"world\")
                      (p (str greeting \" \" target)))")))

  (deftest "nested let scoping"
    ;; Inner let shadows outer binding; outer binding restored after
    (assert-equal "<div><span>inner</span><span>outer</span></div>"
      (render-html "(do
                      (define label \"outer\")
                      (div
                        (let ((label \"inner\")) (span label))
                        (span label)))")))

  (deftest "cond dispatching different elements"
    ;; Different cond branches produce different tags
    (assert-equal "<h1>big</h1>"
      (render-html "(let ((size \"large\"))
                      (cond (= size \"large\") (h1 \"big\")
                            (= size \"small\") (h6 \"small\")
                            :else             (p  \"medium\")))"))
    (assert-equal "<h6>small</h6>"
      (render-html "(let ((size \"small\"))
                      (cond (= size \"large\") (h1 \"big\")
                            (= size \"small\") (h6 \"small\")
                            :else             (p  \"medium\")))"))
    (assert-equal "<p>medium</p>"
      (render-html "(let ((size \"other\"))
                      (cond (= size \"large\") (h1 \"big\")
                            (= size \"small\") (h6 \"small\")
                            :else             (p  \"medium\")))")))

  (deftest "cond dispatching different elements"
    ;; cond on a value selects between different rendered elements
    (assert-equal "<strong>bold</strong>"
      (render-html "(let ((style \"bold\"))
                      (cond (= style \"bold\")   (strong \"bold\")
                            (= style \"italic\") (em \"italic\")
                            :else                (span \"normal\")))"))
    (assert-equal "<em>italic</em>"
      (render-html "(let ((style \"italic\"))
                      (cond (= style \"bold\")   (strong \"bold\")
                            (= style \"italic\") (em \"italic\")
                            :else                (span \"normal\")))"))
    (assert-equal "<span>normal</span>"
      (render-html "(let ((style \"other\"))
                      (cond (= style \"bold\")   (strong \"bold\")
                            (= style \"italic\") (em \"italic\")
                            :else                (span \"normal\")))"))))


;; --------------------------------------------------------------------------
;; List processing patterns
;; --------------------------------------------------------------------------

(defsuite "render-list-patterns"
  (deftest "map producing li items inside ul"
    (assert-equal "<ul><li>a</li><li>b</li><li>c</li></ul>"
      (render-html "(ul (map (fn (x) (li x)) (list \"a\" \"b\" \"c\")))")))

  (deftest "filter then map inside container"
    ;; Keep only even numbers, render each as a span
    (assert-equal "<div><span>2</span><span>4</span></div>"
      (render-html "(div (map (fn (x) (span x))
                              (filter (fn (x) (= (mod x 2) 0))
                                      (list 1 2 3 4 5))))")))

  (deftest "reduce building a string inside a span"
    ;; Join words with a separator via reduce, wrap in span
    (assert-equal "<span>a-b-c</span>"
      (render-html "(let ((words (list \"a\" \"b\" \"c\")))
                      (span (reduce (fn (acc w)
                                      (if (= acc \"\")
                                        w
                                        (str acc \"-\" w)))
                                    \"\"
                                    words)))")))

  (deftest "map-indexed producing numbered items"
    ;; map-indexed provides both the index and the value
    (assert-equal "<ol><li>1. alpha</li><li>2. beta</li><li>3. gamma</li></ol>"
      (render-html "(ol (map-indexed
                          (fn (i x) (li (str (+ i 1) \". \" x)))
                          (list \"alpha\" \"beta\" \"gamma\")))")))

  (deftest "nested map (map inside map)"
    ;; Each outer item produces a ul; inner items produce li
    (let ((html (render-html
                  "(div (map (fn (row)
                               (ul (map (fn (cell) (li cell)) row)))
                             (list (list \"a\" \"b\")
                                   (list \"c\" \"d\"))))")))
      (assert-true (string-contains? html "<div>"))
      ;; Both inner uls must appear
      (assert-true (string-contains? html "<li>a</li>"))
      (assert-true (string-contains? html "<li>b</li>"))
      (assert-true (string-contains? html "<li>c</li>"))
      (assert-true (string-contains? html "<li>d</li>"))))

  (deftest "empty map produces no children"
    ;; mapping over an empty list contributes nothing to the parent
    (assert-equal "<ul></ul>"
      (render-html "(ul (map (fn (x) (li x)) (list)))"))))


;; --------------------------------------------------------------------------
;; Component patterns
;; --------------------------------------------------------------------------

(defsuite "render-component-patterns"
  (deftest "component with conditional rendering (when)"
    ;; when true → renders child; when false → renders nothing
    (let ((html-on  (render-html
                      "(do (defcomp ~toggle (&key active)
                             (div (when active (span \"on\"))))
                           (~toggle :active true))"))
          (html-off (render-html
                      "(do (defcomp ~toggle (&key active)
                             (div (when active (span \"on\"))))
                           (~toggle :active false))")))
      (assert-true  (string-contains? html-on  "<span>on</span>"))
      (assert-false (string-contains? html-off "<span>"))))

  (deftest "component with default keyword value (or pattern)"
    ;; Missing keyword falls back to default; explicit value overrides it
    (let ((with-default (render-html
                          "(do (defcomp ~btn (&key label)
                                 (button (or label \"Click me\")))
                               (~btn))"))
          (with-value   (render-html
                          "(do (defcomp ~btn (&key label)
                                 (button (or label \"Click me\")))
                               (~btn :label \"Submit\"))")))
      (assert-equal "<button>Click me</button>" with-default)
      (assert-equal "<button>Submit</button>"   with-value)))

  (deftest "component composing other components"
    ;; ~page uses ~header and ~footer as sub-components
    (let ((html (render-html
                  "(do
                     (defcomp ~header () (header (h1 \"Top\")))
                     (defcomp ~footer () (footer \"Bottom\"))
                     (defcomp ~page   () (div (~header) (~footer)))
                     (~page))")))
      (assert-true (string-contains? html "<header>"))
      (assert-true (string-contains? html "<h1>Top</h1>"))
      (assert-true (string-contains? html "<footer>"))
      (assert-true (string-contains? html "Bottom"))))

  (deftest "component with map over children"
    ;; Component receives a list via keyword, maps it to li elements
    (let ((html (render-html
                  "(do
                     (defcomp ~item-list (&key items)
                       (ul (map (fn (x) (li x)) items)))
                     (~item-list :items (list \"x\" \"y\" \"z\")))")))
      (assert-true (string-contains? html "<ul>"))
      (assert-true (string-contains? html "<li>x</li>"))
      (assert-true (string-contains? html "<li>y</li>"))
      (assert-true (string-contains? html "<li>z</li>"))
      (assert-true (string-contains? html "</ul>"))))

  (deftest "component that renders nothing (nil body)"
    ;; A component whose body evaluates to nil produces no output
    (assert-equal ""
      (render-html "(do (defcomp ~empty () nil) (~empty))"))))


;; --------------------------------------------------------------------------
;; Special element edge cases
;; --------------------------------------------------------------------------

(defsuite "render-special-elements"
  (deftest "fragment with mixed children: elements and bare text"
    ;; (<> ...) strips the wrapper — children appear side by side
    (assert-equal "<p>a</p>text<p>b</p>"
      (render-html "(<> (p \"a\") \"text\" (p \"b\"))")))

  (deftest "void element with multiple attributes"
    ;; input is void (self-closing) and must carry its attrs correctly
    (let ((html (render-html "(input :type \"text\" :placeholder \"Search…\")")))
      (assert-true  (string-contains? html "<input"))
      (assert-true  (string-contains? html "type=\"text\""))
      (assert-true  (string-contains? html "placeholder="))
      (assert-true  (string-contains? html "/>"))
      (assert-false (string-contains? html "</input>"))))

  (deftest "boolean attribute true emits name only"
    ;; :disabled true → the word "disabled" appears without a value
    (let ((html (render-html "(input :type \"checkbox\" :disabled true)")))
      (assert-true  (string-contains? html "disabled"))
      (assert-false (string-contains? html "disabled=\""))))

  (deftest "boolean attribute false is omitted entirely"
    ;; :disabled false → the attribute must not appear at all
    (let ((html (render-html "(input :type \"checkbox\" :disabled false)")))
      (assert-false (string-contains? html "disabled"))))

  (deftest "raw number as element content"
    ;; Numbers passed as children must be coerced to their string form
    (assert-equal "<span>42</span>"
      (render-html "(span 42)")))

  (deftest "nil content omitted, non-nil siblings kept"
    ;; nil should not contribute text or tags; sibling content survives
    (let ((html (render-html "(div nil \"hello\")")))
      (assert-true  (string-contains? html "hello"))
      (assert-false (string-contains? html "nil"))))

  (deftest "nil-only content leaves element empty"
    ;; A div whose only child is nil should render as an empty div
    (assert-equal "<div></div>"
      (render-html "(div nil)"))))
