;; lib/js/runtime.sx — JS semantics shims
;;
;; Coercions, abstract equality, arithmetic with JS rules, and the
;; global object live here. Transpiled code (from lib/js/transpile.sx)
;; compiles to calls into these helpers.
;;
;; Phase 4 status: core coercions, arithmetic, comparison, equality,
;; property access, `console.log`, Math shim wired up. Enough to make
;; the expression-level test262 slice green.

;; ── JS value sentinels ────────────────────────────────────────────

;; JS `undefined` — we represent it as a distinct keyword so it
;; survives round-trips through the evaluator without colliding with
;; SX `nil` (which maps to JS `null`).
(define js-nan-value (fn () (/ 0 0)))

(define js-infinity-value (fn () (/ 1 0)))

;; ── Type predicates ───────────────────────────────────────────────

(define js-max-value-approx (fn () (js-max-value-loop 1 2000)))

;; ── Boolean coercion (ToBoolean) ──────────────────────────────────

(define js-function-global {:__callable__ (fn (&rest args) (error "TypeError: Function constructor not supported")) :prototype {:call (fn (&rest args) :js-undefined) :length 0 :bind (fn (&rest args) (fn () :js-undefined)) :toString (fn () "function () { [native code] }") :apply (fn (&rest args) :js-undefined) :name ""}})

;; ── Numeric coercion (ToNumber) ───────────────────────────────────

(dict-set!
  (get js-function-global "prototype")
  "isPrototypeOf"
  (fn (x) (js-function? x)))

;; Parse a JS-style string to a number. For the slice we just delegate
;; to SX's number parser via `str->num`/`parse-number`. Empty string → 0
;; per JS (technically ToNumber("") === 0).
(define
  js-global-eval
  (fn (&rest args) (if (empty? args) :js-undefined (nth args 0))))

;; Safe number-parser. Tries to call an SX primitive that can parse
;; strings to numbers; on failure returns 0 (stand-in for NaN so the
;; slice doesn't crash).
(define
  js-max-value-loop
  (fn
    (cur steps)
    (if
      (<= steps 0)
      cur
      (let
        ((next (* cur 2)))
        (if
          (= next (js-infinity-value))
          cur
          (js-max-value-loop next (- steps 1)))))))

;; Minimal string->number for the slice. Handles integers, negatives,
;; and simple decimals. Returns 0 on malformed input.
(define js-undefined :js-undefined)

(define js-undefined? (fn (v) (= v :js-undefined)))

(define __js_this_cell__ (dict))

(define
  js-this
  (fn
    ()
    (if
      (dict-has? __js_this_cell__ "this")
      (get __js_this_cell__ "this")
      :js-undefined)))

(define js-this-set! (fn (v) (dict-set! __js_this_cell__ "this" v)))

(define
  js-call-with-this
  (fn
    (recv fn-val args)
    (let
      ((saved (js-this)))
      (begin
        (js-this-set! recv)
        (let
          ((result (js-apply-fn fn-val args)))
          (begin (js-this-set! saved) result))))))

(define
  js-function-method?
  (fn
    (name)
    (or
      (= name "call")
      (= name "apply")
      (= name "bind")
      (= name "toString")
      (= name "name")
      (= name "length"))))

;; parse a decimal number from a trimmed non-empty string.
;;   s      — source
;;   i      — cursor
;;   acc    — integer part so far (or total for decimals)
;;   sign   — 1 or -1
;;   frac?  — are we past the decimal point
;;   fdiv   — divisor used to scale fraction digits (only if frac?)
(define
  js-fn-length
  (fn
    (f)
    (let
      ((t (type-of f)))
      (cond
        ((= t "lambda") (js-count-real-params (lambda-params f)))
        ((= t "function") 0)
        ((= t "component") 0)
        ((and (= t "dict") (contains? (keys f) "__callable__"))
          (js-fn-length (get f "__callable__")))
        (else 0)))))

(define
  js-extract-fn-name
  (fn (f) (let ((raw (inspect f))) (js-strip-fn-name raw 0 (len raw)))))

(define
  js-strip-fn-name
  (fn
    (s i n)
    (let
      ((start (if (and (< i n) (= (char-at s i) "<")) (+ i 1) i)))
      (js-strip-fn-name-end s start n))))

;; ── String coercion (ToString) ────────────────────────────────────

(define
  js-strip-fn-name-end
  (fn
    (s start n)
    (let
      ((end (js-find-paren-or-space s start n)))
      (let ((name (js-string-slice s start end))) (js-unmap-fn-name name)))))

(define
  js-find-paren-or-space
  (fn
    (s i n)
    (cond
      ((>= i n) n)
      ((or (= (char-at s i) "(") (= (char-at s i) " ")) i)
      (else (js-find-paren-or-space s (+ i 1) n)))))

;; ── Arithmetic (JS rules) ─────────────────────────────────────────

;; JS `+`: if either operand is a string → string concat, else numeric.
(define
  js-unmap-fn-name
  (fn
    (name)
    (cond
      ((= name "js-math-abs") "abs")
      ((= name "js-math-floor") "floor")
      ((= name "js-math-ceil") "ceil")
      ((= name "js-math-round") "round")
      ((= name "js-math-max") "max")
      ((= name "js-math-min") "min")
      ((= name "js-math-random") "random")
      ((= name "js-math-sqrt") "sqrt")
      ((= name "js-math-pow") "pow")
      ((= name "js-math-trunc") "trunc")
      ((= name "js-math-sign") "sign")
      ((= name "js-math-cbrt") "cbrt")
      ((= name "js-math-hypot") "hypot")
      ((= name "js-number-is-finite") "isFinite")
      ((= name "js-number-is-nan") "isNaN")
      ((= name "js-number-is-integer") "isInteger")
      ((= name "js-number-is-safe-integer") "isSafeInteger")
      ((= name "js-global-is-finite") "isFinite")
      ((= name "js-global-is-nan") "isNaN")
      ((= name "js-string-from-char-code") "fromCharCode")
      ((= name "js-array-is-array") "isArray")
      ((= name "js-array-of") "of")
      ((= name "js-array-from") "from")
      ((= name "js-object-keys") "keys")
      ((= name "js-object-values") "values")
      ((= name "js-object-entries") "entries")
      ((= name "js-object-assign") "assign")
      ((= name "js-object-freeze") "freeze")
      ((= name "js-object-get-prototype-of") "getPrototypeOf")
      ((= name "js-object-set-prototype-of") "setPrototypeOf")
      ((= name "js-object-create") "create")
      ((= name "js-object-define-property") "defineProperty")
      ((= name "js-object-define-properties") "defineProperties")
      ((= name "js-object-get-own-property-names") "getOwnPropertyNames")
      ((= name "js-object-get-own-property-descriptor")
        "getOwnPropertyDescriptor")
      ((= name "js-object-get-own-property-descriptors")
        "getOwnPropertyDescriptors")
      ((= name "js-object-is-extensible") "isExtensible")
      ((= name "js-object-is-frozen") "isFrozen")
      ((= name "js-object-is-sealed") "isSealed")
      ((= name "js-object-seal") "seal")
      ((= name "js-object-prevent-extensions") "preventExtensions")
      ((= name "js-object-is") "is")
      ((= name "js-object-from-entries") "fromEntries")
      ((= name "js-object-has-own") "hasOwn")
      ((= name "js-to-number") "Number")
      ((= name "js-to-boolean") "Boolean")
      (else name))))

(define
  js-count-real-params
  (fn
    (params)
    (cond
      ((empty? params) 0)
      (else
        (let
          ((first (first params)))
          (if
            (= first "&rest")
            0
            (+ 1 (js-count-real-params (rest params)))))))))

(define
  js-invoke-function-method
  (fn
    (recv key args)
    (cond
      ((= key "call")
        (let
          ((this-arg (if (< (len args) 1) :js-undefined (nth args 0)))
            (rest
              (if
                (< (len args) 1)
                (list)
                (js-list-slice args 1 (len args)))))
          (js-call-with-this this-arg recv rest)))
      ((= key "apply")
        (let
          ((this-arg (if (< (len args) 1) :js-undefined (nth args 0)))
            (arr (if (< (len args) 2) (list) (nth args 1))))
          (let
            ((rest (cond ((= arr nil) (list)) ((js-undefined? arr) (list)) ((list? arr) arr) (else (js-iterable-to-list arr)))))
            (js-call-with-this this-arg recv rest))))
      ((= key "bind")
        (let
          ((this-arg (if (< (len args) 1) :js-undefined (nth args 0)))
            (bound
              (if
                (< (len args) 1)
                (list)
                (js-list-slice args 1 (len args)))))
          (fn
            (&rest more)
            (js-call-with-this this-arg recv (js-list-concat bound more)))))
      ((= key "toString") "function () { [native code] }")
      ((= key "name") (js-extract-fn-name recv))
      ((= key "length") (js-fn-length recv))
      (else :js-undefined))))

(define
  js-invoke-function-bound
  (fn
    (recv key)
    (cond
      ((= key "call")
        (fn (&rest args) (js-invoke-function-method recv "call" args)))
      ((= key "apply")
        (fn (&rest args) (js-invoke-function-method recv "apply" args)))
      ((= key "bind")
        (fn (&rest args) (js-invoke-function-method recv "bind" args)))
      (else :js-undefined))))

(define
  js-invoke-number-method
  (fn
    (recv key args)
    (cond
      ((= key "toString")
        (let
          ((radix (if (empty? args) 10 (nth args 0))))
          (js-num-to-str-radix
            recv
            (if
              (or (= radix nil) (js-undefined? radix))
              10
              (js-to-number radix)))))
      ((= key "toFixed")
        (js-number-to-fixed
          recv
          (if (empty? args) 0 (js-to-number (nth args 0)))))
      ((= key "valueOf") recv)
      ((= key "toLocaleString") (js-to-string recv))
      ((= key "toPrecision") (js-to-string recv))
      ((= key "toExponential") (js-to-string recv))
      (else
        (error
          (str
            "TypeError: "
            (js-to-string key)
            " is not a function (on number)"))))))

(define
  js-invoke-function-objproto
  (fn
    (recv key args)
    (cond
      ((= key "hasOwnProperty")
        (let
          ((k (if (empty? args) "" (js-to-string (nth args 0)))))
          (or (= k "name") (= k "length") (= k "prototype"))))
      ((= key "toString") "function () { [native code] }")
      ((= key "valueOf") recv)
      ((= key "isPrototypeOf") false)
      ((= key "propertyIsEnumerable") false)
      ((= key "toLocaleString") "function () { [native code] }")
      (else :js-undefined))))

(define
  js-invoke-boolean-method
  (fn
    (recv key args)
    (cond
      ((= key "toString") (if recv "true" "false"))
      ((= key "valueOf") recv)
      (else
        (error
          (str
            "TypeError: "
            (js-to-string key)
            " is not a function (on boolean)"))))))

(define
  js-num-to-str-radix
  (fn
    (n radix)
    (cond
      ((and (number? n) (not (= n n))) "NaN")
      ((= n (/ 1 0)) "Infinity")
      ((= n (/ -1 0)) "-Infinity")
      ((or (= radix 10) (= radix nil) (js-undefined? radix))
        (js-to-string n))
      (else
        (let
          ((int-n (js-math-trunc n)))
          (if
            (< int-n 0)
            (str "-" (js-num-to-str-radix-rec (- 0 int-n) radix ""))
            (js-num-to-str-radix-rec int-n radix "")))))))

;; Bitwise + logical-not
(define
  js-num-to-str-radix-rec
  (fn
    (n radix acc)
    (if
      (= n 0)
      (if (= acc "") "0" acc)
      (let
        ((d (mod n radix)) (rest (js-math-trunc (/ n radix))))
        (js-num-to-str-radix-rec rest radix (str (js-digit-char d) acc))))))

(define
  js-digit-char
  (fn
    (d)
    (cond
      ((< d 10) (js-to-string d))
      (else (let ((offset (+ 97 (- d 10)))) (js-code-to-char offset))))))

;; ── Equality ──────────────────────────────────────────────────────

;; Strict equality (===): no coercion; js-undefined matches js-undefined.
(define
  js-number-to-fixed
  (fn
    (n digits)
    (cond
      ((js-number-is-nan n) "NaN")
      ((= n (js-infinity-value)) "Infinity")
      ((= n (- 0 (js-infinity-value))) "-Infinity")
      (else
        (let
          ((d (js-math-trunc digits)))
          (if
            (< d 1)
            (js-to-string (js-math-round n))
            (let
              ((scale (js-pow-int 10 d)))
              (let
                ((scaled (js-math-round (* n scale))))
                (let
                  ((abs-scaled (if (< scaled 0) (- 0 scaled) scaled))
                    (sign (if (< scaled 0) "-" "")))
                  (let
                    ((int-part (js-math-trunc (/ abs-scaled scale)))
                      (frac-part
                        (-
                          abs-scaled
                          (* (js-math-trunc (/ abs-scaled scale)) scale))))
                    (str
                      sign
                      (js-to-string int-part)
                      "."
                      (js-pad-int-str
                        (js-to-string (js-math-trunc frac-part))
                        d))))))))))))

(define
  js-pow-int
  (fn (b e) (if (<= e 0) 1 (* b (js-pow-int b (- e 1))))))

;; Abstract equality (==): type coercion rules.
;; Simplified: number↔string coerce both to number; null == undefined;
;; everything else falls back to strict equality.
(define
  js-pad-int-str
  (fn (s n) (if (>= (len s) n) s (js-pad-int-str (str "0" s) n))))

(define
  js-apply-fn
  (fn
    (fn-val args)
    (let
      ((callable (if (and (dict? fn-val) (contains? (keys fn-val) "__callable__")) (get fn-val "__callable__") fn-val)))
      (cond
        ((= (len args) 0) (callable))
        ((= (len args) 1) (callable (nth args 0)))
        ((= (len args) 2) (callable (nth args 0) (nth args 1)))
        ((= (len args) 3)
          (callable (nth args 0) (nth args 1) (nth args 2)))
        ((= (len args) 4)
          (callable (nth args 0) (nth args 1) (nth args 2) (nth args 3)))
        ((= (len args) 5)
          (callable
            (nth args 0)
            (nth args 1)
            (nth args 2)
            (nth args 3)
            (nth args 4)))
        ((= (len args) 6)
          (callable
            (nth args 0)
            (nth args 1)
            (nth args 2)
            (nth args 3)
            (nth args 4)
            (nth args 5)))
        (else (apply callable args))))))

;; ── Relational comparisons ────────────────────────────────────────

;; Abstract relational comparison from ES5.
;; Numbers compare numerically; two strings compare lexicographically;
;; mixed types coerce both to numbers.
(define
  js-invoke-method
  (fn
    (recv key args)
    (cond
      ((and (js-promise? recv) (js-promise-builtin-method? key))
        (js-invoke-promise-method recv key args))
      ((js-regex? recv) (js-regex-invoke-method recv key args))
      ((number? recv) (js-invoke-number-method recv key args))
      ((boolean? recv) (js-invoke-boolean-method recv key args))
      (else
        (let
          ((m (js-get-prop recv key)))
          (cond
            ((not (js-undefined? m)) (js-call-with-this recv m args))
            ((and (js-function? recv) (js-function-method? key))
              (js-invoke-function-method recv key args))
            ((and (js-function? recv) (not (= (type-of recv) "dict")) (js-object-builtin-method? key))
              (js-invoke-function-objproto recv key args))
            ((and (= (type-of recv) "dict") (js-object-builtin-method? key))
              (js-invoke-object-method recv key args))
            (else
              (error
                (str "TypeError: " (js-to-string key) " is not a function")))))))))

(define
  js-object-builtin-method?
  (fn
    (name)
    (or
      (= name "hasOwnProperty")
      (= name "isPrototypeOf")
      (= name "propertyIsEnumerable")
      (= name "toString")
      (= name "valueOf")
      (= name "toLocaleString"))))

(define
  js-invoke-object-method
  (fn
    (recv name args)
    (cond
      ((= name "hasOwnProperty")
        (if
          (= (len args) 0)
          false
          (contains? (keys recv) (js-to-string (nth args 0)))))
      ((= name "isPrototypeOf") false)
      ((= name "propertyIsEnumerable")
        (if
          (= (len args) 0)
          false
          (contains? (keys recv) (js-to-string (nth args 0)))))
      ((= name "toString") "[object Object]")
      ((= name "valueOf") recv)
      ((= name "toLocaleString") "[object Object]")
      (else js-undefined))))

(define js-upper-case (fn (s) (js-case-loop s 0 "" true)))

(define js-lower-case (fn (s) (js-case-loop s 0 "" false)))

(define
  js-case-loop
  (fn
    (s i acc to-upper?)
    (cond
      ((>= i (len s)) acc)
      (else
        (let
          ((c (char-at s i)))
          (let
            ((cc (char-code c)))
            (let
              ((cv (cond ((and to-upper? (>= cc 97) (<= cc 122)) (js-code-to-char (- cc 32))) ((and (not to-upper?) (>= cc 65) (<= cc 90)) (js-code-to-char (+ cc 32))) (else c))))
              (js-case-loop s (+ i 1) (str acc cv) to-upper?))))))))

(define
  js-code-to-char
  (fn
    (code)
    (cond
      ((= code 65) "A")
      ((= code 66) "B")
      ((= code 67) "C")
      ((= code 68) "D")
      ((= code 69) "E")
      ((= code 70) "F")
      ((= code 71) "G")
      ((= code 72) "H")
      ((= code 73) "I")
      ((= code 74) "J")
      ((= code 75) "K")
      ((= code 76) "L")
      ((= code 77) "M")
      ((= code 78) "N")
      ((= code 79) "O")
      ((= code 80) "P")
      ((= code 81) "Q")
      ((= code 82) "R")
      ((= code 83) "S")
      ((= code 84) "T")
      ((= code 85) "U")
      ((= code 86) "V")
      ((= code 87) "W")
      ((= code 88) "X")
      ((= code 89) "Y")
      ((= code 90) "Z")
      ((= code 97) "a")
      ((= code 98) "b")
      ((= code 99) "c")
      ((= code 100) "d")
      ((= code 101) "e")
      ((= code 102) "f")
      ((= code 103) "g")
      ((= code 104) "h")
      ((= code 105) "i")
      ((= code 106) "j")
      ((= code 107) "k")
      ((= code 108) "l")
      ((= code 109) "m")
      ((= code 110) "n")
      ((= code 111) "o")
      ((= code 112) "p")
      ((= code 113) "q")
      ((= code 114) "r")
      ((= code 115) "s")
      ((= code 116) "t")
      ((= code 117) "u")
      ((= code 118) "v")
      ((= code 119) "w")
      ((= code 120) "x")
      ((= code 121) "y")
      ((= code 122) "z")
      (else ""))))

;; ── Property access ───────────────────────────────────────────────

;; obj[key] or obj.key in JS. Handles:
;;   • dicts keyed by string
;;   • lists indexed by number (incl. .length)
;;   • strings indexed by number (incl. .length)
;; Returns js-undefined if the key is absent.
(define
  js-invoke-method-dyn
  (fn (recv key args) (js-invoke-method recv key args)))

(define
  js-call-plain
  (fn
    (fn-val args)
    (cond
      ((js-undefined? fn-val)
        (error "TypeError: undefined is not a function"))
      ((and (dict? fn-val) (contains? (keys fn-val) "__callable__"))
        (js-call-with-this :js-undefined (get fn-val "__callable__") args))
      (else (js-call-with-this :js-undefined fn-val args)))))

(define
  js-new-call
  (fn
    (ctor args)
    (let
      ((obj (dict)))
      (begin
        (dict-set! obj "__proto__" (js-get-ctor-proto ctor))
        (let
          ((ret (js-call-with-this obj ctor args)))
          (if
            (and (not (js-undefined? ret)) (= (type-of ret) "dict"))
            ret
            obj))))))

;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
(define
  js-instanceof
  (fn
    (obj ctor)
    (cond
      ((not (= (type-of obj) "dict")) false)
      ((not (js-function? ctor))
        (error "TypeError: Right-hand side of instanceof is not callable"))
      (else
        (let
          ((proto (js-get-ctor-proto ctor)))
          (js-instanceof-walk obj proto))))))

;; ── Short-circuit logical ops ─────────────────────────────────────

;; `a && b` in JS: if a is truthy return b else return a. The thunk
;; form defers evaluation of b — the transpiler passes (fn () b).
(define
  js-instanceof-walk
  (fn
    (obj proto)
    (cond
      ((not (= (type-of obj) "dict")) false)
      ((not (dict-has? obj "__proto__")) false)
      (else
        (let
          ((p (get obj "__proto__")))
          (cond
            ((= p proto) true)
            ((not (= (type-of p) "dict")) false)
            (else (js-instanceof-walk p proto))))))))

(define
  js-in
  (fn
    (key obj)
    (cond
      ((not (= (type-of obj) "dict")) false)
      (else (js-in-walk obj (js-to-string key))))))

;; ── console.log ───────────────────────────────────────────────────

;; Trivial bridge. `log-info` is available on OCaml; fall back to print.
(define
  js-in-walk
  (fn
    (obj skey)
    (cond
      ((not (= (type-of obj) "dict")) false)
      ((dict-has? obj skey) true)
      ((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey))
      (else false))))

(define
  Error
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= (type-of this) "dict")
          (do
            (dict-set!
              this
              "message"
              (if (= (len args) 0) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "Error"))
          nil)
        this))))

