(define _mock-form (dict))

(define _mock-args (dict))

(define _mock-state (dict))

(define _mock-body "")

(define _mock-content-type "")

(define _mock-headers (dict))

(define _mock-now "12:00:00")

(define
  reset-mocks!
  (fn
    ()
    (do
      (set! _mock-form (dict))
      (set! _mock-args (dict))
      (set! _mock-state (dict))
      (set! _mock-body "")
      (set! _mock-content-type "")
      (set! _mock-headers (dict)))))

(define
  helper
  (fn
    (name a1 a2)
    (cond
      (= name "request-form")
      (let
        ((key (or a1 "")) (default (if (nil? a2) "" a2)))
        (let ((val (get _mock-form key))) (if (nil? val) default val)))
      (= name "request-arg")
      (let
        ((key (or a1 "")) (default a2))
        (let ((val (get _mock-args key))) (if (nil? val) default val)))
      (= name "state-get")
      (let
        ((key (or a1 "")) (default a2))
        (let ((val (get _mock-state key))) (if (nil? val) default val)))
      (= name "state-set!")
      (do (set! _mock-state (assoc _mock-state a1 a2)) nil)
      (= name "now")
      _mock-now
      (= name "component-source")
      (str "(defcomp " a1 " () (div))")
      (= name "request-json")
      _mock-body
      (= name "request-content-type")
      _mock-content-type
      (= name "request-form-list")
      (or (get _mock-form a1) (list))
      (= name "request-args-all")
      _mock-args
      (= name "request-headers-all")
      _mock-headers
      (= name "request-form-all")
      _mock-form
      (= name "request-header")
      (or (get _mock-headers a1) a2)
      (= name "request-file-name")
      (or (get _mock-form a1) "")
      (= name "into")
      (let
        ((coll (if (nil? a2) a1 a2)))
        (if
          (dict? coll)
          (map (fn (key) (list key (get coll key))) (keys coll))
          (if (nil? coll) (list) coll)))
      :else nil)))

(define sleep (fn (ms) nil))

(define set-response-status (fn (code) nil))

(define json-encode (fn (val) (inspect val)))

(define random-int (fn (lo hi) lo))

(define
  run-handler
  (fn
    (hdef)
    (do
      (let
        ((params (get hdef "params")) (closure (get hdef "closure")))
        (when
          (and params (not (empty? params)))
          (for-each
            (fn
              (p)
              (let
                ((val (get _mock-args p)))
                (env-bind! closure p (or val nil))))
            params)))
      (let
        ((result (aser (get hdef "body") (get hdef "closure"))))
        (if
          (sx-expr? result)
          (sx-expr-source result)
          (if (string? result) result (str result)))))))

(defsuite
  "swap:click-to-load"
  (deftest
    "innerHTML replaces target content"
    (reset-mocks!)
    (let
      ((page "(div :id \"click-result\" (p \"Click the button\"))")
        (response (run-handler handler:ex-click)))
      (let
        ((result (sx-swap page "innerHTML" "click-result" response)))
        (do
          (assert-true (contains? result "~examples/click-result"))
          (assert-true (contains? result "12:00:00"))
          (assert-false (contains? result "Click the button")))))))

(defsuite
  "swap:form-submission"
  (deftest
    "innerHTML replaces with greeting"
    (reset-mocks!)
    (set! _mock-form {:name "Alice"})
    (let
      ((page "(div :id \"form-result\" (p \"Submit the form\"))")
        (response (run-handler handler:ex-form)))
      (let
        ((result (sx-swap page "innerHTML" "form-result" response)))
        (do
          (assert-true (contains? result "Alice"))
          (assert-false (contains? result "Submit the form")))))))

(defsuite
  "swap:polling"
  (deftest
    "innerHTML shows counter after increment"
    (reset-mocks!)
    (let
      ((page "(div :id \"poll-result\" (p \"Waiting...\"))")
        (response (run-handler handler:ex-poll)))
      (let
        ((result (sx-swap page "innerHTML" "poll-result" response)))
        (do
          (assert-true (contains? result "1"))
          (assert-false (contains? result "Waiting")))))))

(defsuite
  "swap:oob-updates"
  (deftest
    "primary + OOB both update"
    (reset-mocks!)
    (let
      ((page "(div (div :id \"oob-box-a\" (p \"A old\")) (div :id \"oob-box-b\" (p \"B old\")))")
        (response (run-handler handler:ex-oob)))
      (let
        ((result (apply-response page response "innerHTML" "oob-box-a")))
        (do
          (assert-true (contains? result "Box A updated!"))
          (assert-true (contains? result "Box B updated!"))
          (assert-false (contains? result "A old"))
          (assert-false (contains? result "B old")))))))

