(define ahtml (fn (expr) (render-to-html expr {})))

(defcomp ~ahtml-card (&key title) (div :class "card" (h2 title)))

(defcomp ~ahtml-box (&rest children) (div :class "box" children))

(defcomp
  ~ahtml-panel
  (&key heading &rest children)
  (section (h3 heading) children))

(defisland ~ahtml-counter (&key count) (span (str count)))

(defisland ~ahtml-display (&key label) (span label))

(defsuite
  "adapter-html-basics"
  (deftest "nil renders empty" (assert-equal "" (ahtml nil)))
  (deftest "string escapes html" (assert-equal "&lt;b&gt;" (ahtml "<b>")))
  (deftest "number renders as string" (assert-equal "42" (ahtml 42)))
  (deftest
    "boolean renders"
    (assert-equal "true" (ahtml true))
    (assert-equal "false" (ahtml false)))
  (deftest "keyword renders name" (assert-equal "foo" (ahtml :foo))))

(defsuite
  "adapter-html-elements"
  (deftest
    "div with text"
    (assert-equal "<div>hello</div>" (ahtml (quote (div "hello")))))
  (deftest
    "div with class"
    (assert-equal
      "<div class=\"card\">hi</div>"
      (ahtml (quote (div :class "card" "hi")))))
  (deftest
    "nested elements"
    (assert-equal
      "<div><span>inner</span></div>"
      (ahtml (quote (div (span "inner"))))))
  (deftest
    "void element"
    (assert-true (starts-with? (ahtml (quote (br))) "<br")))
  (deftest
    "input void with attrs"
    (assert-true
      (string-contains?
        (ahtml (quote (input :type "text" :name "q")))
        "type=\"text\"")))
  (deftest
    "multiple children"
    (assert-equal
      "<ul><li>a</li><li>b</li></ul>"
      (ahtml (quote (ul (li "a") (li "b")))))))

(defsuite
  "adapter-html-control-flow"
  (deftest
    "if true branch"
    (assert-equal
      "<b>yes</b>"
      (ahtml (quote (if true (b "yes") (i "no"))))))
  (deftest
    "if false branch"
    (assert-equal
      "<i>no</i>"
      (ahtml (quote (if false (b "yes") (i "no"))))))
  (deftest
    "when truthy renders body"
    (assert-equal "<p>ok</p>" (ahtml (quote (when true (p "ok"))))))
  (deftest
    "when falsy renders empty"
    (assert-equal "" (ahtml (quote (when false (p "no")))))))

(defsuite
  "adapter-html-let"
  (deftest
    "let binds and renders"
    (assert-equal
      "<span>hi</span>"
      (ahtml (quote (let ((x "hi")) (span x))))))
  (deftest
    "let multiple bindings"
    (assert-equal
      "<div>AB</div>"
      (ahtml (quote (let ((a "A") (b "B")) (div (begin a b))))))))

(defsuite
  "adapter-html-map"
  (deftest
    "map renders each item"
    (assert-true
      (string-contains?
        (ahtml
          (quote
            (let
              ((items (list "a" "b" "c")))
              (map (fn (x) (li x)) items))))
        "<li>a</li>")))
  (deftest
    "for-each renders items"
    (assert-true
      (string-contains?
        (ahtml (quote (for-each (fn (x) (span x)) (list "x" "y"))))
        "<span>x</span>"))))

(defsuite
  "adapter-html-components"
  (deftest
    "component with kwargs renders"
    (let
      ((html (render-to-html (quote (~ahtml-card :title "Hello")) (test-env))))
      (assert-true (string-contains? html "Hello"))
      (assert-true (string-contains? html "card"))))
  (deftest
    "component with children renders"
    (let
      ((html (render-to-html (quote (~ahtml-box (p "inside"))) (test-env))))
      (assert-true (string-contains? html "inside"))
      (assert-true (string-contains? html "box"))))
  (deftest
    "component with keyword and rest"
    (let
      ((html (render-to-html (quote (~ahtml-panel :heading "Title" (p "body"))) (test-env))))
      (assert-true (string-contains? html "Title"))
      (assert-true (string-contains? html "body")))))

(defsuite
  "adapter-html-fragments"
  (deftest
    "fragment renders children"
    (assert-equal "<b>a</b><i>b</i>" (ahtml (quote (<> (b "a") (i "b"))))))
  (deftest "empty fragment" (assert-equal "" (ahtml (quote (<>))))))

(defsuite
  "adapter-html-raw"
  (deftest
    "raw! passes through unescaped"
    (assert-equal "<b>bold</b>" (ahtml (quote (raw! "<b>bold</b>"))))))

(defsuite
  "adapter-html-render-form-predicate"
  (deftest "if is a render form" (assert-true (render-html-form? "if")))
  (deftest "when is a render form" (assert-true (render-html-form? "when")))
  (deftest "map is a render form" (assert-true (render-html-form? "map")))
  (deftest
    "div is not a render form"
    (assert-false (render-html-form? "div")))
  (deftest
    "scope is a render form"
    (assert-true (render-html-form? "scope")))
  (deftest
    "provide is a render form"
    (assert-true (render-html-form? "provide"))))

(defsuite
  "adapter-html-serialize-island-state"
  (deftest
    "empty dict returns nil"
    (assert-nil (serialize-island-state {})))
  (deftest
    "non-empty dict returns sx string"
    (let
      ((result (serialize-island-state {:count 0})))
      (assert-true (string? result))
      (assert-true (string-contains? result "count")))))

(defsuite
  "adapter-html-islands"
  (deftest
    "island renders with data attributes"
    (let
      ((html (render-to-html (quote (~ahtml-counter :count 0)) (test-env))))
      (assert-true (string-contains? html "data-sx-island"))
      (assert-true (string-contains? html "ahtml-counter"))))
  (deftest
    "island includes state"
    (let
      ((html (render-to-html (quote (~ahtml-display :label "hi")) (test-env))))
      (assert-true (string-contains? html "data-sx-state"))
      (assert-true (string-contains? html "label")))))

(defsuite
  "adapter-html-lakes"
  (deftest
    "lake renders with data attribute"
    (let
      ((html (ahtml (quote (lake :id "my-lake" (p "content"))))))
      (assert-true (string-contains? html "data-sx-lake"))
      (assert-true (string-contains? html "content")))))

(defsuite
  "adapter-html-marshes"
  (deftest
    "marsh renders with data attribute"
    (let
      ((html (ahtml (quote (marsh :id "my-marsh" (p "data"))))))
      (assert-true (string-contains? html "data-sx-marsh"))
      (assert-true (string-contains? html "data")))))

(defsuite
  "adapter-html-definitions"
  (deftest
    "define renders empty"
    (assert-equal "" (ahtml (quote (define test-val 42)))))
  (deftest
    "defmacro renders empty"
    (assert-equal "" (ahtml (quote (defmacro test-m (x) x))))))