;; ── Math object ───────────────────────────────────────────────────

(define
  TypeError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= (type-of this) "dict")
          (do
            (dict-set!
              this
              "message"
              (if (= (len args) 0) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "TypeError"))
          nil)
        this))))
(define
  RangeError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= (type-of this) "dict")
          (do
            (dict-set!
              this
              "message"
              (if (= (len args) 0) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "RangeError"))
          nil)
        this))))
(define
  SyntaxError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= (type-of this) "dict")
          (do
            (dict-set!
              this
              "message"
              (if (= (len args) 0) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "SyntaxError"))
          nil)
        this))))
(define
  ReferenceError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= (type-of this) "dict")
          (do
            (dict-set!
              this
              "message"
              (if (= (len args) 0) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "ReferenceError"))
          nil)
        this))))
(define
  URIError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= this :js-undefined)
          nil
          (do
            (dict-set!
              this
              "message"
              (if (empty? args) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "URIError")))
        this))))
(define
  EvalError
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (begin
        (if
          (= this :js-undefined)
          nil
          (do
            (dict-set!
              this
              "message"
              (if (empty? args) "" (js-to-string (nth args 0))))
            (dict-set! this "name" "EvalError")))
        this))))
(define
  js-function?
  (fn
    (v)
    (let
      ((t (type-of v)))
      (or
        (= t "lambda")
        (= t "function")
        (= t "component")
        (and (= t "dict") (contains? (keys v) "__callable__"))))))
(define __js_proto_table__ (dict))
(define __js_next_id__ (dict))   ; deterministic placeholder for tests

(dict-set! __js_next_id__ "n" 0)

;; The global object — lookup table for JS names that aren't in the
;; SX env. Transpiled idents look up locally first; globals here are a
;; fallback, but most slice programs reference `console`, `Math`,
;; `undefined` as plain symbols, which we bind as defines above.
(define
  js-get-ctor-proto
  (fn
    (ctor)
    (cond
      ((and (= (type-of ctor) "dict") (contains? (keys ctor) "prototype"))
        (get ctor "prototype"))
      (else
        (let
          ((id (js-ctor-id ctor)))
          (cond
            ((dict-has? __js_proto_table__ id) (get __js_proto_table__ id))
            (else
              (let
                ((p (dict)))
                (begin (dict-set! __js_proto_table__ id p) p)))))))))

(define
  js-reset-ctor-proto!
  (fn
    (ctor)
    (let
      ((id (js-ctor-id ctor)) (p (dict)))
      (begin (dict-set! __js_proto_table__ id p) p))))

(define
  js-set-ctor-proto!
  (fn
    (ctor proto)
    (let ((id (js-ctor-id ctor))) (dict-set! __js_proto_table__ id proto))))

(define
  js-ctor-id
  (fn
    (ctor)
    (cond
      ((and (= (type-of ctor) "dict") (dict-has? ctor "__ctor_id__"))
        (get ctor "__ctor_id__"))
      (else (inspect ctor)))))

(define
  js-typeof
  (fn
    (v)
    (cond
      ((js-undefined? v) "undefined")
      ((= v nil) "object")
      ((= (type-of v) "boolean") "boolean")
      ((= (type-of v) "number") "number")
      ((= (type-of v) "string") "string")
      ((= (type-of v) "lambda") "function")
      ((= (type-of v) "function") "function")
      ((= (type-of v) "component") "function")
      ((and (= (type-of v) "dict") (contains? (keys v) "__callable__"))
        "function")
      (else "object"))))

(define
  js-to-boolean
  (fn
    (v)
    (cond
      ((js-undefined? v) false)
      ((= v nil) false)
      ((= v false) false)
      ((= v 0) false)
      ((= v "") false)
      (else true))))

(define
  js-to-number
  (fn
    (v)
    (cond
      ((js-undefined? v) (js-nan-value))
      ((= v nil) 0)
      ((= v true) 1)
      ((= v false) 0)
      ((= (type-of v) "number") v)
      ((= (type-of v) "string") (js-string-to-number v))
      (else 0))))

(define
  js-string-to-number
  (fn
    (s)
    (let
      ((trimmed (js-trim s)))
      (cond
        ((= trimmed "") 0)
        ((= trimmed "Infinity") (js-infinity-value))
        ((= trimmed "+Infinity") (js-infinity-value))
        ((= trimmed "-Infinity") (- 0 (js-infinity-value)))
        ((js-is-numeric-string? trimmed) (js-parse-num-safe trimmed))
        (else (js-nan-value))))))

(define
  js-is-numeric-string?
  (fn
    (s)
    (if
      (js-hex-prefix? s)
      (js-is-hex-body? s 2 (len s))
      (js-is-numeric-loop s 0 false false false))))

(define
  js-is-hex-body?
  (fn
    (s i n)
    (cond
      ((>= i n) (> n 2))
      ((>= (js-hex-digit-value (char-at s i)) 0)
        (js-is-hex-body? s (+ i 1) n))
      (else false))))

(define
  js-is-numeric-loop
  (fn
    (s i sawdigit sawdot sawe)
    (cond
      ((>= i (len s)) sawdigit)
      (else
        (let
          ((c (char-at s i)))
          (cond
            ((or (= c "0") (= c "1") (= c "2") (= c "3") (= c "4") (= c "5") (= c "6") (= c "7") (= c "8") (= c "9"))
              (js-is-numeric-loop s (+ i 1) true sawdot sawe))
            ((and (= c ".") (not sawdot) (not sawe))
              (js-is-numeric-loop s (+ i 1) sawdigit true sawe))
            ((and (or (= c "e") (= c "E")) sawdigit (not sawe))
              (js-is-numeric-loop s (+ i 1) false sawdot true))
            ((and (or (= c "+") (= c "-")) (= i 0))
              (js-is-numeric-loop s (+ i 1) sawdigit sawdot sawe))
            ((and (or (= c "+") (= c "-")) sawe (= i (- (len s) 1)))
              false)
            ((and (or (= c "+") (= c "-")) sawe)
              (let
                ((prev (char-at s (- i 1))))
                (if
                  (or (= prev "e") (= prev "E"))
                  (js-is-numeric-loop s (+ i 1) sawdigit sawdot sawe)
                  false)))
            (else false)))))))

(define js-parse-num-safe (fn (s) (cond (else (js-num-from-string s)))))

(define js-find-exp-char (fn (s) (js-find-exp-char-loop s 0 (len s))))

(define
  js-find-exp-char-loop
  (fn
    (s i n)
    (cond
      ((>= i n) -1)
      ((or (= (char-at s i) "e") (= (char-at s i) "E")) i)
      (else (js-find-exp-char-loop s (+ i 1) n)))))

(define
  js-pow-int
  (fn
    (base exp)
    (cond
      ((= exp 0) 1)
      ((> exp 0) (* base (js-pow-int base (- exp 1))))
      (else (/ 1 (js-pow-int base (- 0 exp)))))))

(define
  js-hex-prefix?
  (fn
    (s)
    (and
      (>= (len s) 2)
      (= (char-at s 0) "0")
      (or (= (char-at s 1) "x") (= (char-at s 1) "X")))))

(define
  js-parse-hex
  (fn
    (s i acc)
    (cond
      ((>= i (len s)) acc)
      (else
        (let
          ((c (char-at s i)) (d (js-hex-digit-value (char-at s i))))
          (cond
            ((< d 0) (js-nan-value))
            (else (js-parse-hex s (+ i 1) (+ (* acc 16) d)))))))))

(define
  js-hex-digit-value
  (fn
    (c)
    (cond
      ((= c "0") 0)
      ((= c "1") 1)
      ((= c "2") 2)
      ((= c "3") 3)
      ((= c "4") 4)
      ((= c "5") 5)
      ((= c "6") 6)
      ((= c "7") 7)
      ((= c "8") 8)
      ((= c "9") 9)
      ((or (= c "a") (= c "A")) 10)
      ((or (= c "b") (= c "B")) 11)
      ((or (= c "c") (= c "C")) 12)
      ((or (= c "d") (= c "D")) 13)
      ((or (= c "e") (= c "E")) 14)
      ((or (= c "f") (= c "F")) 15)
      (else -1))))