(defsuite
  "swap:search"
  (deftest
    "innerHTML replaces search results"
    (reset-mocks!)
    (set! _mock-args {:q "Python"})
    (let
      ((page "(div :id \"search-result\" (p \"Type to search\"))")
        (response (run-handler handler:ex-search)))
      (let
        ((result (sx-swap page "innerHTML" "search-result" response)))
        (do
          (assert-true (contains? result "Python"))
          (assert-false (contains? result "Type to search")))))))

(defsuite
  "swap:delete-row"
  (deftest
    "outerHTML removes element"
    (let
      ((page "(table (tr :id \"row-1\" (td \"Item 1\")) (tr :id \"row-2\" (td \"Item 2\")))")
        (response ""))
      (let
        ((result (sx-swap page "outerHTML" "row-1" response)))
        (do
          (assert-false (contains? result "Item 1"))
          (assert-true (contains? result "Item 2")))))))

(defsuite
  "swap:beforeend"
  (deftest
    "appends new item to list"
    (let
      ((page "(ul :id \"items\" (li \"first\"))") (response "(li \"second\")"))
      (let
        ((result (sx-swap page "beforeend" "items" response)))
        (do
          (assert-true (contains? result "first"))
          (assert-true (contains? result "second")))))))

(defsuite
  "swap:infinite-scroll"
  (deftest
    "page 1 appends items to container"
    (reset-mocks!)
    (set! _mock-args {:page "1"})
    (let
      ((page "(div :id \"scroll-items\" (div \"Item 0 — seed\"))")
        (response (run-handler handler:ex-scroll)))
      (let
        ((result (sx-swap page "beforeend" "scroll-items" response)))
        (do
          (assert-true (contains? result "Item 0"))
          (assert-true (contains? result "Item 1"))
          (assert-true (contains? result "page 1"))
          (assert-true (contains? result "scroll-sentinel"))))))
  (deftest
    "page 2 appends more items"
    (reset-mocks!)
    (set! _mock-args {:page "2"})
    (let
      ((page "(div :id \"scroll-items\" (div \"page 1 items\"))")
        (response (run-handler handler:ex-scroll)))
      (let
        ((result (sx-swap page "beforeend" "scroll-items" response)))
        (do
          (assert-true (contains? result "page 1 items"))
          (assert-true (contains? result "Item 6"))
          (assert-true (contains? result "page 2"))))))
  (deftest
    "last page has no sentinel"
    (reset-mocks!)
    (set! _mock-args {:page "6"})
    (let
      ((page "(div :id \"scroll-items\")")
        (response (run-handler handler:ex-scroll)))
      (let
        ((result (sx-swap page "beforeend" "scroll-items" response)))
        (do
          (assert-true (contains? result "All items loaded"))
          (assert-false (contains? result "scroll-sentinel")))))))

(defsuite
  "swap:state-across-calls"
  (deftest
    "counter increments across handler calls"
    (reset-mocks!)
    (let
      ((r1 (run-handler handler:ex-poll)))
      (let
        ((r2 (run-handler handler:ex-poll)))
        (let
          ((page "(div :id \"counter\" (span \"0\"))")
            (result (sx-swap page "innerHTML" "counter" r2)))
          (assert-true (contains? result "2")))))))

(defsuite
  "swap:dashboard"
  (deftest
    "innerHTML replaces with stats"
    (reset-mocks!)
    (let
      ((page "(div :id \"dash-content\" (p \"Loading...\"))")
        (response (run-handler handler:ex-dashboard)))
      (let
        ((result (sx-swap page "innerHTML" "dash-content" response)))
        (do
          (assert-true (contains? result "142"))
          (assert-true (contains? result "Revenue"))
          (assert-false (contains? result "Loading")))))))

(defsuite
  "swap:lazy-load"
  (deftest
    "innerHTML replaces placeholder"
    (reset-mocks!)
    (let
      ((page "(div :id \"lazy-target\" (p \"Loading...\"))")
        (response (run-handler handler:ex-lazy)))
      (let
        ((result (sx-swap page "innerHTML" "lazy-target" response)))
        (do
          (assert-true (contains? result "~examples/lazy-result"))
          (assert-true (contains? result "12:00:00"))
          (assert-false (contains? result "Loading")))))))

