;; ==========================================================================
;; test-text-layout.sx — Tests for Pretext (DOM-free text layout)
;; ==========================================================================
;; All tests use fixed widths — no IO, pure arithmetic verification.

;; --------------------------------------------------------------------------
;; Line breaking
;; --------------------------------------------------------------------------

(defsuite
  "line-badness"
  (deftest
    "exact fit has zero badness"
    (assert-equal 0 (line-badness 100 100)))
  (deftest
    "underfull line has positive badness"
    (assert (> (line-badness 50 100) 0)))
  (deftest
    "overfull line has large badness"
    (assert (> (line-badness 110 100) 10000))))

(defsuite
  "compute-demerits"
  (deftest
    "zero badness and penalty gives 1"
    (assert-equal 1 (compute-demerits 0 0)))
  (deftest
    "badness contributes quadratically"
    (assert-equal 121 (compute-demerits 10 0)))
  (deftest
    "penalty adds independently"
    (assert-equal 26 (compute-demerits 0 5))))

(defsuite
  "sum-widths"
  (deftest
    "single word"
    (assert-equal 10 (sum-widths (list 10 20 30) 5 0 1)))
  (deftest
    "two words with space"
    (assert-equal 35 (sum-widths (list 10 20 30) 5 0 2)))
  (deftest
    "three words with spaces"
    (assert-equal 70 (sum-widths (list 10 20 30) 5 0 3)))
  (deftest
    "subset range"
    (assert-equal 55 (sum-widths (list 10 20 30) 5 1 3))))

(defsuite
  "break-lines"
  (deftest "empty input" (assert-equal (list) (break-lines (list) 5 100)))
  (deftest
    "all words fit on one line"
    (let
      ((result (break-lines (list 10 20 30) 5 100)))
      (assert-equal 1 (len result))
      (assert-equal (list 0 3) (first result))))
  (deftest
    "forced two-line break"
    (let
      ((result (break-lines (list 40 40) 5 50)))
      (assert-equal 2 (len result))
      (assert-equal (list 0 1) (first result))
      (assert-equal (list 1 2) (nth result 1))))
  (deftest
    "three lines from five words"
    (let
      ((result (break-lines (list 30 30 30 30 30) 5 70)))
      (assert-equal 3 (len result))))
  (deftest
    "single word per line when words are wide"
    (let
      ((result (break-lines (list 80 80 80) 5 90)))
      (assert-equal 3 (len result)))))

;; --------------------------------------------------------------------------
;; Positioning
;; --------------------------------------------------------------------------

(defsuite
  "position-line"
  (deftest
    "single word at origin"
    (let
      ((result (position-line (list "hello") (list 50) 5 0 0)))
      (assert-equal 1 (len result))
      (assert-equal 0 (get (first result) :x))
      (assert-equal 0 (get (first result) :y))
      (assert-equal "hello" (get (first result) :word))))
  (deftest
    "two words spaced apart"
    (let
      ((result (position-line (list "hi" "there") (list 20 40) 5 0 10)))
      (assert-equal 2 (len result))
      (assert-equal 0 (get (first result) :x))
      (assert-equal 25 (get (nth result 1) :x))
      (assert-equal 10 (get (nth result 1) :y))))
  (deftest
    "custom x offset"
    (let
      ((result (position-line (list "word") (list 30) 5 10 0)))
      (assert-equal 10 (get (first result) :x)))))

(defsuite
  "position-lines"
  (deftest
    "two lines stacked vertically"
    (let
      ((result (position-lines (list "a" "b" "c" "d") (list 10 10 10 10) (list (list 0 2) (list 2 4)) 5 20 0 0)))
      (assert-equal 2 (len result))
      (assert-equal 0 (get (first (first result)) :y))
      (assert-equal 20 (get (first (nth result 1)) :y)))))

;; --------------------------------------------------------------------------
;; Hyphenation
;; --------------------------------------------------------------------------

(defsuite
  "hyphenation-trie"
  (deftest
    "build trie from patterns"
    (let
      ((trie (make-hyphenation-trie (list "hy1p" "he2n"))))
      (assert (dict? trie))
      (assert (has-key? trie :children))))
  (deftest
    "trie has expected structure"
    (let
      ((trie (make-hyphenation-trie (list "ab1c"))))
      (assert (has-key? (get trie :children) "a")))))

(defsuite
  "hyphenate-word"
  (deftest
    "word with no patterns returns single syllable"
    (let
      ((trie (make-hyphenation-trie (list))))
      (assert-equal (list "xyz") (hyphenate-word trie "xyz"))))
  (deftest
    "simple hyphenation"
    (let
      ((trie (make-hyphenation-trie (list "hy1phen"))))
      (let
        ((syllables (hyphenate-word trie "hyphen")))
        (assert (>= (len syllables) 1))))))

;; --------------------------------------------------------------------------
;; Integration: break + position
;; --------------------------------------------------------------------------

(defsuite
  "layout-integration"
  (deftest
    "break and position round-trip"
    (let
      ((widths (list 30 30 30 30))
        (words (list "one" "two" "three" "four"))
        (space-width 5)
        (max-width 70)
        (line-height 20))
      (let
        ((ranges (break-lines widths space-width max-width)))
        (let
          ((positioned (position-lines words widths ranges space-width line-height 0 0)))
          (assert (> (len positioned) 0))
          (assert-equal 0 (get (first (first positioned)) :x))
          (assert-equal 0 (get (first (first positioned)) :y))))))
  (deftest
    "total height is lines × line-height"
    (let
      ((widths (list 80 80 80))
        (space-width 5)
        (max-width 90)
        (line-height 20))
      (let
        ((ranges (break-lines widths space-width max-width)))
        (assert-equal 3 (len ranges))))))