;; lib/guest/tests/layout.sx — synthetic Python-ish off-side fixture.
;;
;; Exercises lib/guest/layout.sx with a config different from Haskell's
;; (no module-prelude, layout opens via trailing `:` not via reserved
;; keyword) to prove the kit isn't Haskell-shaped.

(define glayout-test-pass 0)
(define glayout-test-fail 0)
(define glayout-test-fails (list))

(define
  glayout-test
  (fn (name actual expected)
    (if (= actual expected)
      (set! glayout-test-pass (+ glayout-test-pass 1))
      (begin
        (set! glayout-test-fail (+ glayout-test-fail 1))
        (append! glayout-test-fails {:name name :expected expected :actual actual})))))

;; Convenience: build a token from {type value line col}.
(define
  glayout-tok
  (fn (ty val line col)
    {:type ty :value val :line line :col col}))

;; Project a token list to ((type value) ...) for compact comparison.
(define
  glayout-shape
  (fn (toks)
    (map (fn (t) (list (get t :type) (get t :value))) toks)))

;; ── Haskell-flavour: keyword opens block ─────────────────────────
(define
  glayout-haskell-cfg
  {:open-keywords (list "let" "where" "do" "of")
   :open-trailing-fn nil
   :open-token   {:type "vlbrace" :value "{"}
   :close-token  {:type "vrbrace" :value "}"}
   :sep-token    {:type "vsemi"   :value ";"}
   :module-prelude? false
   :explicit-open? (fn (tok) (= (get tok :type) "lbrace"))})

;;   do
;;     a
;;     b
;;   c       ← outside the do-block
(glayout-test "haskell-do-block"
  (glayout-shape
    (layout-pass
      glayout-haskell-cfg
      (list (glayout-tok "reserved" "do" 1 1)
            (glayout-tok "ident" "a" 2 3)
            (glayout-tok "ident" "b" 3 3)
            (glayout-tok "ident" "c" 4 1))))
  (list (list "reserved" "do")
        (list "vlbrace" "{")
        (list "ident" "a")
        (list "vsemi" ";")
        (list "ident" "b")
        (list "vrbrace" "}")
        (list "ident" "c")))

;; Explicit `{` after `do` suppresses virtual layout.
(glayout-test "haskell-explicit-brace"
  (glayout-shape
    (layout-pass
      glayout-haskell-cfg
      (list (glayout-tok "reserved" "do" 1 1)
            (glayout-tok "lbrace" "{" 1 4)
            (glayout-tok "ident" "a" 1 6)
            (glayout-tok "rbrace" "}" 1 8))))
  (list (list "reserved" "do")
        (list "lbrace" "{")
        (list "ident" "a")
        (list "rbrace" "}")))

;; Single-statement do-block on the same line.
(glayout-test "haskell-do-inline"
  (glayout-shape
    (layout-pass
      glayout-haskell-cfg
      (list (glayout-tok "reserved" "do" 1 1)
            (glayout-tok "ident" "a" 1 4))))
  (list (list "reserved" "do")
        (list "vlbrace" "{")
        (list "ident" "a")
        (list "vrbrace" "}")))

;; Module-prelude: wrap whole input in implicit layout block at first
;; tok's column.
(glayout-test "haskell-module-prelude"
  (glayout-shape
    (layout-pass
      (assoc glayout-haskell-cfg :module-prelude? true)
      (list (glayout-tok "ident" "x" 1 1)
            (glayout-tok "ident" "y" 2 1)
            (glayout-tok "ident" "z" 3 1))))
  (list (list "vlbrace" "{")
        (list "ident" "x")
        (list "vsemi" ";")
        (list "ident" "y")
        (list "vsemi" ";")
        (list "ident" "z")
        (list "vrbrace" "}")))

;; ── Python-flavour: trailing `:` opens block ─────────────────────
(define
  glayout-python-cfg
  {:open-keywords (list)
   :open-trailing-fn (fn (tok) (and (= (get tok :type) "punct")
                                    (= (get tok :value) ":")))
   :open-token   {:type "indent" :value "INDENT"}
   :close-token  {:type "dedent" :value "DEDENT"}
   :sep-token    {:type "newline" :value "NEWLINE"}
   :module-prelude? false
   :explicit-open? nil})

;;   if x:
;;       a
;;       b
;;   c
(glayout-test "python-if-block"
  (glayout-shape
    (layout-pass
      glayout-python-cfg
      (list (glayout-tok "reserved" "if" 1 1)
            (glayout-tok "ident" "x" 1 4)
            (glayout-tok "punct" ":" 1 5)
            (glayout-tok "ident" "a" 2 5)
            (glayout-tok "ident" "b" 3 5)
            (glayout-tok "ident" "c" 4 1))))
  (list (list "reserved" "if")
        (list "ident" "x")
        (list "punct" ":")
        (list "indent" "INDENT")
        (list "ident" "a")
        (list "newline" "NEWLINE")
        (list "ident" "b")
        (list "dedent" "DEDENT")
        (list "ident" "c")))

;; Nested Python-style blocks.
;;   def f():
;;       if x:
;;           a
;;       b
(glayout-test "python-nested"
  (glayout-shape
    (layout-pass
      glayout-python-cfg
      (list (glayout-tok "reserved" "def" 1 1)
            (glayout-tok "ident" "f" 1 5)
            (glayout-tok "punct" "(" 1 6)
            (glayout-tok "punct" ")" 1 7)
            (glayout-tok "punct" ":" 1 8)
            (glayout-tok "reserved" "if" 2 5)
            (glayout-tok "ident" "x" 2 8)
            (glayout-tok "punct" ":" 2 9)
            (glayout-tok "ident" "a" 3 9)
            (glayout-tok "ident" "b" 4 5))))
  (list (list "reserved" "def")
        (list "ident" "f")
        (list "punct" "(")
        (list "punct" ")")
        (list "punct" ":")
        (list "indent" "INDENT")
        (list "reserved" "if")
        (list "ident" "x")
        (list "punct" ":")
        (list "indent" "INDENT")
        (list "ident" "a")
        (list "dedent" "DEDENT")
        (list "ident" "b")
        (list "dedent" "DEDENT")))

(define glayout-tests-run!
  (fn ()
    {:passed glayout-test-pass
     :failed glayout-test-fail
     :total  (+ glayout-test-pass glayout-test-fail)}))