(defsuite
  "swap:dialog"
  (deftest
    "innerHTML opens modal"
    (reset-mocks!)
    (let
      ((page "(div :id \"dialog-target\")")
        (response (run-handler handler:ex-dialog)))
      (let
        ((result (sx-swap page "innerHTML" "dialog-target" response)))
        (do
          (assert-true (contains? result "~examples/dialog-modal"))
          (assert-true (contains? result "Confirm Action"))))))
  (deftest
    "innerHTML closes modal"
    (reset-mocks!)
    (let
      ((page "(div :id \"dialog-target\" (~examples/dialog-modal))")
        (response (run-handler handler:ex-dialog-close)))
      (let
        ((result (sx-swap page "innerHTML" "dialog-target" response)))
        (assert-false (contains? result "dialog-modal"))))))

(defsuite
  "swap:keyboard"
  (deftest
    "dispatches action for known key"
    (reset-mocks!)
    (set! _mock-args {:key "s"})
    (let
      ((page "(div :id \"kbd-result\" (p \"Press a key\"))")
        (response (run-handler handler:ex-keyboard)))
      (let
        ((result (sx-swap page "innerHTML" "kbd-result" response)))
        (do
          (assert-true (contains? result "Search panel activated"))
          (assert-false (contains? result "Press a key"))))))
  (deftest
    "shows unknown for unmapped key"
    (reset-mocks!)
    (set! _mock-args {:key "z"})
    (let
      ((page "(div :id \"kbd-result\")")
        (response (run-handler handler:ex-keyboard)))
      (let
        ((result (sx-swap page "innerHTML" "kbd-result" response)))
        (assert-true (contains? result "Unknown key: z"))))))

(defsuite
  "swap:validate"
  (deftest
    "empty email shows required"
    (reset-mocks!)
    (set! _mock-args {:email ""})
    (let
      ((page "(div :id \"validate-result\")")
        (response (run-handler handler:ex-validate)))
      (let
        ((result (sx-swap page "innerHTML" "validate-result" response)))
        (assert-true (contains? result "Email is required")))))
  (deftest
    "missing @ shows format error"
    (reset-mocks!)
    (set! _mock-args {:email "bad"})
    (let
      ((page "(div :id \"validate-result\")")
        (response (run-handler handler:ex-validate)))
      (let
        ((result (sx-swap page "innerHTML" "validate-result" response)))
        (assert-true (contains? result "Invalid email format")))))
  (deftest
    "valid email shows ok"
    (reset-mocks!)
    (set! _mock-args {:email "new@example.com"})
    (let
      ((page "(div :id \"validate-result\")")
        (response (run-handler handler:ex-validate)))
      (let
        ((result (sx-swap page "innerHTML" "validate-result" response)))
        (assert-true (contains? result "~examples/validation-ok"))))))

(defsuite
  "swap:validate-submit"
  (deftest
    "valid email submits"
    (reset-mocks!)
    (set! _mock-form {:email "hi@example.com"})
    (let
      ((page "(div :id \"validate-form-result\")")
        (response (run-handler handler:ex-validate-submit)))
      (let
        ((result (sx-swap page "innerHTML" "validate-form-result" response)))
        (assert-true (contains? result "hi@example.com")))))
  (deftest
    "empty email rejects"
    (reset-mocks!)
    (set! _mock-form {:email ""})
    (let
      ((page "(div :id \"validate-form-result\")")
        (response (run-handler handler:ex-validate-submit)))
      (let
        ((result (sx-swap page "innerHTML" "validate-form-result" response)))
        (assert-true (contains? result "valid email"))))))

(defsuite
  "swap:dependent-select"
  (deftest
    "returns options for category"
    (reset-mocks!)
    (set! _mock-args {:category "Languages"})
    (let
      ((page "(select :id \"values-result\")")
        (response (run-handler handler:ex-values)))
      (let
        ((result (sx-swap page "innerHTML" "values-result" response)))
        (do
          (assert-true (contains? result "Python"))
          (assert-true (contains? result "Rust"))))))
  (deftest
    "empty category returns no items"
    (reset-mocks!)
    (set! _mock-args {:category "Nonexistent"})
    (let
      ((page "(select :id \"values-result\")")
        (response (run-handler handler:ex-values)))
      (let
        ((result (sx-swap page "innerHTML" "values-result" response)))
        (assert-true (contains? result "No items"))))))

(defsuite
  "swap:form-reset"
  (deftest
    "echoes submitted message"
    (reset-mocks!)
    (set! _mock-form {:message "Hello world"})
    (let
      ((page "(div :id \"reset-result\")")
        (response (run-handler handler:ex-reset-submit)))
      (let
        ((result (sx-swap page "innerHTML" "reset-result" response)))
        (assert-true (contains? result "Hello world"))))))