(define
  js-num-from-string
  (fn
    (s)
    (let
      ((trimmed (js-trim s)))
      (cond
        ((= trimmed "") 0)
        ((js-hex-prefix? trimmed) (js-parse-hex trimmed 2 0))
        (else
          (let
            ((esplit (js-find-exp-char trimmed)))
            (if
              (>= esplit 0)
              (let
                ((mant (js-string-slice trimmed 0 esplit))
                  (expstr
                    (js-string-slice trimmed (+ esplit 1) (len trimmed))))
                (let
                  ((m (js-parse-decimal mant 0 0 1 false 0))
                    (e (js-parse-decimal expstr 0 0 1 false 0)))
                  (* m (js-pow-int 10 e))))
              (js-parse-decimal trimmed 0 0 1 false 0))))))))

(define js-trim (fn (s) (js-trim-left (js-trim-right s))))

(define
  js-trim-left
  (fn (s) (let ((n (len s))) (js-trim-left-at s n 0))))

(define
  js-trim-left-at
  (fn
    (s n i)
    (cond
      ((>= i n) "")
      ((js-is-space? (char-at s i)) (js-trim-left-at s n (+ i 1)))
      (else (substr s i n)))))

(define
  js-trim-right
  (fn (s) (let ((n (len s))) (js-trim-right-at s n))))

(define
  js-trim-right-at
  (fn
    (s n)
    (cond
      ((<= n 0) "")
      ((js-is-space? (char-at s (- n 1))) (js-trim-right-at s (- n 1)))
      (else (substr s 0 n)))))

(define
  js-is-space?
  (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))

(define
  js-parse-decimal
  (fn
    (s i acc sign frac? fdiv)
    (let
      ((n (len s)))
      (cond
        ((>= i n) (* sign (if frac? (/ acc fdiv) acc)))
        ((and (= i 0) (= (char-at s 0) "-"))
          (js-parse-decimal s 1 0 -1 false 0))
        ((and (= i 0) (= (char-at s 0) "+"))
          (js-parse-decimal s 1 0 1 false 0))
        ((= (char-at s i) ".")
          (js-parse-decimal s (+ i 1) acc sign true 1))
        ((js-is-digit? (char-at s i))
          (if
            frac?
            (js-parse-decimal
              s
              (+ i 1)
              (+ (* acc 10) (js-digit-val (char-at s i)))
              sign
              true
              (* fdiv 10))
            (js-parse-decimal
              s
              (+ i 1)
              (+ (* acc 10) (js-digit-val (char-at s i)))
              sign
              false
              0)))
        (else (* sign (if frac? (/ acc fdiv) acc)))))))

(define
  js-is-digit?
  (fn
    (c)
    (and
      (or
        (= c "0")
        (= c "1")
        (= c "2")
        (= c "3")
        (= c "4")
        (= c "5")
        (= c "6")
        (= c "7")
        (= c "8")
        (= c "9")))))

(define
  js-digit-val
  (fn
    (c)
    (cond
      ((= c "0") 0)
      ((= c "1") 1)
      ((= c "2") 2)
      ((= c "3") 3)
      ((= c "4") 4)
      ((= c "5") 5)
      ((= c "6") 6)
      ((= c "7") 7)
      ((= c "8") 8)
      ((= c "9") 9)
      (else 0))))

(define
  js-to-string
  (fn
    (v)
    (cond
      ((js-undefined? v) "undefined")
      ((= v nil) "null")
      ((= v true) "true")
      ((= v false) "false")
      ((= (type-of v) "string") v)
      ((= (type-of v) "number") (js-number-to-string v))
      (else (str v)))))

(define
  js-template-concat
  (fn (&rest parts) (js-template-concat-loop parts 0 "")))

(define
  js-template-concat-loop
  (fn
    (parts i acc)
    (if
      (>= i (len parts))
      acc
      (js-template-concat-loop
        parts
        (+ i 1)
        (str acc (js-to-string (nth parts i)))))))

(define
  js-number-to-string
  (fn
    (n)
    (cond
      ((js-number-is-nan n) "NaN")
      ((= n (js-infinity-value)) "Infinity")
      ((= n (- 0 (js-infinity-value))) "-Infinity")
      (else (js-normalize-num-str (str n))))))

(define
  js-normalize-num-str
  (fn
    (s)
    (let
      ((ei (js-find-exp-char s)))
      (if
        (< ei 0)
        s
        (let
          ((mant (js-string-slice s 0 ei))
            (expraw (js-string-slice s (+ ei 1) (len s))))
          (str mant "e" (js-strip-leading-zeros expraw)))))))

(define
  js-strip-leading-zeros
  (fn
    (s)
    (let
      ((sign-and-body (js-split-sign s)))
      (let
        ((sign (nth sign-and-body 0)) (body (nth sign-and-body 1)))
        (let
          ((stripped (js-strip-zeros-loop body 0 (len body))))
          (if (= stripped "") (str sign "0") (str sign stripped)))))))

(define
  js-split-sign
  (fn
    (s)
    (cond
      ((= s "") (list "" ""))
      ((= (char-at s 0) "-") (list "-" (js-string-slice s 1 (len s))))
      ((= (char-at s 0) "+") (list "" (js-string-slice s 1 (len s))))
      (else (list "" s)))))

(define
  js-strip-zeros-loop
  (fn
    (s i n)
    (cond
      ((>= i n) "")
      ((= (char-at s i) "0") (js-strip-zeros-loop s (+ i 1) n))
      (else (js-string-slice s i n)))))

(define
  js-add
  (fn
    (a b)
    (cond
      ((or (= (type-of a) "string") (= (type-of b) "string"))
        (str (js-to-string a) (js-to-string b)))
      (else (+ (js-to-number a) (js-to-number b))))))

(define js-sub (fn (a b) (- (js-to-number a) (js-to-number b))))

(define js-mul (fn (a b) (* (js-to-number a) (js-to-number b))))

(define js-div (fn (a b) (/ (js-to-number a) (js-to-number b))))

(define js-mod (fn (a b) (mod (js-to-number a) (js-to-number b))))

(define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b))))

(define js-neg (fn (a) (- 0 (js-to-number a))))

(define js-pos (fn (a) (js-to-number a)))

(define js-not (fn (a) (not (js-to-boolean a))))

(define js-bitnot (fn (a) (- 0 (+ (js-num-to-int (js-to-number a)) 1))))

(define
  js-strict-eq
  (fn
    (a b)
    (cond
      ((and (js-undefined? a) (js-undefined? b)) true)
      ((or (js-undefined? a) (js-undefined? b)) false)
      ((not (= (type-of a) (type-of b))) false)
      (else
        (if (or (js-number-is-nan a) (js-number-is-nan b)) false (= a b))))))

(define js-strict-neq (fn (a b) (not (js-strict-eq a b))))

(define
  js-loose-eq
  (fn
    (a b)
    (cond
      ((js-strict-eq a b) true)
      ((and (= a nil) (js-undefined? b)) true)
      ((and (js-undefined? a) (= b nil)) true)
      ((and (= (type-of a) "number") (= (type-of b) "string"))
        (= a (js-to-number b)))
      ((and (= (type-of a) "string") (= (type-of b) "number"))
        (= (js-to-number a) b))
      ((= (type-of a) "boolean") (js-loose-eq (js-to-number a) b))
      ((= (type-of b) "boolean") (js-loose-eq a (js-to-number b)))
      (else false))))

(define js-loose-neq (fn (a b) (not (js-loose-eq a b))))

(define
  js-lt
  (fn
    (a b)
    (cond
      ((and (= (type-of a) "string") (= (type-of b) "string"))
        (js-str-lt a b))
      (else (< (js-to-number a) (js-to-number b))))))

(define js-gt (fn (a b) (js-lt b a)))

(define js-le (fn (a b) (not (js-lt b a))))

(define js-ge (fn (a b) (not (js-lt a b))))

(define js-str-lt (fn (a b) (js-str-lt-at a b 0 (len a) (len b))))

(define
  js-str-lt-at
  (fn
    (a b i la lb)
    (cond
      ((and (>= i la) (>= i lb)) false)
      ((>= i la) true)
      ((>= i lb) false)
      ((< (char-code-at a i) (char-code-at b i)) true)
      ((> (char-code-at a i) (char-code-at b i)) false)
      (else (js-str-lt-at a b (+ i 1) la lb)))))

(define char-code-at (fn (s i) (char-code (char-at s i))))

(define
  js-array-method
  (fn
    (arr name)
    (cond
      ((= name "push")
        (fn
          (&rest args)
          (begin (for-each (fn (x) (append! arr x)) args) (len arr))))
      ((= name "pop")
        (fn
          ()
          (if
            (= (len arr) 0)
            js-undefined
            (let
              ((v (nth arr (- (len arr) 1))))
              (begin (pop-last! arr) v)))))
      ((= name "shift")
        (fn
          ()
          (if
            (= (len arr) 0)
            js-undefined
            (let ((v (nth arr 0))) (begin (pop-first! arr) v)))))
      ((= name "slice")
        (fn
          (&rest args)
          (let
            ((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
              (stop
                (if
                  (< (len args) 2)
                  (len arr)
                  (js-num-to-int (nth args 1)))))
            (js-list-slice arr start stop))))
      ((= name "indexOf")
        (fn
          (&rest args)
          (if
            (= (len args) 0)
            -1
            (js-list-index-of
              arr
              (nth args 0)
              (if (< (len args) 2) 0 (js-num-to-int (nth args 1)))))))
      ((= name "join")
        (fn
          (&rest args)
          (let
            ((sep (if (= (len args) 0) "," (js-to-string (nth args 0)))))
            (js-list-join arr sep))))
      ((= name "concat") (fn (&rest args) (js-list-concat arr args)))
      ((= name "map") (fn (f) (js-list-map-loop f arr 0 (list))))
      ((= name "filter") (fn (f) (js-list-filter-loop f arr 0 (list))))
      ((= name "forEach")
        (fn (f) (begin (js-list-foreach-loop f arr 0) js-undefined)))
      ((= name "reduce")
        (fn
          (&rest args)
          (cond
            ((= (len args) 1)
              (if
                (= (len arr) 0)
                (error "Reduce of empty array with no initial value")
                (js-list-reduce-loop (nth args 0) (nth arr 0) arr 1)))
            (else (js-list-reduce-loop (nth args 0) (nth args 1) arr 0)))))
      ((= name "includes")
        (fn
          (&rest args)
          (if
            (= (len args) 0)
            false
            (>= (js-list-index-of arr (nth args 0) 0) 0))))
      ((= name "find") (fn (f) (js-list-find-loop f arr 0)))
      ((= name "findIndex") (fn (f) (js-list-find-index-loop f arr 0)))
      ((= name "some") (fn (f) (js-list-some-loop f arr 0)))
      ((= name "every") (fn (f) (js-list-every-loop f arr 0)))
      ((= name "reverse")
        (fn () (js-list-reverse-loop arr (- (len arr) 1) (list))))
      ((= name "flat")
        (fn
          (&rest args)
          (let
            ((depth (if (= (len args) 0) 1 (js-num-to-int (nth args 0)))))
            (js-list-flat-loop arr depth (list)))))
      ((= name "fill")
        (fn
          (&rest args)
          (let
            ((v (if (= (len args) 0) js-undefined (nth args 0)))
              (s (if (< (len args) 2) 0 (js-num-to-int (nth args 1))))
              (e
                (if
                  (< (len args) 3)
                  (len arr)
                  (js-num-to-int (nth args 2)))))
            (js-list-fill-loop arr v s e)
            arr)))
      ((= name "sort")
        (fn
          (&rest args)
          (let
            ((cmp (if (= (len args) 0) nil (nth args 0))))
            (js-list-sort! arr cmp)
            arr)))
      ((= name "lastIndexOf")
        (fn
          (&rest args)
          (if
            (= (len args) 0)
            -1
            (js-list-last-index-of
              arr
              (nth args 0)
              (if
                (< (len args) 2)
                (- (len arr) 1)
                (js-num-to-int (nth args 1)))))))
      ((= name "at")
        (fn
          (&rest args)
          (let
            ((i (if (empty? args) 0 (js-num-to-int (nth args 0)))))
            (let
              ((idx (if (< i 0) (+ (len arr) i) i)))
              (if
                (or (< idx 0) (>= idx (len arr)))
                :js-undefined (nth arr idx))))))
      ((= name "unshift") (fn (&rest args) (+ (len arr) (len args))))
      ((= name "splice")
        (fn
          (&rest args)
          (let
            ((n (len arr))
              (start-raw (if (empty? args) 0 (js-num-to-int (nth args 0)))))
            (let
              ((start (cond ((< start-raw 0) (max 0 (+ n start-raw))) ((> start-raw n) n) (else start-raw))))
              (let
                ((delete-count (if (< (len args) 2) (- n start) (max 0 (min (- n start) (js-num-to-int (nth args 1)))))))
                (js-list-slice arr start (+ start delete-count)))))))
      ((= name "flatMap")
        (fn
          (f)
          (let
            ((mapped (js-list-map-loop f arr 0 (list))))
            (js-list-flat-loop mapped 1 (list)))))
      ((= name "findLast")
        (fn (f) (js-list-find-last-loop f arr (- (len arr) 1))))
      ((= name "findLastIndex")
        (fn (f) (js-list-find-last-index-loop f arr (- (len arr) 1))))
      ((= name "reduceRight")
        (fn
          (&rest args)
          (cond
            ((= (len args) 1)
              (if
                (= (len arr) 0)
                (error "Reduce of empty array with no initial value")
                (js-list-reduce-right-loop
                  (nth args 0)
                  (nth arr (- (len arr) 1))
                  arr
                  (- (len arr) 2))))
            (else
              (js-list-reduce-right-loop
                (nth args 0)
                (nth args 1)
                arr
                (- (len arr) 1))))))
      ((= name "toString") (fn () (js-list-join arr ",")))
      ((= name "toLocaleString") (fn () (js-list-join arr ",")))
      ((= name "keys")
        (fn
          ()
          (let
            ((result (list)))
            (begin (js-list-keys-loop arr 0 result) result))))
      ((= name "values") (fn () (js-list-slice arr 0 (len arr))))
      ((= name "entries")
        (fn
          ()
          (let
            ((result (list)))
            (begin (js-list-entries-loop arr 0 result) result))))
      ((= name "copyWithin")
        (fn
          (&rest args)
          (let
            ((n (len arr))
              (target-raw
                (if (empty? args) 0 (js-num-to-int (nth args 0))))
              (start-raw
                (if (< (len args) 2) 0 (js-num-to-int (nth args 1))))
              (end-raw
                (if
                  (< (len args) 3)
                  (len arr)
                  (js-num-to-int (nth args 2)))))
            (let
              ((target (cond ((< target-raw 0) (max 0 (+ n target-raw))) (else (min n target-raw))))
                (start
                  (cond
                    ((< start-raw 0) (max 0 (+ n start-raw)))
                    (else (min n start-raw))))
                (end
                  (cond
                    ((< end-raw 0) (max 0 (+ n end-raw)))
                    (else (min n end-raw)))))
              (begin (js-list-copy-within! arr target start end) arr)))))
      ((= name "toReversed")
        (fn () (js-list-reverse-loop arr (- (len arr) 1) (list))))
      ((= name "toSorted")
        (fn
          (&rest args)
          (let
            ((cmp (if (empty? args) nil (nth args 0)))
              (copy (js-list-slice arr 0 (len arr))))
            (begin (js-list-sort! copy cmp) copy))))
      (else js-undefined))))

(define pop-last! (fn (lst) nil))

(define pop-first! (fn (lst) nil))

(define
  js-list-slice
  (fn
    (arr start stop)
    (let
      ((n (len arr)))
      (let
        ((s (if (< start 0) (max 0 (+ n start)) (min start n)))
          (e (if (< stop 0) (max 0 (+ n stop)) (min stop n))))
        (js-list-slice-loop arr s e (list))))))

(define
  js-list-slice-loop
  (fn
    (arr i e acc)
    (cond
      ((>= i e) acc)
      (else
        (do
          (append! acc (nth arr i))
          (js-list-slice-loop arr (+ i 1) e acc))))))

(define
  js-list-index-of
  (fn
    (arr v i)
    (cond
      ((>= i (len arr)) -1)
      ((js-strict-eq (nth arr i) v) i)
      (else (js-list-index-of arr v (+ i 1))))))

(define
  js-list-join
  (fn
    (arr sep)
    (cond
      ((= (len arr) 0) "")
      (else
        (js-list-join-loop arr sep 1 (js-to-string-for-join (nth arr 0)))))))

(define
  js-to-string-for-join
  (fn
    (v)
    (cond ((js-undefined? v) "") ((= v nil) "") (else (js-to-string v)))))

(define
  js-list-join-loop
  (fn
    (arr sep i acc)
    (cond
      ((>= i (len arr)) acc)
      (else
        (js-list-join-loop
          arr
          sep
          (+ i 1)
          (str acc sep (js-to-string-for-join (nth arr i))))))))

(define
  js-list-concat
  (fn
    (arr tail)
    (let
      ((result (list)))
      (begin
        (for-each (fn (x) (append! result x)) arr)
        (for-each
          (fn
            (other)
            (cond
              ((= (type-of other) "list")
                (for-each (fn (x) (append! result x)) other))
              (else (append! result other))))
          tail)
        result))))

(define
  js-list-map-loop
  (fn
    (f arr i acc)
    (cond
      ((>= i (len arr)) acc)
      (else
        (do
          (append! acc (f (nth arr i)))
          (js-list-map-loop f arr (+ i 1) acc))))))

(define
  js-list-filter-loop
  (fn
    (f arr i acc)
    (cond
      ((>= i (len arr)) acc)
      (else
        (do
          (let
            ((v (nth arr i)))
            (if (js-to-boolean (f v)) (append! acc v) nil))
          (js-list-filter-loop f arr (+ i 1) acc))))))

(define
  js-list-foreach-loop
  (fn
    (f arr i)
    (cond
      ((>= i (len arr)) nil)
      (else (do (f (nth arr i)) (js-list-foreach-loop f arr (+ i 1)))))))

(define
  js-list-reduce-loop
  (fn
    (f acc arr i)
    (cond
      ((>= i (len arr)) acc)
      (else (js-list-reduce-loop f (f acc (nth arr i)) arr (+ i 1))))))

(define
  js-list-find-loop
  (fn
    (f arr i)
    (cond
      ((>= i (len arr)) js-undefined)
      ((js-to-boolean (f (nth arr i))) (nth arr i))
      (else (js-list-find-loop f arr (+ i 1))))))

(define
  js-list-find-index-loop
  (fn
    (f arr i)
    (cond
      ((>= i (len arr)) -1)
      ((js-to-boolean (f (nth arr i))) i)
      (else (js-list-find-index-loop f arr (+ i 1))))))

(define
  js-list-some-loop
  (fn
    (f arr i)
    (cond
      ((>= i (len arr)) false)
      ((js-to-boolean (f (nth arr i))) true)
      (else (js-list-some-loop f arr (+ i 1))))))

(define
  js-list-flat-loop
  (fn
    (arr depth acc)
    (for-each
      (fn
        (x)
        (if
          (and (list? x) (> depth 0))
          (js-list-flat-loop x (- depth 1) acc)
          (append! acc x)))
      arr)
    acc))

(define
  js-list-fill-loop
  (fn
    (arr v s e)
    (cond
      ((>= s e) nil)
      ((>= s (len arr)) nil)
      (else
        (begin (js-list-set! arr s v) (js-list-fill-loop arr v (+ s 1) e))))))

(define
  js-list-sort!
  (fn (arr cmp) (let ((n (len arr))) (js-list-sort-outer! arr cmp 0 n))))

(define
  js-list-sort-outer!
  (fn
    (arr cmp i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-list-sort-inner! arr cmp 0 (- n i 1))
          (js-list-sort-outer! arr cmp (+ i 1) n))))))

