(define
  request-fields
  (quote
    ((:verb "Symbol — the action to perform (required)")
      (:path "String — resource path (required)")
      (:headers "Dict — structured request metadata (optional)")
      (:cookies "Dict — client state, values can be any SX type (optional)")
      (:params "Dict — query parameters as typed values (optional)")
      (:capabilities "List — capabilities this request requires (optional)")
      (:body "Any SX value — request payload (optional)"))))

(define
  response-fields
  (quote
    ((:status "Symbol or condition — result status (required)")
      (:headers "Dict — structured response metadata (optional)")
      (:set-cookie
        "Dict — cookies to set, values are dicts with :value :max-age :path (optional)")
      (:body "Any SX value — response payload (optional)")
      (:stream "Boolean — if true, body is a sequence of chunks (optional)"))))

(define
  core-verbs
  (quote
    ((navigate "Retrieve a page for display — analogous to GET for documents")
      (fetch "Retrieve data — analogous to GET for APIs")
      (query "Structured query — body contains a query expression")
      (mutate "Change state — analogous to POST/PUT/PATCH")
      (create "Create a new resource — analogous to POST")
      (delete "Remove a resource — analogous to DELETE")
      (subscribe "Open a streaming channel for real-time updates")
      (inspect "Retrieve metadata about a resource (capabilities, schema)")
      (ping "Liveness check — server responds with (response :status ok)"))))

(define
  standard-headers
  (quote
    ((:accept "List of acceptable response types")
      (:language "String or list — preferred languages")
      (:if-match "String — content hash for conditional requests")
      (:capabilities "List — capabilities the client holds")
      (:origin "String — requesting origin for CORS-like checks")
      (:content-type "String — always text/sx in pure SXTP")
      (:content-hash "String — SHA3-256 of the body expression")
      (:cache "Symbol — :immutable, :revalidate, :none")
      (:vary "List of header keys that affect caching")
      (:link "Dict — related resources"))))

(define
  cookie-options
  (quote
    ((:value "Any SX value — the cookie payload (required)")
      (:max-age "Number — seconds until expiry (optional)")
      (:path "String — path scope (optional, default /)")
      (:domain "String — domain scope (optional)")
      (:secure "Boolean — require secure transport (optional)")
      (:same-site "Symbol — :strict, :lax, or :none (optional)")
      (:delete "Boolean — if true, remove this cookie (optional)"))))

(define
  status-symbols
  (quote
    ((ok "Success — body contains the result")
      (created "Resource created — body contains the new resource")
      (accepted "Request accepted for async processing")
      (no-content "Success with no body")
      (redirect "See :headers :location for target")
      (not-modified "Cached version is current based on :if-match")
      (error "General error — see :body for condition")
      (not-found "Resource does not exist")
      (forbidden "Insufficient capabilities")
      (invalid "Malformed request or invalid params")
      (conflict "State conflict — concurrent edit")
      (unavailable "Service temporarily unavailable"))))

(define
  condition-fields
  (quote
    ((:type "Symbol — condition type (required)")
      (:message "String — human-readable description (optional)")
      (:path "String — resource that caused the error (optional)")
      (:retry "Boolean — whether retrying may succeed (optional)")
      (:detail "Any SX value — domain-specific detail (optional)"))))

(define
  chunk-fields
  (quote
    ((:seq "Number — sequence index for ordered chunks")
      (:body "Any SX value — the chunk content")
      (:done "Boolean — signals end of stream"))))

(define
  event-fields
  (quote
    ((:type "Symbol — event type (required)")
      (:id "String — event or resource identifier (optional)")
      (:body "Any SX value — event payload (optional)")
      (:time "Number — unix timestamp (optional)"))))

;; ── patch (DOM fragment patch — borrowed from Datastar) ───────────
;; A server-driven instruction to morph a region of the client DOM.
;; Subsumes HTMX swap modes; the :body is an SX subtree that the client
;; renders to DOM nodes before applying the mode at the target.
(define
  patch-fields
  (quote
    ((:target "String — CSS selector for the element to patch (required)")
      (:mode "Symbol — patch mode (optional, default outer)")
      (:body "SX tree — the new content (omitted for mode remove)")
      (:transition "Boolean — use a view transition (optional, default false)"))))