(defsuite
  "swap:swap-modes-log"
  (deftest
    "beforeend appends log entry with OOB counter"
    (reset-mocks!)
    (set! _mock-args {:mode "beforeend"})
    (let
      ((page "(div (div :id \"swap-log\") (span :id \"swap-counter\" \"Count: 0\"))")
        (response (run-handler handler:ex-swap-log)))
      (let
        ((result (apply-response page response "beforeend" "swap-log")))
        (do
          (assert-true (contains? result "beforeend"))
          (assert-true (contains? result "Count: 1")))))))

(defsuite
  "swap:json-echo"
  (deftest
    "echoes content type and body"
    (reset-mocks!)
    (set! _mock-body "{\"key\":\"val\"}")
    (set! _mock-content-type "application/json")
    (let
      ((page "(div :id \"json-result\")")
        (response (run-handler handler:ex-json-echo)))
      (let
        ((result (sx-swap page "innerHTML" "json-result" response)))
        (do
          (assert-true (contains? result "application/json"))
          (assert-true (contains? result "~examples/json-result")))))))

(defsuite
  "swap:retry"
  (deftest
    "first two calls return empty (503)"
    (reset-mocks!)
    (let
      ((r1 (run-handler handler:ex-flaky)))
      (let
        ((r2 (run-handler handler:ex-flaky)))
        (do (assert-equal r1 "") (assert-equal r2 "")))))
  (deftest
    "third call succeeds"
    (reset-mocks!)
    (let
      ((r1 (run-handler handler:ex-flaky)))
      (let
        ((r2 (run-handler handler:ex-flaky)))
        (let
          ((r3 (run-handler handler:ex-flaky)))
          (let
            ((page "(div :id \"retry-result\" (p \"Retrying...\"))")
              (result (sx-swap page "innerHTML" "retry-result" r3)))
            (do
              (assert-true (contains? result "~examples/retry-result"))
              (assert-true (contains? result "Success"))
              (assert-false (contains? result "Retrying")))))))))

(defcomp
  ~examples/anim-result
  (&key color time)
  (div :class color (p (str "Color: " color)) (p (str "Time: " time))))

(defsuite
  "swap:animate"
  (deftest
    "returns color and timestamp"
    (reset-mocks!)
    (let
      ((page "(div :id \"anim-result\")")
        (response (run-handler handler:ex-animate)))
      (let
        ((result (str (sx-swap page "innerHTML" "anim-result" response))))
        (do
          (assert-true (string-contains? result "anim-result"))
          (assert-true (string-contains? result "12:00:00")))))))

(defsuite
  "swap:inline-edit"
  (deftest
    "edit form loads current value"
    (reset-mocks!)
    (set! _mock-args {:value "Hello" :id "42"})
    (let
      ((page "(div :id \"edit-target\" (span \"Hello\"))")
        (response (run-handler handler:ex-edit-form)))
      (let
        ((result (sx-swap page "innerHTML" "edit-target" response)))
        (assert-true (contains? result "~examples/inline-edit-form")))))
  (deftest
    "save returns updated view"
    (reset-mocks!)
    (set! _mock-form {:value "Updated"})
    (let
      ((page "(div :id \"edit-target\" (input))")
        (response (run-handler handler:ex-edit-save)))
      (let
        ((result (sx-swap page "innerHTML" "edit-target" response)))
        (assert-true (contains? result "Updated")))))
  (deftest
    "cancel restores original"
    (reset-mocks!)
    (set! _mock-args {:value "Original" :id "42"})
    (let
      ((page "(div :id \"edit-target\" (input))")
        (response (run-handler handler:ex-edit-cancel)))
      (let
        ((result (sx-swap page "innerHTML" "edit-target" response)))
        (assert-true (contains? result "Original"))))))

(defsuite
  "swap:row-editing"
  (deftest
    "form loads row data"
    (reset-mocks!)
    (set! _mock-args {:row-id "r1"})
    (set! _mock-state {:ex-row-r1 {:stock "5" :id "r1" :price "$10" :name "Widget"}})
    (let
      ((page "(tr :id \"row-r1\" (td \"Widget\"))")
        (response (run-handler handler:ex-editrow-form)))
      (let
        ((result (sx-swap page "outerHTML" "row-r1" response)))
        (do
          (assert-true (contains? result "~examples/edit-row-form"))
          (assert-true (contains? result "Widget"))))))
  (deftest
    "save stores and returns view"
    (reset-mocks!)
    (set! _mock-form {:id "r1" :price "$20" :name "Gadget"})
    (let
      ((page "(tr :id \"row-r1\" (input))")
        (response (run-handler handler:ex-editrow-save)))
      (let
        ((result (sx-swap page "outerHTML" "row-r1" response)))
        (assert-true (contains? result "Gadget"))))))