(define
  js-list-sort-inner!
  (fn
    (arr cmp i end)
    (cond
      ((>= i end) nil)
      (else
        (begin
          (let
            ((a (nth arr i)) (b (nth arr (+ i 1))))
            (let
              ((result (if (= cmp nil) (if (js-str-lt (js-to-string b) (js-to-string a)) 1 -1) (js-to-number (cmp a b)))))
              (when
                (> result 0)
                (begin (js-list-set! arr i b) (js-list-set! arr (+ i 1) a)))))
          (js-list-sort-inner! arr cmp (+ i 1) end))))))

(define
  js-list-last-index-of
  (fn
    (arr x i)
    (cond
      ((< i 0) -1)
      ((= (nth arr i) x) i)
      (else (js-list-last-index-of arr x (- i 1))))))

(define
  js-list-every-loop
  (fn
    (f arr i)
    (cond
      ((>= i (len arr)) true)
      ((not (js-to-boolean (f (nth arr i)))) false)
      (else (js-list-every-loop f arr (+ i 1))))))

(define
  js-list-reverse-loop
  (fn
    (arr i acc)
    (cond
      ((< i 0) acc)
      (else
        (begin
          (append! acc (nth arr i))
          (js-list-reverse-loop arr (- i 1) acc))))))

(define
  js-list-find-last-loop
  (fn
    (f arr i)
    (cond
      ((< i 0) :js-undefined)
      ((js-to-boolean (f (nth arr i))) (nth arr i))
      (else (js-list-find-last-loop f arr (- i 1))))))

(define
  js-list-find-last-index-loop
  (fn
    (f arr i)
    (cond
      ((< i 0) -1)
      ((js-to-boolean (f (nth arr i))) i)
      (else (js-list-find-last-index-loop f arr (- i 1))))))

(define
  js-list-reduce-right-loop
  (fn
    (f acc arr i)
    (if
      (< i 0)
      acc
      (js-list-reduce-right-loop f (f acc (nth arr i)) arr (- i 1)))))

(define
  js-list-keys-loop
  (fn
    (arr i result)
    (if
      (>= i (len arr))
      result
      (begin (append! result i) (js-list-keys-loop arr (+ i 1) result)))))

(define
  js-list-entries-loop
  (fn
    (arr i result)
    (if
      (>= i (len arr))
      result
      (begin
        (append! result (list i (nth arr i)))
        (js-list-entries-loop arr (+ i 1) result)))))

(define
  js-list-copy-within!
  (fn
    (arr target start end)
    (let
      ((snap (js-list-slice arr start end)))
      (js-list-copy-within-loop! arr target snap 0))))

(define
  js-list-copy-within-loop!
  (fn
    (arr target snap i)
    (cond
      ((>= i (len snap)) arr)
      ((>= (+ target i) (len arr)) arr)
      (else
        (begin
          (set-nth! arr (+ target i) (nth snap i))
          (js-list-copy-within-loop! arr target snap (+ i 1)))))))

(define
  js-string-repeat
  (fn
    (s n acc)
    (if (<= n 0) acc (js-string-repeat s (- n 1) (str acc s)))))

(define
  js-string-pad
  (fn
    (s target pad at-start)
    (let
      ((slen (len s)))
      (if
        (or (<= target slen) (= (len pad) 0))
        s
        (let
          ((needed (- target slen)))
          (let
            ((padding (js-string-pad-build pad needed "")))
            (if at-start (str padding s) (str s padding))))))))

(define
  js-string-pad-build
  (fn
    (pad needed acc)
    (cond
      ((<= needed 0) acc)
      ((>= (len acc) needed) (js-string-slice acc 0 needed))
      (else (js-string-pad-build pad needed (str acc pad))))))