(define
  patch-modes
  (quote
    ((outer "Replace the target's outerHTML (default; the morph target)")
      (inner "Replace the target's innerHTML, preserving the wrapper")
      (replace "Hard-replace without morphing (no diff, plain swap)")
      (prepend "Insert the body as the target's first child")
      (append "Insert the body as the target's last child")
      (before "Insert the body before the target")
      (after "Insert the body after the target")
      (remove "Detach the target; :body MUST be absent"))))

;; ── signals (reactive state patch — borrowed from Datastar) ──────
;; A server-driven update to client-side reactive signals. :values is a
;; dict of signal-name -> new-value; setting a value to nil REMOVES the
;; signal. With :only-if-missing true, existing signals are not touched
;; (use this to lazily initialise signal state without clobbering).
(define
  signals-fields
  (quote
    ((:values "Dict — signal-name -> new-value (required)")
      (:only-if-missing
        "Boolean — only set signals that don't yet exist (optional, default false)"))))

(define
  example-navigate
  (quote
    ((request :verb navigate :path "/geography/capabilities" :headers {:host "sx.rose-ash.com" :accept "text/sx"})
      (response
        :status ok
        :headers {:content-type "text/sx" :content-hash "sha3-9f2a"}
        :body (page
          :title "Capabilities"
          (h1 "Geography Capabilities")
          (~capability-list :domain "geography"))))))

(define
  example-query
  (quote
    ((request :verb query :path "/events" :capabilities (fetch db:read) :params {:after "2026-03-01" :limit 10} :body (filter (events) (fn (e) (> (:attendees e) 50))))
      (response
        :status ok
        :headers {:cache :revalidate}
        :body ((event :id "evt-42" :title "Jazz Night" :attendees 87)
          (event :id "evt-55" :title "Art Walk" :attendees 120))))))

(define
  example-mutate
  (quote
    ((request :verb create :path "/blog/posts" :capabilities (mutate blog:publish) :cookies {:session "tok_abc123"} :body {:tags ("protocol" "sx" "web") :body (article (h1 "SXTP") (p "Everything is SX.")) :title "SXTP Protocol"})
      (response :status created :headers {:location "/blog/posts/sxtp-protocol" :content-hash "sha3-ff01"} :body {:created-at 1711612800 :id "post-789" :path "/blog/posts/sxtp-protocol"}))))

(define
  example-subscribe
  (quote
    ((request :verb subscribe :path "/events/live" :capabilities (fetch) :headers {:host "events.rose-ash.com"})
      (response :status ok :stream true)
      (event
        :type new-event
        :id "evt-99"
        :body (div :class "event-card" (h3 "Poetry Slam")))
      (event :type heartbeat :time 1711612860))))

(define
  example-error
  (quote
    ((request :verb fetch :path "/blog/nonexistent")
      (response
        :status not-found
        :body (condition
          :type resource-not-found
          :path "/blog/nonexistent"
          :message "No such post"
          :retry false)))))

;; A streaming response intermixing patch + signals: the server pushes
;; DOM updates AND signal updates over the same channel. The client
;; dispatches each message by its head symbol; ordering is preserved.
(define
  example-patch-stream
  (quote
    ((request :verb subscribe :path "/cart/live" :capabilities (fetch))
      (response :status ok :stream true)
      (signals :values {:cart/count 3 :cart/loading false})
      (patch
        :target "#cart-mini"
        :mode outer
        :body (~cart-mini :count 3 :total 47.50))
      (patch :target "#flash" :mode inner :body (p "Item added."))
      (signals :values {:cart/loading true})
      (patch :target "#cart-loading-spinner" :mode remove))))

(define
  example-inspect
  (quote
    ((request :verb inspect :path "/cart/checkout")
      (response :status ok :body {:available-verbs (inspect mutate) :params-schema {:payment-method "symbol" :shipping-address "dict"} :required-capabilities (mutate cart:checkout)}))))