(defsuite
  "swap:profile-editing"
  (deftest
    "edit loads profile fields"
    (reset-mocks!)
    (set! _mock-state {:ex-profile {:email "ada@example.com" :role "Engineer" :name "Ada"}})
    (let
      ((page "(div :id \"profile-target\" (p \"Ada\"))")
        (response (run-handler handler:ex-putpatch-edit-all)))
      (let
        ((result (sx-swap page "innerHTML" "profile-target" response)))
        (assert-true (contains? result "~examples/pp-form-full")))))
  (deftest
    "put saves and returns view"
    (reset-mocks!)
    (set! _mock-form {:email "grace@example.com" :role "Captain" :name "Grace"})
    (let
      ((page "(div :id \"profile-target\" (form))")
        (response (run-handler handler:ex-putpatch)))
      (let
        ((result (sx-swap page "innerHTML" "profile-target" response)))
        (assert-true (contains? result "Grace"))))))

(defsuite
  "swap:tabs"
  (deftest
    "returns tab content with OOB buttons"
    (reset-mocks!)
    (set! _mock-args {:tab "tab1"})
    (let
      ((page "(div (div :id \"tab-content\" (p \"old\")) (div :id \"tab-buttons\" (button \"old\")))")
        (response (run-handler handler:ex-tabs)))
      (let
        ((result (apply-response page response "innerHTML" "tab-content")))
        (do
          (assert-true (contains? result "~examples/tab-btn"))
          (assert-false (contains? result "\"old\"")))))))

(defsuite
  "swap:loading-indicator"
  (deftest
    "slow handler returns content after mock sleep"
    (reset-mocks!)
    (let
      ((page "(div :id \"loading-result\" (p \"Loading...\"))")
        (response (run-handler handler:ex-slow)))
      (let
        ((result (sx-swap page "innerHTML" "loading-result" response)))
        (do
          (assert-true (contains? result "~examples/loading-result"))
          (assert-true (contains? result "12:00:00"))
          (assert-false (contains? result "Loading")))))))

(defsuite
  "swap:bulk-operations"
  (deftest
    "activates selected users"
    (reset-mocks!)
    (set! _mock-args {:action "activate"})
    (set! _mock-form {:ids (list "u1" "u2")})
    (set! _mock-state {:ex-bulk-u2 {:status "inactive" :id "u2" :name "Bob"} :ex-bulk-u1 {:status "inactive" :id "u1" :name "Alice"}})
    (let
      ((page "(tbody :id \"bulk-rows\" (tr \"old\"))")
        (response (run-handler handler:ex-bulk)))
      (let
        ((result (sx-swap page "innerHTML" "bulk-rows" response)))
        (assert-true (contains? result "~examples/bulk-row"))))))

(defsuite
  "swap:dedup-search"
  (deftest
    "returns search result"
    (reset-mocks!)
    (set! _mock-args {:q "test"})
    (let
      ((page "(div :id \"search-result\" (p \"Searching...\"))")
        (response (run-handler handler:ex-slow-search)))
      (let
        ((result (sx-swap page "innerHTML" "search-result" response)))
        (do
          (assert-true (contains? result "~examples/sync-result"))
          (assert-false (contains? result "Searching")))))))

(defsuite
  "swap:popstate-oob-nav"
  (deftest
    "aser preserves sx-swap-oob attribute in OOB elements"
    (let
      ((src (quote (<> (div :id "sx-nav" :sx-swap-oob "innerHTML" (span "Updated Nav")) (div :id "sx-content" (p "Page content"))))))
      (let
        ((result (serialize (aser src))))
        (assert-true (contains? result "sx-swap-oob"))
        (assert-true (contains? result "innerHTML"))
        (assert-true (contains? result "sx-nav"))
        (assert-true (contains? result "Updated Nav"))
        (assert-true (contains? result "Page content")))))
  (deftest
    "aser OOB response preserves both targets"
    (let
      ((src (quote (<> (div :id "sx-nav" :sx-swap-oob "innerHTML" (span "Nav A")) (div :id "sidebar" :sx-swap-oob "innerHTML" (span "Sidebar B")) (div :id "sx-content" (p "Main"))))))
      (let
        ((result (serialize (aser src))))
        (assert-true (contains? result "sx-nav"))
        (assert-true (contains? result "sidebar"))
        (assert-true (contains? result "Nav A"))
        (assert-true (contains? result "Sidebar B"))
        (assert-true (contains? result "Main"))))))