(define
  js-string-method
  (fn
    (s name)
    (cond
      ((= name "charAt")
        (fn
          (i)
          (let
            ((idx (js-num-to-int i)))
            (if (and (>= idx 0) (< idx (len s))) (char-at s idx) ""))))
      ((= name "charCodeAt")
        (fn
          (i)
          (let
            ((idx (js-num-to-int i)))
            (if
              (and (>= idx 0) (< idx (len s)))
              (char-code (char-at s idx))
              0))))
      ((= name "indexOf")
        (fn (needle) (js-string-index-of s (js-to-string needle) 0)))
      ((= name "slice")
        (fn
          (&rest args)
          (let
            ((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
              (stop
                (if (< (len args) 2) (len s) (js-num-to-int (nth args 1)))))
            (js-string-slice s start stop))))
      ((= name "substring")
        (fn
          (&rest args)
          (let
            ((start (if (= (len args) 0) 0 (max 0 (js-num-to-int (nth args 0)))))
              (stop
                (if
                  (< (len args) 2)
                  (len s)
                  (max 0 (js-num-to-int (nth args 1))))))
            (let
              ((lo (min start stop)) (hi (max start stop)))
              (js-string-slice s lo (min hi (len s)))))))
      ((= name "toUpperCase") (fn () (js-upper-case s)))
      ((= name "toLowerCase") (fn () (js-lower-case s)))
      ((= name "split") (fn (sep) (js-string-split s (js-to-string sep))))
      ((= name "concat")
        (fn (&rest args) (js-string-concat-loop s args 0)))
      ((= name "includes")
        (fn
          (&rest args)
          (let
            ((needle (if (= (len args) 0) "" (js-to-string (nth args 0)))))
            (>= (js-string-index-of s needle 0) 0))))
      ((= name "startsWith")
        (fn
          (&rest args)
          (let
            ((needle (if (= (len args) 0) "" (js-to-string (nth args 0))))
              (start (if (< (len args) 2) 0 (js-num-to-int (nth args 1)))))
            (js-string-matches? s needle start 0))))
      ((= name "endsWith")
        (fn
          (&rest args)
          (let
            ((needle (if (= (len args) 0) "" (js-to-string (nth args 0)))))
            (let
              ((end-len (len s)) (n-len (len needle)))
              (if
                (> n-len end-len)
                false
                (js-string-matches? s needle (- end-len n-len) 0))))))
      ((= name "trim") (fn () (js-trim s)))
      ((= name "trimStart") (fn () (js-trim-left s)))
      ((= name "trimEnd") (fn () (js-trim-right s)))
      ((= name "repeat")
        (fn (n) (js-string-repeat s (js-num-to-int n) "")))
      ((= name "padStart")
        (fn
          (&rest args)
          (let
            ((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
              (pad (if (< (len args) 2) " " (js-to-string (nth args 1)))))
            (js-string-pad s target pad true))))
      ((= name "padEnd")
        (fn
          (&rest args)
          (let
            ((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
              (pad (if (< (len args) 2) " " (js-to-string (nth args 1)))))
            (js-string-pad s target pad false))))
      ((= name "toString") (fn () s))
      ((= name "valueOf") (fn () s))
      ((= name "replace")
        (fn
          (&rest args)
          (cond
            ((< (len args) 2) s)
            ((js-regex? (nth args 0))
              (let
                ((rx (nth args 0)) (repl (nth args 1)))
                (let
                  ((src (get rx "source")))
                  (let
                    ((idx (js-string-index-of (if (get rx "ignoreCase") (js-lower-case s) s) (if (get rx "ignoreCase") (js-lower-case src) src) 0)))
                    (if
                      (= idx -1)
                      s
                      (let
                        ((matched (js-string-slice s idx (+ idx (len src)))))
                        (str
                          (js-string-slice s 0 idx)
                          (if
                            (js-function? repl)
                            (repl matched)
                            (js-to-string repl))
                          (js-string-slice s (+ idx (len src)) (len s)))))))))
            (else
              (let
                ((needle (js-to-string (nth args 0))) (repl (nth args 1)))
                (let
                  ((idx (js-string-index-of s needle 0)))
                  (if
                    (= idx -1)
                    s
                    (str
                      (js-string-slice s 0 idx)
                      (if
                        (js-function? repl)
                        (repl needle)
                        (js-to-string repl))
                      (js-string-slice s (+ idx (len needle)) (len s))))))))))
      ((= name "search")
        (fn
          (&rest args)
          (cond
            ((= (len args) 0) -1)
            ((js-regex? (nth args 0))
              (let
                ((rx (nth args 0)) (src (get (nth args 0) "source")))
                (js-string-index-of
                  (if (get rx "ignoreCase") (js-lower-case s) s)
                  (if (get rx "ignoreCase") (js-lower-case src) src)
                  0)))
            (else (js-string-index-of s (js-to-string (nth args 0)) 0)))))
      ((= name "match")
        (fn
          (&rest args)
          (cond
            ((= (len args) 0) nil)
            ((js-regex? (nth args 0))
              (let
                ((rx (nth args 0)))
                (let
                  ((impl (get __js_regex_platform__ "exec")))
                  (if
                    (js-undefined? impl)
                    (js-regex-stub-exec rx s)
                    (impl rx s)))))
            (else
              (let
                ((needle (js-to-string (nth args 0))))
                (let
                  ((idx (js-string-index-of s needle 0)))
                  (if
                    (= idx -1)
                    nil
                    (let ((res (list))) (begin (append! res needle) res)))))))))
      ((= name "at")
        (fn
          (i)
          (let
            ((idx (js-num-to-int i)))
            (let
              ((actual (if (< idx 0) (+ (len s) idx) idx)))
              (if
                (or (< actual 0) (>= actual (len s)))
                :js-undefined (char-at s actual))))))
      ((= name "codePointAt")
        (fn
          (i)
          (let
            ((idx (js-num-to-int i)))
            (if
              (or (< idx 0) (>= idx (len s)))
              :js-undefined (char-code (char-at s idx))))))
      ((= name "lastIndexOf")
        (fn
          (&rest args)
          (if
            (empty? args)
            -1
            (let
              ((needle (js-to-string (nth args 0))))
              (js-string-last-index-of s needle (- (len s) (len needle)))))))
      ((= name "localeCompare")
        (fn
          (&rest args)
          (if
            (empty? args)
            0
            (let
              ((other (js-to-string (nth args 0))))
              (cond ((< s other) -1) ((> s other) 1) (else 0))))))
      ((= name "replaceAll")
        (fn
          (&rest args)
          (if
            (< (len args) 2)
            s
            (let
              ((needle-arg (nth args 0)) (repl (nth args 1)))
              (let
                ((needle (if (js-regex? needle-arg) (get needle-arg "source") (js-to-string needle-arg))))
                (js-string-replace-all
                  s
                  needle
                  (if (js-function? repl) repl (js-to-string repl))))))))
      ((= name "normalize") (fn (&rest args) s))
      ((= name "toLocaleLowerCase") (fn (&rest args) (js-lower-case s)))
      ((= name "toLocaleUpperCase") (fn (&rest args) (js-upper-case s)))
      ((= name "isWellFormed") (fn () true))
      ((= name "toWellFormed") (fn () s))
      (else js-undefined))))

(define __js_tdz_sentinel__ (dict "__tdz__" true))

(define js-tdz? (fn (v) (and (dict? v) (dict-has? v "__tdz__"))))

(define
  js-tdz-check
  (fn
    (name val)
    (if
      (js-tdz? val)
      (raise
        (TypeError (str "Cannot access '" name "' before initialization")))
      val)))

(define
  js-string-slice
  (fn
    (s start stop)
    (let
      ((n (len s)))
      (let
        ((lo (if (< start 0) (max 0 (+ n start)) (min start n)))
          (hi (if (< stop 0) (max 0 (+ n stop)) (min stop n))))
        (if (>= lo hi) "" (js-string-slice-loop s lo hi ""))))))

(define
  js-string-slice-loop
  (fn
    (s i e acc)
    (cond
      ((>= i e) acc)
      (else (js-string-slice-loop s (+ i 1) e (str acc (char-at s i)))))))

(define
  js-string-index-of
  (fn
    (s needle i)
    (cond
      ((> (+ i (len needle)) (len s)) -1)
      ((js-string-matches? s needle i 0) i)
      (else (js-string-index-of s needle (+ i 1))))))

(define
  js-string-last-index-of
  (fn
    (s needle start)
    (cond
      ((< start 0) -1)
      ((= needle "") start)
      ((js-string-matches? s needle start 0) start)
      (else (js-string-last-index-of s needle (- start 1))))))

(define
  js-string-replace-all
  (fn
    (s needle repl)
    (if
      (= needle "")
      s
      (let
        ((idx (js-string-index-of s needle 0)))
        (if
          (= idx -1)
          s
          (str
            (js-string-slice s 0 idx)
            (if (js-function? repl) (repl needle) repl)
            (js-string-replace-all
              (js-string-slice s (+ idx (len needle)) (len s))
              needle
              repl)))))))

(define
  js-string-matches?
  (fn
    (s needle si ni)
    (cond
      ((>= ni (len needle)) true)
      ((not (= (char-at s (+ si ni)) (char-at needle ni))) false)
      (else (js-string-matches? s needle si (+ ni 1))))))

(define
  js-string-split
  (fn
    (s sep)
    (cond
      ((= sep "") (js-string-split-chars s 0 (list)))
      (else (js-string-split-loop s sep 0 0 (list))))))

(define
  js-string-split-chars
  (fn
    (s i acc)
    (cond
      ((>= i (len s)) acc)
      (else
        (do
          (append! acc (char-at s i))
          (js-string-split-chars s (+ i 1) acc))))))

(define
  js-string-split-loop
  (fn
    (s sep start i acc)
    (cond
      ((> (+ i (len sep)) (len s))
        (do (append! acc (js-string-slice s start (len s))) acc))
      ((js-string-matches? s sep i 0)
        (do
          (append! acc (js-string-slice s start i))
          (js-string-split-loop s sep (+ i (len sep)) (+ i (len sep)) acc)))
      (else (js-string-split-loop s sep start (+ i 1) acc)))))

(define
  js-string-concat-loop
  (fn
    (acc args i)
    (cond
      ((>= i (len args)) acc)
      (else
        (js-string-concat-loop
          (str acc (js-to-string (nth args i)))
          args
          (+ i 1))))))

(begin
  (define
    js-get-prop
    (fn
      (obj key)
      (cond
        ((= obj nil) js-undefined)
        ((js-undefined? obj) js-undefined)
        ((= (type-of obj) "list")
          (cond
            ((= key "length") (len obj))
            ((= (type-of key) "number")
              (if
                (and (>= key 0) (< key (len obj)))
                (nth obj (js-num-to-int key))
                js-undefined))
            ((= key "push") (js-array-method obj "push"))
            ((= key "pop") (js-array-method obj "pop"))
            ((= key "shift") (js-array-method obj "shift"))
            ((= key "slice") (js-array-method obj "slice"))
            ((= key "indexOf") (js-array-method obj "indexOf"))
            ((= key "join") (js-array-method obj "join"))
            ((= key "concat") (js-array-method obj "concat"))
            ((= key "map") (js-array-method obj "map"))
            ((= key "filter") (js-array-method obj "filter"))
            ((= key "forEach") (js-array-method obj "forEach"))
            ((= key "reduce") (js-array-method obj "reduce"))
            ((= key "includes") (js-array-method obj "includes"))
            ((= key "find") (js-array-method obj "find"))
            ((= key "findIndex") (js-array-method obj "findIndex"))
            ((= key "some") (js-array-method obj "some"))
            ((= key "every") (js-array-method obj "every"))
            ((= key "reverse") (js-array-method obj "reverse"))
            ((= key "flat") (js-array-method obj "flat"))
            ((= key "fill") (js-array-method obj "fill"))
            ((= key "sort") (js-array-method obj "sort"))
            ((= key "lastIndexOf") (js-array-method obj "lastIndexOf"))
            ((= key "at") (js-array-method obj "at"))
            ((= key "unshift") (js-array-method obj "unshift"))
            ((= key "splice") (js-array-method obj "splice"))
            ((= key "flatMap") (js-array-method obj "flatMap"))
            ((= key "findLast") (js-array-method obj "findLast"))
            ((= key "findLastIndex") (js-array-method obj "findLastIndex"))
            ((= key "reduceRight") (js-array-method obj "reduceRight"))
            ((= key "toString") (js-array-method obj "toString"))
            ((= key "toLocaleString") (js-array-method obj "toLocaleString"))
            ((= key "keys") (js-array-method obj "keys"))
            ((= key "values") (js-array-method obj "values"))
            ((= key "entries") (js-array-method obj "entries"))
            ((= key "copyWithin") (js-array-method obj "copyWithin"))
            ((= key "toReversed") (js-array-method obj "toReversed"))
            ((= key "toSorted") (js-array-method obj "toSorted"))
            (else js-undefined)))
        ((= (type-of obj) "string")
          (cond
            ((= key "length") (len obj))
            ((= (type-of key) "number")
              (if
                (and (>= key 0) (< key (len obj)))
                (char-at obj (js-num-to-int key))
                js-undefined))
            ((= key "charAt") (js-string-method obj "charAt"))
            ((= key "charCodeAt") (js-string-method obj "charCodeAt"))
            ((= key "indexOf") (js-string-method obj "indexOf"))
            ((= key "slice") (js-string-method obj "slice"))
            ((= key "substring") (js-string-method obj "substring"))
            ((= key "toUpperCase") (js-string-method obj "toUpperCase"))
            ((= key "toLowerCase") (js-string-method obj "toLowerCase"))
            ((= key "split") (js-string-method obj "split"))
            ((= key "concat") (js-string-method obj "concat"))
            ((= key "includes") (js-string-method obj "includes"))
            ((= key "startsWith") (js-string-method obj "startsWith"))
            ((= key "endsWith") (js-string-method obj "endsWith"))
            ((= key "trim") (js-string-method obj "trim"))
            ((= key "trimStart") (js-string-method obj "trimStart"))
            ((= key "trimEnd") (js-string-method obj "trimEnd"))
            ((= key "repeat") (js-string-method obj "repeat"))
            ((= key "padStart") (js-string-method obj "padStart"))
            ((= key "padEnd") (js-string-method obj "padEnd"))
            ((= key "toString") (js-string-method obj "toString"))
            ((= key "valueOf") (js-string-method obj "valueOf"))
            ((= key "replace") (js-string-method obj "replace"))
            ((= key "search") (js-string-method obj "search"))
            ((= key "match") (js-string-method obj "match"))
            ((= key "at") (js-string-method obj "at"))
            ((= key "codePointAt") (js-string-method obj "codePointAt"))
            ((= key "lastIndexOf") (js-string-method obj "lastIndexOf"))
            ((= key "localeCompare") (js-string-method obj "localeCompare"))
            ((= key "replaceAll") (js-string-method obj "replaceAll"))
            ((= key "normalize") (js-string-method obj "normalize"))
            ((= key "toLocaleLowerCase")
              (js-string-method obj "toLocaleLowerCase"))
            ((= key "toLocaleUpperCase")
              (js-string-method obj "toLocaleUpperCase"))
            ((= key "isWellFormed") (js-string-method obj "isWellFormed"))
            ((= key "toWellFormed") (js-string-method obj "toWellFormed"))
            (else js-undefined)))
        ((= (type-of obj) "dict")
          (js-dict-get-walk obj (js-to-string key)))
        ((and (= obj Promise) (dict-has? __js_promise_statics__ (js-to-string key)))
          (get __js_promise_statics__ (js-to-string key)))
        ((and (js-function? obj) (or (= key "prototype") (= key "name") (= key "length") (= key "call") (= key "apply") (= key "bind")))
          (cond
            ((= key "prototype") (js-get-ctor-proto obj))
            ((= key "name") (js-extract-fn-name obj))
            ((= key "length") (js-fn-length obj))
            (else (js-invoke-function-bound obj key))))
        (else js-undefined))))
  (define
    js-dict-get-walk
    (fn
      (obj skey)
      (cond
        ((= obj nil) js-undefined)
        ((js-undefined? obj) js-undefined)
        ((not (= (type-of obj) "dict")) js-undefined)
        ((dict-has? obj skey) (get obj skey))
        ((dict-has? obj "__proto__")
          (js-dict-get-walk (get obj "__proto__") skey))
        (else js-undefined)))))

(define
  js-num-to-int
  (fn
    (v)
    (let
      ((n (js-to-number v)))
      (cond
        ((js-number-is-nan n) 0)
        ((= n (js-infinity-value)) 0)
        ((= n (- 0 (js-infinity-value))) 0)
        ((>= n 0) (floor n))
        (else (- 0 (floor (- 0 n))))))))

(define dict-has? (fn (d k) (contains? (keys d) k)))

(begin
  (define
    js-set-prop
    (fn
      (obj key val)
      (cond
        ((js-undefined? obj) (error "js-set-prop: cannot set on undefined"))
        ((= (type-of obj) "dict")
          (do (dict-set! obj (js-to-string key) val) val))
        ((= (type-of obj) "list") (do (js-list-set! obj key val) val))
        (else val))))
  (define
    js-list-set!
    (fn
      (lst key val)
      (cond
        ((= (type-of key) "number")
          (let
            ((i (js-num-to-int key)) (n (len lst)))
            (cond
              ((< i 0) nil)
              ((< i n) (set-nth! lst i val))
              ((= i n) (append! lst val))
              (else (do (js-pad-list! lst n i) (append! lst val))))))
        ((= key "length") nil)
        (else nil))))
  (define
    js-pad-list!
    (fn
      (lst from target)
      (cond
        ((>= from target) nil)
        (else
          (do
            (append! lst js-undefined)
            (js-pad-list! lst (+ from 1) target)))))))

(define js-and (fn (a b-thunk) (if (js-to-boolean a) (b-thunk) a)))

(define js-or (fn (a b-thunk) (if (js-to-boolean a) a (b-thunk))))

(define
  js-console-log
  (fn (&rest args) (for-each (fn (a) (log-info (js-to-string a))) args)))

(define console {:log js-console-log})

(define js-math-abs (fn (x) (abs (js-to-number x))))

(define js-math-floor (fn (x) (floor (js-to-number x))))

(define js-math-ceil (fn (x) (ceil (js-to-number x))))

(define js-math-round (fn (x) (floor (+ (js-to-number x) 0.5))))

(define
  js-math-max
  (fn
    (&rest args)
    (cond
      ((empty? args) (- 0 (/ 1 0)))
      (else (js-math-max-loop (first args) (rest args))))))

(define
  js-math-max-loop
  (fn
    (acc xs)
    (cond
      ((empty? xs) acc)
      (else
        (let
          ((h (js-to-number (first xs))))
          (js-math-max-loop (if (> h acc) h acc) (rest xs)))))))

(define
  js-math-min
  (fn
    (&rest args)
    (cond
      ((empty? args) (/ 1 0))
      (else (js-math-min-loop (first args) (rest args))))))

(define
  js-math-min-loop
  (fn
    (acc xs)
    (cond
      ((empty? xs) acc)
      (else
        (let
          ((h (js-to-number (first xs))))
          (js-math-min-loop (if (< h acc) h acc) (rest xs)))))))

(define js-math-random (fn () 0))

(define js-math-sqrt (fn (x) (sqrt (js-to-number x))))

(define js-math-pow (fn (a b) (pow (js-to-number a) (js-to-number b))))

(define
  js-math-trunc
  (fn
    (x)
    (let ((n (js-to-number x))) (if (< n 0) (ceil n) (floor n)))))

(define
  js-math-sign
  (fn
    (x)
    (let
      ((n (js-to-number x)))
      (cond ((> n 0) 1) ((< n 0) -1) (else n)))))

(define
  js-math-cbrt
  (fn
    (x)
    (let
      ((n (js-to-number x)))
      (if (< n 0) (- 0 (pow (- 0 n) (/ 1 3))) (pow n (/ 1 3))))))

(define js-math-hypot (fn (&rest args) (sqrt (js-math-hypot-loop args 0))))

(define
  js-math-hypot-loop
  (fn
    (args acc)
    (if
      (empty? args)
      acc
      (let
        ((n (js-to-number (first args))))
        (js-math-hypot-loop (rest args) (+ acc (* n n)))))))

(define Math {:random js-math-random :trunc js-math-trunc :LN10 2.30259 :SQRT1_2 0.707107 :floor js-math-floor :PI 3.14159 :sqrt js-math-sqrt :hypot js-math-hypot :LOG2E 1.4427 :round js-math-round :ceil js-math-ceil :abs js-math-abs :pow js-math-pow :max js-math-max :LOG10E 0.434294 :SQRT2 1.41421 :cbrt js-math-cbrt :min js-math-min :sign js-math-sign :E 2.71828 :LN2 0.693147})

(define
  js-number-is-finite
  (fn
    (v)
    (and
      (number? v)
      (not (js-number-is-nan v))
      (not (= v (/ 1 0)))
      (not (= v (/ -1 0))))))

(define
  js-number-is-nan
  (fn
    (v)
    (and (number? v) (or (= (inspect v) "nan") (= (inspect v) "-nan")))))

(define
  js-number-is-integer
  (fn
    (v)
    (and (number? v) (js-number-is-finite v) (= v (js-math-trunc v)))))

(define
  js-number-is-safe-integer
  (fn
    (v)
    (and (js-number-is-integer v) (<= (js-math-abs v) 9007199254740991))))

(define
  js-global-is-finite
  (fn (v) (js-number-is-finite (js-to-number v))))

(define js-global-is-nan (fn (v) (js-number-is-nan (js-to-number v))))

(define Number {:isFinite js-number-is-finite :MAX_SAFE_INTEGER 9007199254740991 :EPSILON 2.22045e-16 :MAX_VALUE (js-max-value-approx) :POSITIVE_INFINITY (js-infinity-value) :__callable__ js-to-number :isInteger js-number-is-integer :prototype {:valueOf (fn () (js-this)) :toPrecision (fn (&rest args) (js-to-string (js-this))) :toString (fn (&rest args) (let ((this-val (js-this)) (radix (if (empty? args) 10 (js-to-number (nth args 0))))) (js-num-to-str-radix this-val (if (or (= radix nil) (js-undefined? radix)) 10 radix)))) :toLocaleString (fn () (js-to-string (js-this))) :toFixed (fn (d) (js-number-to-fixed (js-this) (if (= d nil) 0 (js-to-number d)))) :toExponential (fn (&rest args) (js-to-string (js-this)))} :isNaN js-number-is-nan :isSafeInteger js-number-is-safe-integer :NEGATIVE_INFINITY (- 0 (js-infinity-value)) :NaN (js-nan-value) :MIN_VALUE 4.94066e-324 :MIN_SAFE_INTEGER -9007199254740991})

(dict-set! Number "length" 1)

(dict-set! Number "name" "Number")

(dict-set! (get Number "prototype") "constructor" Number)

(define isFinite js-global-is-finite)

(define isNaN js-global-is-nan)

(define __js_microtask_queue__ (dict))

(dict-set! __js_microtask_queue__ "q" (list))

(define
  js-mt-push!
  (fn
    (thunk)
    (dict-set!
      __js_microtask_queue__
      "q"
      (append (get __js_microtask_queue__ "q") (list thunk)))))

(define
  js-mt-pop!
  (fn
    ()
    (let
      ((q (get __js_microtask_queue__ "q")))
      (if
        (empty? q)
        nil
        (let
          ((h (first q)))
          (dict-set! __js_microtask_queue__ "q" (rest q))
          h)))))

(define js-mt-empty? (fn () (empty? (get __js_microtask_queue__ "q"))))

(define
  js-drain-microtasks!
  (fn
    ()
    (cond
      ((js-mt-empty?) :js-undefined)
      (else (let ((t (js-mt-pop!))) (t) (js-drain-microtasks!))))))

(define
  js-promise?
  (fn
    (v)
    (and
      (= (type-of v) "dict")
      (dict-has? v "__js_promise__")
      (= (get v "__js_promise__") true))))

(define
  js-make-promise
  (fn
    ()
    (let
      ((p (dict)))
      (dict-set! p "__js_promise__" true)
      (dict-set! p "state" "pending")
      (dict-set! p "value" :js-undefined)
      (dict-set! p "callbacks" (list))
      p)))

(define
  js-promise-resolve!
  (fn
    (p value)
    (cond
      ((not (= (get p "state") "pending")) :js-undefined)
      ((js-promise? value)
        (js-promise-then-internal!
          value
          (fn (v) (js-promise-resolve! p v))
          (fn (r) (js-promise-reject! p r))))
      (else
        (begin
          (dict-set! p "state" "fulfilled")
          (dict-set! p "value" value)
          (js-promise-flush-callbacks! p))))))

(define
  js-promise-reject!
  (fn
    (p reason)
    (cond
      ((not (= (get p "state") "pending")) :js-undefined)
      (else
        (begin
          (dict-set! p "state" "rejected")
          (dict-set! p "value" reason)
          (js-promise-flush-callbacks! p))))))

(define
  js-iterable-to-list
  (fn
    (v)
    (cond
      ((list? v) v)
      ((= (type-of v) "string") (js-string-to-list v 0 (list)))
      ((dict? v)
        (cond
          ((contains? (keys v) "length") (js-arraylike-to-list v))
          (else
            (let
              ((result (list)))
              (begin
                (for-each (fn (k) (append! result (get v k))) (keys v))
                result)))))
      (else (list)))))

(define
  js-arraylike-to-list
  (fn
    (v)
    (cond
      ((list? v) v)
      ((= (type-of v) "string") (js-string-to-list v 0 (list)))
      ((dict? v)
        (let
          ((n-val (get v "length")))
          (if
            (or (= n-val nil) (js-undefined? n-val))
            (list)
            (let
              ((n (js-to-number n-val)))
              (js-arraylike-to-list-loop v 0 n (list))))))
      (else (list)))))

(define
  js-arraylike-to-list-loop
  (fn
    (v i n acc)
    (if
      (>= i n)
      acc
      (let
        ((val (get v (str i))))
        (do
          (append! acc (if (= val nil) :js-undefined val))
          (js-arraylike-to-list-loop v (+ i 1) n acc))))))

(define
  js-string-to-list
  (fn
    (s i acc)
    (if
      (>= i (len s))
      acc
      (begin (append! acc (char-at s i)) (js-string-to-list s (+ i 1) acc)))))

(define
  js-object-keys
  (fn
    (o)
    (cond
      ((dict? o)
        (let
          ((result (list)))
          (for-each (fn (k) (append! result k)) (keys o))
          result))
      (else (list)))))

(define
  js-object-values
  (fn
    (o)
    (cond
      ((dict? o)
        (let
          ((result (list)))
          (for-each (fn (k) (append! result (get o k))) (keys o))
          result))
      (else (list)))))

(define
  js-object-entries
  (fn
    (o)
    (cond
      ((dict? o)
        (let
          ((result (list)))
          (for-each
            (fn
              (k)
              (let
                ((pair (list)))
                (append! pair k)
                (append! pair (get o k))
                (append! result pair)))
            (keys o))
          result))
      (else (list)))))

(define
  js-object-assign
  (fn
    (&rest args)
    (cond
      ((= (len args) 0) (dict))
      (else
        (let
          ((target (nth args 0)))
          (for-each
            (fn
              (src)
              (when
                (dict? src)
                (for-each
                  (fn (k) (dict-set! target k (get src k)))
                  (keys src))))
            (rest args))
          target)))))

(define js-object-freeze (fn (o) o))

(define
  js-object-get-prototype-of
  (fn
    (o)
    (cond
      ((= o nil) (error "TypeError: Cannot convert null to object"))
      ((js-undefined? o)
        (error "TypeError: Cannot convert undefined to object"))
      ((dict? o)
        (if (contains? (keys o) "__proto__") (get o "__proto__") nil))
      (else nil))))

(define
  js-object-set-prototype-of
  (fn
    (o proto)
    (begin (when (dict? o) (dict-set! o "__proto__" proto)) o)))

(define
  js-object-create
  (fn
    (&rest args)
    (let
      ((proto (if (empty? args) nil (nth args 0))))
      (let
        ((obj (dict)))
        (begin
          (when (not (= proto nil)) (dict-set! obj "__proto__" proto))
          (when
            (and (>= (len args) 2) (dict? (nth args 1)))
            (for-each
              (fn
                (k)
                (dict-set! obj k (get (get (nth args 1) k) "value")))
              (keys (nth args 1))))
          obj)))))

(define
  js-object-define-property
  (fn
    (o key desc)
    (begin
      (when
        (and (dict? o) (dict? desc) (contains? (keys desc) "value"))
        (dict-set! o (js-to-string key) (get desc "value")))
      o)))

(define
  js-object-define-properties
  (fn
    (o descs)
    (begin
      (when
        (and (dict? o) (dict? descs))
        (for-each
          (fn (k) (js-object-define-property o k (get descs k)))
          (keys descs)))
      o)))

(define
  js-object-get-own-property-names
  (fn
    (o)
    (cond
      ((list? o) (let ((r (list))) (begin (js-list-keys-loop o 0 r) r)))
      ((dict? o) (js-object-keys o))
      (else (list)))))

(define
  js-object-get-own-property-descriptor
  (fn
    (o key)
    (if
      (and (dict? o) (contains? (keys o) (js-to-string key)))
      {:writable true :value (get o (js-to-string key)) :enumerable true :configurable true}
      :js-undefined)))

(define
  js-object-get-own-property-descriptors
  (fn
    (o)
    (let
      ((out (dict)))
      (begin
        (when
          (dict? o)
          (for-each
            (fn
              (k)
              (dict-set! out k (js-object-get-own-property-descriptor o k)))
            (keys o)))
        out))))

(define js-object-is-extensible (fn (o) (not (js-undefined? o))))

(define js-object-is-frozen (fn (o) false))

(define js-object-is-sealed (fn (o) false))

(define js-object-prevent-extensions (fn (o) o))

(define js-object-seal (fn (o) o))

(define
  js-object-is
  (fn
    (a b)
    (cond
      ((and (js-number-is-nan a) (js-number-is-nan b)) true)
      ((and (= a 0) (= b 0))
        (let ((ia (inspect a)) (ib (inspect b))) (= ia ib)))
      (else (js-strict-eq a b)))))

(define
  js-object-from-entries
  (fn
    (iter)
    (let
      ((out (dict)) (lst (js-iterable-to-list iter)))
      (begin
        (for-each
          (fn
            (pair)
            (when
              (and (list? pair) (>= (len pair) 2))
              (dict-set! out (js-to-string (nth pair 0)) (nth pair 1))))
          lst)
        out))))

(define
  js-object-has-own
  (fn
    (o key)
    (cond
      ((dict? o) (contains? (keys o) (js-to-string key)))
      ((list? o)
        (let
          ((idx (js-to-number key)))
          (and (>= idx 0) (< idx (len o)) (integer? idx))))
      (else false))))

(define Object {:entries js-object-entries :defineProperties js-object-define-properties :__callable__ (fn (&rest args) (cond ((= (len args) 0) (dict)) (else (nth args 0)))) :preventExtensions js-object-prevent-extensions :prototype {:valueOf (fn () (js-this)) :propertyIsEnumerable (fn (k) (let ((o (js-this))) (js-object-has-own o k))) :isPrototypeOf (fn (o) (let ((this-val (js-this))) (cond ((not (dict? o)) false) (else (let ((proto (if (contains? (keys o) "__proto__") (get o "__proto__") nil))) (cond ((= proto this-val) true) ((= proto nil) false) (else ((get (get Object "prototype") "isPrototypeOf") proto)))))))) :toString (fn () "[object Object]") :hasOwnProperty (fn (k) (let ((o (js-this))) (js-object-has-own o k))) :toLocaleString (fn () "[object Object]")} :values js-object-values :hasOwn js-object-has-own :freeze js-object-freeze :assign js-object-assign :isFrozen js-object-is-frozen :getOwnPropertyDescriptor js-object-get-own-property-descriptor :fromEntries js-object-from-entries :defineProperty js-object-define-property :setPrototypeOf js-object-set-prototype-of :getOwnPropertyNames js-object-get-own-property-names :getOwnPropertyDescriptors js-object-get-own-property-descriptors :create js-object-create :isExtensible js-object-is-extensible :is js-object-is :keys js-object-keys :getPrototypeOf js-object-get-prototype-of :isSealed js-object-is-sealed :seal js-object-seal})

(dict-set! Object "length" 1)

(dict-set! Object "name" "Object")

(dict-set! (get Object "prototype") "constructor" Object)

(define
  js-delete-prop
  (fn
    (obj key)
    (cond
      ((dict? obj)
        (begin (dict-set! obj (js-to-string key) js-undefined) true))
      (else true))))

(define
  js-optchain-get
  (fn
    (obj key)
    (if
      (or (= obj nil) (js-undefined? obj))
      js-undefined
      (js-get-prop obj key))))

(define
  js-optchain-call
  (fn
    (fn-val args)
    (if
      (or (= fn-val nil) (js-undefined? fn-val))
      js-undefined
      (js-call-plain fn-val args))))

(define
  js-array-spread-build
  (fn
    (&rest items)
    (let
      ((result (list)))
      (for-each
        (fn
          (item)
          (let
            ((kind (nth item 0)))
            (cond
              ((= kind "js-spread")
                (for-each
                  (fn (x) (append! result x))
                  (js-iterable-to-list (nth item 1))))
              (else (append! result (nth item 1))))))
        items)
      result)))

(define js-array-is-array (fn (v) (list? v)))

(define js-array-of (fn (&rest args) args))

(define
  js-array-proto-fn
  (fn
    (name)
    (fn
      (&rest args)
      (let
        ((this-val (js-this)))
        (let
          ((recv (cond ((list? this-val) this-val) ((and (dict? this-val) (contains? (keys this-val) "length")) (js-arraylike-to-list this-val)) (else this-val))))
          (js-invoke-method recv name args))))))

(define
  js-array-from
  (fn
    (&rest args)
    (cond
      ((= (len args) 0) (list))
      (else
        (let
          ((src (js-iterable-to-list (nth args 0)))
            (map-fn (if (< (len args) 2) nil (nth args 1))))
          (if
            (= map-fn nil)
            (let
              ((result (list)))
              (for-each (fn (x) (append! result x)) src)
              result)
            (let
              ((result (list)) (i 0))
              (for-each
                (fn (x) (append! result (map-fn x)) (set! i (+ i 1)))
                src)
              result)))))))

(define Array {:__callable__ (fn (&rest args) (cond ((= (len args) 0) (list)) ((and (= (len args) 1) (number? (nth args 0))) (js-make-list-of-length (js-num-to-int (nth args 0)) :js-undefined)) (else args))) :prototype {:entries (js-array-proto-fn "entries") :concat (js-array-proto-fn "concat") :lastIndexOf (js-array-proto-fn "lastIndexOf") :splice (js-array-proto-fn "splice") :filter (js-array-proto-fn "filter") :findLast (js-array-proto-fn "findLast") :shift (js-array-proto-fn "shift") :join (js-array-proto-fn "join") :reduceRight (js-array-proto-fn "reduceRight") :values (js-array-proto-fn "values") :reduce (js-array-proto-fn "reduce") :slice (js-array-proto-fn "slice") :includes (js-array-proto-fn "includes") :findLastIndex (js-array-proto-fn "findLastIndex") :find (js-array-proto-fn "find") :toLocaleString (js-array-proto-fn "toLocaleString") :findIndex (js-array-proto-fn "findIndex") :sort (js-array-proto-fn "sort") :every (js-array-proto-fn "every") :indexOf (js-array-proto-fn "indexOf") :unshift (js-array-proto-fn "unshift") :push (js-array-proto-fn "push") :map (js-array-proto-fn "map") :some (js-array-proto-fn "some") :flat (js-array-proto-fn "flat") :toSorted (js-array-proto-fn "toSorted") :at (js-array-proto-fn "at") :pop (js-array-proto-fn "pop") :toReversed (js-array-proto-fn "toReversed") :copyWithin (js-array-proto-fn "copyWithin") :toString (js-array-proto-fn "toString") :forEach (js-array-proto-fn "forEach") :fill (js-array-proto-fn "fill") :flatMap (js-array-proto-fn "flatMap") :keys (js-array-proto-fn "keys") :reverse (js-array-proto-fn "reverse")} :isArray js-array-is-array :of js-array-of :from js-array-from})

(dict-set! Array "length" 1)

(dict-set! (get Array "prototype") "constructor" Array)

(dict-set! Array "name" "Array")

(define
  js-string-from-char-code
  (fn (&rest args) (js-string-from-char-code-loop args 0 "")))

(define
  js-string-from-char-code-loop
  (fn
    (args i acc)
    (if
      (>= i (len args))
      acc
      (js-string-from-char-code-loop
        args
        (+ i 1)
        (str acc (js-code-to-char (js-num-to-int (nth args i))))))))

(define
  js-string-proto-fn
  (fn
    (name)
    (fn
      (&rest args)
      (let
        ((this-val (js-this)))
        (js-invoke-method (js-to-string this-val) name args)))))

(define String {:fromCharCode js-string-from-char-code :__callable__ (fn (&rest args) (if (= (len args) 0) "" (js-to-string (nth args 0)))) :prototype {:toLowerCase (js-string-proto-fn "toLowerCase") :concat (js-string-proto-fn "concat") :startsWith (js-string-proto-fn "startsWith") :padEnd (js-string-proto-fn "padEnd") :codePointAt (js-string-proto-fn "codePointAt") :lastIndexOf (js-string-proto-fn "lastIndexOf") :indexOf (js-string-proto-fn "indexOf") :localeCompare (js-string-proto-fn "localeCompare") :split (js-string-proto-fn "split") :endsWith (js-string-proto-fn "endsWith") :trim (js-string-proto-fn "trim") :valueOf (js-string-proto-fn "valueOf") :at (js-string-proto-fn "at") :normalize (js-string-proto-fn "normalize") :substring (js-string-proto-fn "substring") :replaceAll (js-string-proto-fn "replaceAll") :repeat (js-string-proto-fn "repeat") :padStart (js-string-proto-fn "padStart") :search (js-string-proto-fn "search") :toUpperCase (js-string-proto-fn "toUpperCase") :trimEnd (js-string-proto-fn "trimEnd") :toString (js-string-proto-fn "toString") :toLocaleLowerCase (js-string-proto-fn "toLocaleLowerCase") :charCodeAt (js-string-proto-fn "charCodeAt") :slice (js-string-proto-fn "slice") :charAt (js-string-proto-fn "charAt") :match (js-string-proto-fn "match") :includes (js-string-proto-fn "includes") :trimStart (js-string-proto-fn "trimStart") :toLocaleUpperCase (js-string-proto-fn "toLocaleUpperCase") :replace (js-string-proto-fn "replace")} :raw (fn (&rest args) (if (empty? args) "" (js-to-string (nth args 0))))})

(dict-set! String "length" 1)

(dict-set! (get String "prototype") "constructor" String)

(dict-set! String "name" "String")

(define Boolean {:__callable__ (fn (&rest args) (if (= (len args) 0) false (js-to-boolean (nth args 0))))})

(dict-set! Boolean "length" 1)

(dict-set! Boolean "name" "Boolean")

(define
  parseInt
  (fn
    (&rest args)
    (cond
      ((= (len args) 0) (js-nan-value))
      (else
        (let
          ((s (js-to-string (nth args 0)))
            (radix-arg
              (if (< (len args) 2) 10 (js-to-number (nth args 1)))))
          (let
            ((radix (if (or (js-number-is-nan radix-arg) (= radix-arg 0)) 10 radix-arg)))
            (js-parse-int-str (js-trim s) (js-math-trunc radix))))))))

(define
  js-parse-int-str
  (fn
    (s radix)
    (cond
      ((= s "") (js-nan-value))
      (else
        (let
          ((first (char-at s 0)))
          (cond
            ((= first "-")
              (let
                ((r (js-parse-int-digits (js-string-slice s 1 (len s)) radix 0 false)))
                (if (js-number-is-nan r) r (- 0 r))))
            ((= first "+")
              (js-parse-int-digits
                (js-string-slice s 1 (len s))
                radix
                0
                false))
            (else (js-parse-int-digits s radix 0 false))))))))

(define
  js-parse-int-digits
  (fn
    (s radix acc sawdigit)
    (if
      (= (len s) 0)
      (if sawdigit acc (js-nan-value))
      (let
        ((c (char-at s 0)))
        (let
          ((d (js-digit-value c radix)))
          (if
            (= d -1)
            (if sawdigit acc (js-nan-value))
            (js-parse-int-digits
              (js-string-slice s 1 (len s))
              radix
              (+ (* acc radix) d)
              true)))))))

(define
  js-digit-value
  (fn
    (c radix)
    (let
      ((code (char-code c)))
      (let
        ((d (cond ((and (>= code 48) (<= code 57)) (- code 48)) ((and (>= code 97) (<= code 122)) (+ 10 (- code 97))) ((and (>= code 65) (<= code 90)) (+ 10 (- code 65))) (else -1))))
        (if (or (= d -1) (>= d radix)) -1 d)))))

(define
  parseFloat
  (fn
    (&rest args)
    (if
      (= (len args) 0)
      (js-nan-value)
      (let
        ((s (js-trim (js-to-string (nth args 0)))))
        (cond
          ((= s "") (js-nan-value))
          ((= s "Infinity") (js-infinity-value))
          ((= s "+Infinity") (js-infinity-value))
          ((= s "-Infinity") (- 0 (js-infinity-value)))
          (else (js-parse-float-prefix s)))))))

(define
  js-parse-float-prefix
  (fn
    (s)
    (let
      ((end (js-float-prefix-end s 0 false false false)))
      (cond
        ((= end 0) (js-nan-value))
        (else (js-parse-num-safe (js-string-slice s 0 end)))))))

(define
  js-float-prefix-end
  (fn
    (s i sawdigit sawdot sawe)
    (cond
      ((>= i (len s)) i)
      (else
        (let
          ((c (char-at s i)))
          (cond
            ((or (= c "0") (= c "1") (= c "2") (= c "3") (= c "4") (= c "5") (= c "6") (= c "7") (= c "8") (= c "9"))
              (js-float-prefix-end s (+ i 1) true sawdot sawe))
            ((and (= c ".") (not sawdot) (not sawe))
              (js-float-prefix-end s (+ i 1) sawdigit true sawe))
            ((and (or (= c "e") (= c "E")) sawdigit (not sawe))
              (js-float-prefix-end s (+ i 1) false sawdot true))
            ((and (or (= c "+") (= c "-")) (= i 0))
              (js-float-prefix-end s (+ i 1) sawdigit sawdot sawe))
            ((and (or (= c "+") (= c "-")) sawe)
              (let
                ((prev (char-at s (- i 1))))
                (if
                  (or (= prev "e") (= prev "E"))
                  (js-float-prefix-end s (+ i 1) sawdigit sawdot sawe)
                  i)))
            (else i)))))))

(define
  encodeURIComponent
  (fn (v) (let ((s (js-to-string v))) (js-uri-encode-loop s 0 ""))))

(define decodeURIComponent (fn (v) (js-to-string v)))

(define encodeURI (fn (v) (js-to-string v)))

(define decodeURI (fn (v) (js-to-string v)))

(define
  js-uri-encode-loop
  (fn
    (s i acc)
    (cond
      ((>= i (len s)) acc)
      (else
        (let
          ((c (char-at s i)))
          (let
            ((code (char-code c)))
            (cond
              ((= c " ") (js-uri-encode-loop s (+ i 1) (str acc "%20")))
              ((and (>= code 48) (<= code 57))
                (js-uri-encode-loop s (+ i 1) (str acc c)))
              ((and (>= code 65) (<= code 90))
                (js-uri-encode-loop s (+ i 1) (str acc c)))
              ((and (>= code 97) (<= code 122))
                (js-uri-encode-loop s (+ i 1) (str acc c)))
              ((or (= c "-") (= c "_") (= c ".") (= c "~") (= c "!") (= c "*") (= c "'") (= c "(") (= c ")"))
                (js-uri-encode-loop s (+ i 1) (str acc c)))
              (else
                (js-uri-encode-loop s (+ i 1) (str acc "%" (js-hex-2 code)))))))))))

(define
  js-hex-2
  (fn
    (n)
    (let
      ((hi (js-math-trunc (/ n 16))) (lo (mod n 16)))
      (str (js-hex-digit hi) (js-hex-digit lo)))))

(define
  js-hex-digit
  (fn
    (d)
    (cond ((< d 10) (js-to-string d)) (else (js-code-to-char (+ 55 d))))))

(define
  js-json-stringify
  (fn
    (&rest args)
    (if
      (= (len args) 0)
      js-undefined
      (js-json-stringify-value (nth args 0)))))

(define
  js-json-stringify-value
  (fn
    (v)
    (cond
      ((= v nil) "null")
      ((js-undefined? v) js-undefined)
      ((= (type-of v) "boolean") (if v "true" "false"))
      ((number? v) (js-number-to-string v))
      ((= (type-of v) "string") (js-json-escape-string v))
      ((list? v)
        (let
          ((parts (list)))
          (for-each
            (fn
              (x)
              (let
                ((s (js-json-stringify-value x)))
                (if
                  (js-undefined? s)
                  (append! parts "null")
                  (append! parts s))))
            v)
          (str "[" (join "," parts) "]")))
      ((dict? v)
        (let
          ((parts (list)))
          (for-each
            (fn
              (k)
              (let
                ((val (get v k)))
                (let
                  ((vs (js-json-stringify-value val)))
                  (if
                    (not (js-undefined? vs))
                    (append! parts (str (js-json-escape-string k) ":" vs))))))
            (keys v))
          (str "{" (join "," parts) "}")))
      (else "null"))))

(define
  js-json-escape-string
  (fn (s) (str "\"" (js-json-escape-loop s 0 "") "\"")))

(define
  js-json-escape-loop
  (fn
    (s i acc)
    (if
      (>= i (len s))
      acc
      (let
        ((c (char-at s i)))
        (cond
          ((= c "\"") (js-json-escape-loop s (+ i 1) (str acc "\\\"")))
          ((= c "\\") (js-json-escape-loop s (+ i 1) (str acc "\\\\")))
          ((= c "\n") (js-json-escape-loop s (+ i 1) (str acc "\\n")))
          ((= c "\r") (js-json-escape-loop s (+ i 1) (str acc "\\r")))
          ((= c "\t") (js-json-escape-loop s (+ i 1) (str acc "\\t")))
          (else (js-json-escape-loop s (+ i 1) (str acc c))))))))

(define
  js-json-parse
  (fn
    (&rest args)
    (if
      (= (len args) 0)
      js-undefined
      (let
        ((st (dict)))
        (dict-set! st "s" (js-to-string (nth args 0)))
        (dict-set! st "i" 0)
        (js-json-parse-value st)))))

(define
  js-json-skip-ws!
  (fn
    (st)
    (let
      ((s (get st "s")) (i (get st "i")))
      (cond
        ((>= i (len s)) nil)
        ((or (= (char-at s i) " ") (= (char-at s i) "\t") (= (char-at s i) "\n") (= (char-at s i) "\r"))
          (begin (dict-set! st "i" (+ i 1)) (js-json-skip-ws! st)))
        (else nil)))))

(define
  js-json-parse-value
  (fn
    (st)
    (js-json-skip-ws! st)
    (let
      ((s (get st "s")) (i (get st "i")))
      (cond
        ((>= i (len s)) (error "JSON: unexpected end"))
        ((= (char-at s i) "\"") (js-json-parse-string st))
        ((= (char-at s i) "[") (js-json-parse-array st))
        ((= (char-at s i) "{") (js-json-parse-object st))
        ((= (char-at s i) "t") (begin (dict-set! st "i" (+ i 4)) true))
        ((= (char-at s i) "f") (begin (dict-set! st "i" (+ i 5)) false))
        ((= (char-at s i) "n") (begin (dict-set! st "i" (+ i 4)) nil))
        (else (js-json-parse-number st))))))

(define
  js-json-parse-string
  (fn
    (st)
    (let
      ((s (get st "s")))
      (dict-set! st "i" (+ (get st "i") 1))
      (let
        ((buf (list)))
        (js-json-parse-string-loop st s buf)
        (dict-set! st "i" (+ (get st "i") 1))
        (join "" buf)))))

(define
  js-json-parse-string-loop
  (fn
    (st s buf)
    (let
      ((i (get st "i")))
      (cond
        ((>= i (len s)) nil)
        ((= (char-at s i) "\"") nil)
        ((= (char-at s i) "\\")
          (begin
            (when
              (< (+ i 1) (len s))
              (let
                ((e (char-at s (+ i 1))))
                (cond
                  ((= e "n") (append! buf "\n"))
                  ((= e "t") (append! buf "\t"))
                  ((= e "r") (append! buf "\r"))
                  ((= e "\"") (append! buf "\""))
                  ((= e "\\") (append! buf "\\"))
                  ((= e "/") (append! buf "/"))
                  (else (append! buf e)))))
            (dict-set! st "i" (+ i 2))
            (js-json-parse-string-loop st s buf)))
        (else
          (begin
            (append! buf (char-at s i))
            (dict-set! st "i" (+ i 1))
            (js-json-parse-string-loop st s buf)))))))

(define
  js-json-parse-number
  (fn
    (st)
    (let
      ((s (get st "s")) (i (get st "i")))
      (let
        ((start i))
        (js-json-parse-number-loop st s)
        (js-to-number (js-string-slice s start (get st "i")))))))

(define
  js-json-parse-number-loop
  (fn
    (st s)
    (let
      ((i (get st "i")))
      (cond
        ((>= i (len s)) nil)
        ((or (js-is-digit? (char-at s i)) (= (char-at s i) "-") (= (char-at s i) "+") (= (char-at s i) ".") (= (char-at s i) "e") (= (char-at s i) "E"))
          (begin
            (dict-set! st "i" (+ i 1))
            (js-json-parse-number-loop st s)))
        (else nil)))))

(define
  js-json-parse-array
  (fn
    (st)
    (let
      ((result (list)))
      (dict-set! st "i" (+ (get st "i") 1))
      (js-json-skip-ws! st)
      (cond
        ((and (< (get st "i") (len (get st "s"))) (= (char-at (get st "s") (get st "i")) "]"))
          (begin (dict-set! st "i" (+ (get st "i") 1)) result))
        (else (begin (js-json-parse-array-loop st result) result))))))

(define
  js-json-parse-array-loop
  (fn
    (st result)
    (append! result (js-json-parse-value st))
    (js-json-skip-ws! st)
    (let
      ((c (char-at (get st "s") (get st "i"))))
      (cond
        ((= c ",")
          (begin
            (dict-set! st "i" (+ (get st "i") 1))
            (js-json-skip-ws! st)
            (js-json-parse-array-loop st result)))
        ((= c "]") (dict-set! st "i" (+ (get st "i") 1)))
        (else (error "JSON: expected , or ]"))))))

(define
  js-json-parse-object
  (fn
    (st)
    (let
      ((result (dict)))
      (dict-set! st "i" (+ (get st "i") 1))
      (js-json-skip-ws! st)
      (cond
        ((and (< (get st "i") (len (get st "s"))) (= (char-at (get st "s") (get st "i")) "}"))
          (begin (dict-set! st "i" (+ (get st "i") 1)) result))
        (else (begin (js-json-parse-object-loop st result) result))))))

(define
  js-json-parse-object-loop
  (fn
    (st result)
    (js-json-skip-ws! st)
    (let
      ((k (js-json-parse-string st)))
      (js-json-skip-ws! st)
      (when
        (not (= (char-at (get st "s") (get st "i")) ":"))
        (error "JSON: expected :"))
      (dict-set! st "i" (+ (get st "i") 1))
      (let ((v (js-json-parse-value st))) (dict-set! result k v))
      (js-json-skip-ws! st)
      (let
        ((c (char-at (get st "s") (get st "i"))))
        (cond
          ((= c ",")
            (begin
              (dict-set! st "i" (+ (get st "i") 1))
              (js-json-parse-object-loop st result)))
          ((= c "}") (dict-set! st "i" (+ (get st "i") 1)))
          (else (error "JSON: expected , or }")))))))

(define JSON {:parse js-json-parse :stringify js-json-stringify})

(define
  js-promise-flush-callbacks!
  (fn
    (p)
    (let
      ((cbs (get p "callbacks")))
      (dict-set! p "callbacks" (list))
      (for-each
        (fn (cb) (js-mt-push! (fn () (js-promise-run-callback! p cb))))
        cbs))))

(define
  js-promise-run-callback!
  (fn
    (p cb)
    (let
      ((on-fulfilled (nth cb 0))
        (on-rejected (nth cb 1))
        (result-promise (nth cb 2))
        (state (get p "state"))
        (value (get p "value")))
      (cond
        ((= state "fulfilled")
          (if
            (js-function? on-fulfilled)
            (js-promise-run-handler! result-promise on-fulfilled value)
            (js-promise-resolve! result-promise value)))
        ((= state "rejected")
          (if
            (js-function? on-rejected)
            (js-promise-run-handler! result-promise on-rejected value)
            (js-promise-reject! result-promise value)))
        (else :js-undefined)))))

(define
  js-promise-run-handler!
  (fn
    (result-promise handler arg)
    (let
      ((outcome (js-promise-try-call handler arg)))
      (cond
        ((get outcome "threw")
          (js-promise-reject! result-promise (get outcome "error")))
        (else (js-promise-resolve! result-promise (get outcome "value")))))))

(define
  js-call-arity-tolerant
  (fn
    (handler arg)
    (cond
      ((= (type-of handler) "lambda")
        (let
          ((params (lambda-params handler)))
          (cond
            ((empty? params) (handler))
            ((= (first params) "&rest") (handler arg))
            (else (handler arg)))))
      (else (handler arg)))))

(define
  js-promise-try-call
  (fn
    (handler arg)
    (let
      ((out (dict)))
      (dict-set! out "threw" false)
      (dict-set! out "value" :js-undefined)
      (dict-set! out "error" :js-undefined)
      (guard
        (e
          (else
            (begin
              (dict-set! out "threw" true)
              (dict-set! out "error" e)
              out)))
        (dict-set! out "value" (js-call-arity-tolerant handler arg))
        out))))

(define
  js-promise-then-internal!
  (fn
    (p on-fulfilled on-rejected)
    (let
      ((new-p (js-make-promise)) (cb (list on-fulfilled on-rejected)))
      (let
        ((cb3 (append cb (list new-p))))
        (cond
          ((= (get p "state") "pending")
            (dict-set!
              p
              "callbacks"
              (append (get p "callbacks") (list cb3))))
          (else (js-mt-push! (fn () (js-promise-run-callback! p cb3))))))
      new-p)))

(define
  js-promise-then!
  (fn
    (p args)
    (let
      ((on-f (if (>= (len args) 1) (nth args 0) :js-undefined))
        (on-r (if (>= (len args) 2) (nth args 1) :js-undefined)))
      (js-promise-then-internal! p on-f on-r))))

(define
  js-promise-catch!
  (fn
    (p args)
    (let
      ((on-r (if (>= (len args) 1) (nth args 0) :js-undefined)))
      (js-promise-then-internal! p :js-undefined on-r))))

(define
  js-promise-finally!
  (fn
    (p args)
    (let
      ((on-fin (if (>= (len args) 1) (nth args 0) :js-undefined)))
      (let
        ((pass-val (fn (v) (begin (when (js-function? on-fin) (on-fin)) v)))
          (pass-err
            (fn
              (r)
              (begin
                (when (js-function? on-fin) (on-fin))
                (let
                  ((throw-p (js-make-promise)))
                  (js-promise-reject! throw-p r)
                  throw-p)))))
        (js-promise-then-internal! p pass-val pass-err)))))

(define
  js-invoke-promise-method
  (fn
    (p name args)
    (cond
      ((= name "then") (js-promise-then! p args))
      ((= name "catch") (js-promise-catch! p args))
      ((= name "finally") (js-promise-finally! p args))
      (else (error (str "TypeError: Promise." name " is not a function"))))))

(define
  js-promise-builtin-method?
  (fn (name) (or (= name "then") (= name "catch") (= name "finally"))))

(define
  Promise
  (fn
    (&rest args)
    (let
      ((executor (if (empty? args) :js-undefined (first args)))
        (p (js-make-promise)))
      (let
        ((resolve-fn (fn (&rest a) (let ((v (if (empty? a) :js-undefined (first a)))) (js-promise-resolve! p v) :js-undefined)))
          (reject-fn
            (fn
              (&rest a)
              (let
                ((r (if (empty? a) :js-undefined (first a))))
                (js-promise-reject! p r)
                :js-undefined))))
        (cond
          ((js-function? executor)
            (guard
              (e (else (js-promise-reject! p e)))
              (executor resolve-fn reject-fn)))
          (else :js-undefined))
        p))))

(define
  js-promise-resolve-static
  (fn
    (&rest args)
    (let
      ((v (if (empty? args) :js-undefined (first args))))
      (cond
        ((js-promise? v) v)
        (else (let ((p (js-make-promise))) (js-promise-resolve! p v) p))))))

(define
  js-promise-reject-static
  (fn
    (&rest args)
    (let
      ((r (if (empty? args) :js-undefined (first args)))
        (p (js-make-promise)))
      (js-promise-reject! p r)
      p)))

(define
  js-make-list-of-length
  (fn (n fill) (js-make-list-loop (list) n fill)))

(define
  js-make-list-loop
  (fn
    (acc n fill)
    (cond
      ((<= n 0) acc)
      (else
        (begin (append! acc fill) (js-make-list-loop acc (- n 1) fill))))))

(define
  js-promise-all-loop!
  (fn
    (result-p items state idx)
    (cond
      ((>= idx (len items)) :js-undefined)
      (else
        (let
          ((item (nth items idx)) (i idx))
          (let
            ((child (if (js-promise? item) item (js-promise-resolve-static item))))
            (js-promise-then-internal!
              child
              (fn
                (v)
                (let
                  ((results (get state "results")))
                  (set-nth! results i v)
                  (dict-set! state "remaining" (- (get state "remaining") 1))
                  (cond
                    ((= (get state "remaining") 0)
                      (js-promise-resolve! result-p results))
                    (else :js-undefined))))
              (fn (r) (js-promise-reject! result-p r))))
          (js-promise-all-loop! result-p items state (+ idx 1)))))))

(define
  js-promise-all-static
  (fn
    (&rest args)
    (let
      ((items (if (empty? args) (list) (first args)))
        (p (js-make-promise)))
      (cond
        ((= (len items) 0) (begin (js-promise-resolve! p (list)) p))
        (else
          (let
            ((n (len items)) (state (dict)))
            (dict-set! state "remaining" n)
            (dict-set!
              state
              "results"
              (js-make-list-of-length n :js-undefined))
            (js-promise-all-loop! p items state 0)
            p))))))

(define
  js-promise-race-static
  (fn
    (&rest args)
    (let
      ((items (if (empty? args) (list) (first args)))
        (p (js-make-promise)))
      (for-each
        (fn
          (item)
          (let
            ((child (if (js-promise? item) item (js-promise-resolve-static item))))
            (js-promise-then-internal!
              child
              (fn (v) (js-promise-resolve! p v))
              (fn (r) (js-promise-reject! p r)))))
        items)
      p)))

(define __js_promise_statics__ (dict))

(dict-set! __js_promise_statics__ "resolve" js-promise-resolve-static)

(dict-set! __js_promise_statics__ "reject" js-promise-reject-static)

(dict-set! __js_promise_statics__ "all" js-promise-all-static)

(dict-set! __js_promise_statics__ "race" js-promise-race-static)

(define
  js-async-wrap
  (fn
    (thunk)
    (let
      ((p (js-make-promise)))
      (guard
        (e (else (js-promise-reject! p e)))
        (let
          ((v (thunk)))
          (cond
            ((js-promise? v)
              (js-promise-then-internal!
                v
                (fn (x) (js-promise-resolve! p x))
                (fn (r) (js-promise-reject! p r))))
            (else (js-promise-resolve! p v)))))
      p)))

(define
  js-await-value
  (fn
    (v)
    (cond
      ((not (js-promise? v)) v)
      (else
        (begin
          (js-drain-microtasks!)
          (let
            ((state (get v "state")))
            (cond
              ((= state "fulfilled") (get v "value"))
              ((= state "rejected") (raise (get v "value")))
              (else
                (begin
                  (js-drain-microtasks!)
                  (let
                    ((state2 (get v "state")))
                    (cond
                      ((= state2 "fulfilled") (get v "value"))
                      ((= state2 "rejected") (raise (get v "value")))
                      (else (error "await on pending Promise (no scheduler)")))))))))))))

(define __drain (fn () (js-drain-microtasks!) :js-undefined))

(define __js_regex_platform__ (dict))

(define
  js-regex-platform-override!
  (fn (op impl) (dict-set! __js_regex_platform__ op impl)))

(define
  js-regex?
  (fn (v) (and (dict? v) (contains? (keys v) "__js_regex__"))))

(define
  js-regex-has-flag?
  (fn (flags ch) (>= (js-string-index-of flags ch 0) 0)))

(define
  js-regex-new
  (fn
    (pattern flags)
    (let
      ((rx (dict))
        (fl (if (js-undefined? flags) "" (if (= flags nil) "" flags))))
      (dict-set! rx "__js_regex__" true)
      (dict-set! rx "source" pattern)
      (dict-set! rx "flags" fl)
      (dict-set! rx "global" (js-regex-has-flag? fl "g"))
      (dict-set! rx "ignoreCase" (js-regex-has-flag? fl "i"))
      (dict-set! rx "multiline" (js-regex-has-flag? fl "m"))
      (dict-set! rx "sticky" (js-regex-has-flag? fl "y"))
      (dict-set! rx "unicode" (js-regex-has-flag? fl "u"))
      (dict-set! rx "dotAll" (js-regex-has-flag? fl "s"))
      (dict-set! rx "hasIndices" (js-regex-has-flag? fl "d"))
      (dict-set! rx "lastIndex" 0)
      rx)))

(define
  js-regex-stub-test
  (fn
    (rx s)
    (let
      ((src (get rx "source")) (ci (get rx "ignoreCase")))
      (let
        ((hay (if ci (js-lower-case s) s))
          (needle (if ci (js-lower-case src) src)))
        (>= (js-string-index-of hay needle 0) 0)))))

(define
  js-regex-stub-exec
  (fn
    (rx s)
    (let
      ((src (get rx "source")) (ci (get rx "ignoreCase")))
      (let
        ((hay (if ci (js-lower-case s) s))
          (needle (if ci (js-lower-case src) src)))
        (let
          ((idx (js-string-index-of hay needle 0)))
          (if
            (= idx -1)
            nil
            (let
              ((matched (js-string-slice s idx (+ idx (len src))))
                (res (list)))
              (append! res matched)
              res)))))))

(define
  js-regex-invoke-method
  (fn
    (rx name args)
    (cond
      ((= name "test")
        (let
          ((impl (get __js_regex_platform__ "test"))
            (arg (if (= (len args) 0) "" (js-to-string (nth args 0)))))
          (if
            (js-undefined? impl)
            (js-regex-stub-test rx arg)
            (impl rx arg))))
      ((= name "exec")
        (let
          ((impl (get __js_regex_platform__ "exec"))
            (arg (if (= (len args) 0) "" (js-to-string (nth args 0)))))
          (if
            (js-undefined? impl)
            (js-regex-stub-exec rx arg)
            (impl rx arg))))
      ((= name "toString")
        (str "/" (get rx "source") "/" (get rx "flags")))
      (else js-undefined))))

(define js-global {:isFinite js-global-is-finite :console console :Number Number :parseFloat parseFloat :Math Math :Array Array :Boolean Boolean :String String :NaN 0 :Infinity inf :isNaN js-global-is-nan :Object Object :parseInt parseInt :JSON JSON :undefined js-undefined})
