;; web/console-render.sx — render a live DOM element tree to terminal text.
;;
;; The SX hypermedia engine builds the SAME element tree whatever the platform: a
;; browser PAINTS that tree, the test harness ASSERTS it, and this PRINTS it. Three
;; bindings of one tree — the proof that the engine is a general runtime, not a
;; browser library (Phase 3 of plans/sx-native-engine-tests.md).
;;
;; It reads the tree through the engine's own dom-* accessors, so it renders
;; whatever the engine produced — e.g. the ~relate-picker after a load/filter/paging
;; swap: the results <ul> becomes a bulleted list, the filter <input> a text field,
;; the load-more sentinel a "…" line, and an .sx-error element a flagged line. The
;; same render-to-console is the console platform's draw step; the picker's SX engine
;; tests (web/tests/test-relate-picker.sx) double as its regression suite — they
;; already drive the tree, this just prints it.

;; class membership via the live classList (dom-has-class?), NOT the parsed `class`
;; attribute — the engine adds .sx-error through classList, which leaves the static
;; attribute untouched, so an attribute read would miss it.
(define cr/has-class?
  (fn (el cls) (dom-has-class? el cls)))

;; The visible text of an element: its own textContent when it has no element
;; children, else the concatenation of its children's text (the mock parser hangs
;; leaf text on textContent, so this covers both row buttons and the sentinel).
(define cr/text-of
  (fn (el)
    (if (nil? el)
      ""
      (let ((kids (dom-child-list el)))
        (if (empty? kids)
          (or (host-get el "textContent") "")
          (let ((parts (map cr/text-of kids)))
            (let ((joined (join "" parts)))
              (if (= joined "")
                (or (host-get el "textContent") "")
                joined))))))))

;; A <ul>/<ol> renders one line per <li>: a bullet for a candidate row, an ellipsis
;; for the load-more sentinel (.rp-more).
(define cr/list-lines
  (fn (el)
    (map
      (fn (li)
        (if (cr/has-class? li "rp-more")
          (str "  … " (cr/text-of li))
          (str "  • " (cr/text-of li))))
      (dom-child-list el))))

;; An <input> renders as a labelled text field; hidden inputs aren't drawn.
(define cr/input-line
  (fn (el)
    (let ((type (or (dom-get-attr el "type") "text")))
      (if (= type "hidden")
        ""
        (let ((label (or (dom-get-attr el "placeholder") (dom-get-attr el "name") ""))
              (val (or (host-get el "value") "")))
          (str label ": [" (if (= val "") "          " val) "]"))))))

;; Flatten the rendered lines of an element's children, dropping blanks.
(define cr/children-lines
  (fn (el)
    (let ((out (list)))
      (for-each
        (fn (kid)
          (for-each
            (fn (ln) (when (not (= ln "")) (append! out ln)))
            (cr/lines kid)))
        (dom-child-list el))
      out)))

;; Render one element to a list of text lines, tag-driven. An .sx-error element is
;; flagged with a leading status line (the terminal's red line).
(define cr/lines
  (fn (el)
    (if (nil? el)
      (list)
      (let ((tag (lower (dom-tag-name el)))
            (err (cr/has-class? el "sx-error")))
        (let
          ((body
            (cond
              (or (= tag "ul") (= tag "ol")) (cr/list-lines el)
              (= tag "input") (let ((l (cr/input-line el))) (if (= l "") (list) (list l)))
              (= tag "button") (list (str "[ " (cr/text-of el) " ]"))
              (or (= tag "h1") (= tag "h2") (= tag "h3")) (list (upper (cr/text-of el)))
              :else (cr/children-lines el))))
          (if err
            (cons "✖ connection problem — retrying…" body)
            body))))))

;; render-to-console — the DOM-tree → terminal-text mode, alongside render-to-html /
;; render-to-dom. Returns the rendered tree as a single newline-joined string.
(define render-to-console
  (fn (el) (join "\n" (cr/lines el))))
