;; lib/dream/json.sx — Dream-on-SX JSON encode/parse (pure SX).
;; The host JSON primitives live in the ocaml-on-sx runtime, not the base env, so
;; Dream ships its own. Depends on types.sx. (number? is unreliable in this env —
;; type-of "number" is used instead.)

;; ── encoding ───────────────────────────────────────────────────────
(define
  dr/json-escape
  (fn
    (s)
    (replace
      (replace
        (replace (replace (replace s "\\" "\\\\") "\"" "\\\"") "\n" "\\n")
        "\r"
        "\\r")
      "\t"
      "\\t")))
(define dr/json-quote (fn (s) (str "\"" (dr/json-escape s) "\"")))

(define
  dream-json-encode
  (fn
    (v)
    (cond
      ((nil? v) "null")
      ((boolean? v) (if v "true" "false"))
      ((= (type-of v) "number") (str v))
      ((string? v) (dr/json-quote v))
      ((list? v) (str "[" (join "," (map dream-json-encode v)) "]"))
      ((dict? v)
        (str
          "{"
          (join
            ","
            (map
              (fn
                (k)
                (str (dr/json-quote k) ":" (dream-json-encode (get v k))))
              (keys v)))
          "}"))
      (else (dr/json-quote (str v))))))

;; ── parsing (recursive descent; returns {:val :pos}) ───────────────
(define
  dr/json-space?
  (fn (c) (or (= c " ") (= c "\n") (= c "\r") (= c "\t"))))
(define
  dr/json-ws
  (fn
    (s i)
    (if
      (and (< i (string-length s)) (dr/json-space? (char-at s i)))
      (dr/json-ws s (+ i 1))
      i)))

(define
  dr/json-digit?
  (fn
    (c)
    (let ((n (char-code c))) (and (>= n 48) (<= n 57)))))
(define
  dr/json-num-char?
  (fn
    (c)
    (or
      (dr/json-digit? c)
      (= c "-")
      (= c "+")
      (= c ".")
      (= c "e")
      (= c "E"))))
(define
  dr/json-num-end
  (fn
    (s i)
    (if
      (and (< i (string-length s)) (dr/json-num-char? (char-at s i)))
      (dr/json-num-end s (+ i 1))
      i)))
(define
  dr/json-to-number
  (fn
    (str-val)
    (if
      (or
        (contains? str-val ".")
        (contains? str-val "e")
        (contains? str-val "E"))
      (parse-float str-val)
      (parse-int str-val))))

(define
  dr/json-str
  (fn
    (s i acc)
    (let
      ((c (char-at s i)))
      (cond
        ((= c "\"") {:val acc :pos (+ i 1)})
        ((= c "\\")
          (let
            ((e (char-at s (+ i 1))))
            (cond
              ((= e "n") (dr/json-str s (+ i 2) (str acc "\n")))
              ((= e "r") (dr/json-str s (+ i 2) (str acc "\r")))
              ((= e "t") (dr/json-str s (+ i 2) (str acc "\t")))
              (else (dr/json-str s (+ i 2) (str acc e))))))
        (else (dr/json-str s (+ i 1) (str acc c)))))))

(define
  dr/json-num
  (fn (s i) (let ((j (dr/json-num-end s i))) {:val (dr/json-to-number (substr s i (- j i))) :pos j})))

(define
  dr/json-arr
  (fn
    (s i acc)
    (let
      ((i (dr/json-ws s i)))
      (if
        (= (char-at s i) "]")
        {:val acc :pos (+ i 1)}
        (let
          ((r (dr/json-val s i)))
          (let
            ((i2 (dr/json-ws s (get r :pos))))
            (if
              (= (char-at s i2) ",")
              (dr/json-arr
                s
                (+ i2 1)
                (concat acc (list (get r :val))))
              {:val (concat acc (list (get r :val))) :pos (+ i2 1)})))))))

(define
  dr/json-obj
  (fn
    (s i acc)
    (let
      ((i (dr/json-ws s i)))
      (if
        (= (char-at s i) "}")
        {:val acc :pos (+ i 1)}
        (let
          ((kr (dr/json-str s (+ i 1) "")))
          (let
            ((i2 (dr/json-ws s (get kr :pos))))
            (let
              ((vr (dr/json-val s (+ i2 1))))
              (let
                ((i3 (dr/json-ws s (get vr :pos))))
                (if
                  (= (char-at s i3) ",")
                  (dr/json-obj
                    s
                    (+ i3 1)
                    (assoc acc (get kr :val) (get vr :val)))
                  {:val (assoc acc (get kr :val) (get vr :val)) :pos (+ i3 1)})))))))))

(define
  dr/json-val
  (fn
    (s i)
    (let
      ((i (dr/json-ws s i)))
      (let
        ((c (char-at s i)))
        (cond
          ((= c "{") (dr/json-obj s (+ i 1) {}))
          ((= c "[") (dr/json-arr s (+ i 1) (list)))
          ((= c "\"") (dr/json-str s (+ i 1) ""))
          ((= c "t") {:val true :pos (+ i 4)})
          ((= c "f") {:val false :pos (+ i 5)})
          ((= c "n") {:val nil :pos (+ i 4)})
          (else (dr/json-num s i)))))))

(define dream-json-parse (fn (s) (get (dr/json-val s 0) :val)))

;; ── responses ──────────────────────────────────────────────────────
;; encode a value into a JSON response (dream-json takes a raw string body)
(define dream-json-value (fn (v) (dream-json (dream-json-encode v))))
;; read + parse the request body as JSON
(define dream-json-body (fn (req) (dream-json-parse (dream-body req))))
