;; 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 () nan))

(define js-infinity-value (fn () inf))

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

(define js-max-value-approx (fn () 1.7976931348623157e+308))

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

(define js-function-global {:__callable__ (fn (&rest args) (js-function-ctor args)) :prototype {:call {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "call" args)) :length 1 :name "call"} :length 0 :bind {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "bind" args)) :length 1 :name "bind"} :toString {:__callable__ (fn () (js-invoke-function-method (js-this) "toString" (list))) :length 0 :name "toString"} :apply {:__callable__ (fn (&rest args) (js-invoke-function-method (js-this) "apply" args)) :length 2 :name "apply"} :name ""}})

(define
  js-function-ctor
  (fn
    (args)
    (cond
      ((empty? args) (js-eval "(function(){})"))
      (else
        (let
          ((all-strs (js-fn-args-to-strs args))
            (n (len args)))
          (let
            ((param-strs (js-fn-take-init all-strs))
              (body-str (js-fn-take-last all-strs)))
            (js-eval
              (str "(function(" (js-fn-join-commas param-strs) "){" body-str "})"))))))))

(define
  js-fn-args-to-strs
  (fn
    (args)
    (cond
      ((empty? args) (list))
      (else (cons (js-to-string (first args)) (js-fn-args-to-strs (rest args)))))))

(define
  js-fn-take-init
  (fn
    (lst)
    (cond
      ((empty? lst) (list))
      ((empty? (rest lst)) (list))
      (else (cons (first lst) (js-fn-take-init (rest lst)))))))

(define
  js-fn-take-last
  (fn
    (lst)
    (cond
      ((empty? lst) "")
      ((empty? (rest lst)) (first lst))
      (else (js-fn-take-last (rest lst))))))

(define
  js-fn-join-commas
  (fn
    (lst)
    (cond
      ((empty? lst) "")
      ((empty? (rest lst)) (first lst))
      (else (str (first lst) "," (js-fn-join-commas (rest lst)))))))

;; ── 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)
    (cond
      ((empty? args) :js-undefined)
      ((not (= (type-of (nth args 0)) "string")) (nth args 0))
      (else (js-eval (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-global-this)))

(define js-global-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")
          (let
            ((mapped (js-builtin-fn-length (js-unmap-fn-name (js-extract-fn-name f)))))
            (if (>= mapped 0) mapped (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-builtin-fn-length
  (fn
    (name)
    (cond
      ((= name "fromCharCode") 1)
      ((= name "fromCodePoint") 1)
      ((= name "raw") 1)
      ((= name "of") 0)
      ((= name "from") 1)
      ((= name "isArray") 1)
      ((= name "max") 2)
      ((= name "min") 2)
      ((= name "hypot") 2)
      ((= name "atan2") 2)
      ((= name "imul") 2)
      ((= name "pow") 2)
      (else -1))))

(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-math-sin") "sin")
      ((= name "js-math-cos") "cos")
      ((= name "js-math-tan") "tan")
      ((= name "js-math-asin") "asin")
      ((= name "js-math-acos") "acos")
      ((= name "js-math-atan") "atan")
      ((= name "js-math-atan2") "atan2")
      ((= name "js-math-sinh") "sinh")
      ((= name "js-math-cosh") "cosh")
      ((= name "js-math-tanh") "tanh")
      ((= name "js-math-asinh") "asinh")
      ((= name "js-math-acosh") "acosh")
      ((= name "js-math-atanh") "atanh")
      ((= name "js-math-exp") "exp")
      ((= name "js-math-log") "log")
      ((= name "js-math-log2") "log2")
      ((= name "js-math-log10") "log10")
      ((= name "js-math-expm1") "expm1")
      ((= name "js-math-log1p") "log1p")
      ((= name "js-math-clz32") "clz32")
      ((= name "js-math-imul") "imul")
      ((= name "js-math-fround") "fround")
      ((= 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-coerce-this-arg
  (fn
    (v)
    (cond
      ((js-undefined? v) js-global-this)
      ((= v nil) js-global-this)
      ((or (= (type-of v) "number") (= (type-of v) "rational"))
        (js-new-call Number (js-args v)))
      ((= (type-of v) "string") (js-new-call String (js-args v)))
      ((= (type-of v) "boolean") (js-new-call Boolean (js-args v)))
      (else v))))

(define
  js-call-this-coerce
  (fn
    (recv v)
    (cond
      ((or (= (type-of recv) "lambda") (= (type-of recv) "component"))
        (js-coerce-this-arg v))
      (else v))))

(define
  js-invoke-function-method
  (fn
    (recv key args)
    (cond
      ((= key "call")
        (let
          ((raw-this (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 (js-call-this-coerce recv raw-this) recv rest)))
      ((= key "apply")
        (let
          ((raw-this (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 (js-call-this-coerce recv raw-this) recv rest))))
      ((= key "bind")
        (cond
          ((not (js-function? recv))
            (raise (js-new-call TypeError (js-args "Function.prototype.bind: target is not callable"))))
          (else
            (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)))))
              (let
                ((target-len (js-fn-length recv)))
                (let
                  ((bound-len
                     (let ((d (- target-len (len bound))))
                       (if (< d 0) 0 d))))
                  {:__callable__
                    (fn
                      (&rest more)
                      (js-call-with-this this-arg recv (js-list-concat bound more)))
                    :length bound-len
                    :name "bound"
                    :__js_bound_target__ recv}))))))
      ((= key "toString")
        (let
          ((override (js-dict-get-walk (get js-function-global "prototype") "toString")))
          (if
            (= (type-of override) "lambda")
            (js-call-with-this recv override args)
            "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
        (let
          ((m (js-dict-get-walk (get Number "prototype") (js-to-string key))))
          (cond
            ((js-undefined? m)
              (error
                (str
                  "TypeError: "
                  (js-to-string key)
                  " is not a function (on number)")))
            (else (js-call-with-this recv m args))))))))

(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
        (let
          ((m (js-dict-get-walk (get Boolean "prototype") (js-to-string key))))
          (cond
            ((js-undefined? m)
              (error
                (str
                  "TypeError: "
                  (js-to-string key)
                  " is not a function (on boolean)")))
            (else (js-call-with-this recv m args))))))))

(define
  js-num-to-str-radix
  (fn
    (n radix)
    (cond
      ((and (number? n) (js-number-is-nan n)) "NaN")
      ((= n (js-infinity-value)) "Infinity")
      ((= n (- 0 (js-infinity-value))) "-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)
    (let
      ((d (js-math-trunc (js-to-number digits))))
      (cond
        ((or (js-number-is-nan d) (< d 0) (> d 100))
          (raise
            (js-new-call RangeError
              (js-args "toFixed() digits argument must be between 0 and 100"))))
        ((js-number-is-nan n) "NaN")
        ((= n (js-infinity-value)) "Infinity")
        ((= n (- 0 (js-infinity-value))) "-Infinity")
        ((or (>= n 1e21) (<= n -1e21)) (js-number-to-string n))
        (else
          (cond
            ((< d 1) (js-to-string (js-math-round n)))
            (else
              (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)))
      (if
        (not (js-function? callable))
        (raise
          (js-new-call
            TypeError
            (list (str (type-of fn-val) " is not a function"))))
        (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)
        (raise (js-new-call TypeError (list "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)
    (cond
      ((not (js-function? ctor))
        (raise (js-new-call TypeError (list (str (type-of ctor) " is not a constructor")))))
      (else
        (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))
                  (or (= (type-of ret) "dict") (= (type-of ret) "list") (js-function? ret)))
                ret
                obj))))))))

;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs).
(define
  js-instanceof
  (fn
    (obj ctor)
    (cond
      ((not (js-function? ctor))
        (error "TypeError: Right-hand side of instanceof is not callable"))
      ((js-function? obj)
        (let
          ((proto (js-get-ctor-proto ctor))
            (fnproto (get js-function-global "prototype"))
            (objproto (get Object "prototype")))
          (cond
            ((= proto fnproto) true)
            ((= proto objproto) true)
            ((and (= (type-of obj) "dict") (contains? (keys obj) "__proto__"))
              (js-instanceof-walk obj proto))
            (else false))))
      ((list? obj)
        (let
          ((proto (js-get-ctor-proto ctor))
            (arrproto (get Array "prototype"))
            (objproto (get Object "prototype")))
          (cond
            ((= proto arrproto) true)
            ((= proto objproto) true)
            (else false))))
      ((not (= (type-of obj) "dict")) false)
      (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
  js-error-init!
  (fn
    (this name args)
    (begin
      (dict-set!
        this
        "message"
        (if (= (len args) 0) "" (js-to-string (nth args 0))))
      (dict-set! this "name" name)
      (dict-set! this "__js_error_data__" true)
      this)))

(define
  js-error-receiver
  (fn
    (ctor)
    (let
      ((this (js-this)))
      (cond
        ((= (type-of this) "dict") this)
        (else (js-new-call ctor (list)))))))

(define
  Error
  (fn
    (&rest args)
    (js-error-init! (js-error-receiver Error) "Error" args)))

(define
  js-error-is-error
  (fn
    (&rest args)
    (cond
      ((= (len args) 0) false)
      (else
        (let
          ((v (nth args 0)))
          (and
            (= (type-of v) "dict")
            (or
              (contains? (keys v) "__js_error_data__")
              (js-error-proto-walk? v))))))))

(define
  js-error-proto-walk?
  (fn
    (v)
    (cond
      ((not (= (type-of v) "dict")) false)
      ((contains? (keys v) "__js_error_data__") true)
      ((not (contains? (keys v) "__proto__")) false)
      (else (js-error-proto-walk? (get v "__proto__"))))))

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

(define
  TypeError
  (fn (&rest args)
    (js-error-init! (js-error-receiver TypeError) "TypeError" args)))
(define
  RangeError
  (fn (&rest args)
    (js-error-init! (js-error-receiver RangeError) "RangeError" args)))
(define
  SyntaxError
  (fn (&rest args)
    (js-error-init! (js-error-receiver SyntaxError) "SyntaxError" args)))
(define
  ReferenceError
  (fn (&rest args)
    (js-error-init! (js-error-receiver ReferenceError) "ReferenceError" args)))
(define
  URIError
  (fn (&rest args)
    (js-error-init! (js-error-receiver URIError) "URIError" args)))
(define
  EvalError
  (fn (&rest args)
    (js-error-init! (js-error-receiver EvalError) "EvalError" args)))

(define AggregateError :js-undefined)

(define SuppressedError :js-undefined)

(define
  RegExp
  {:length 2
    :name "RegExp"
    :__callable__
      (fn
        (&rest args)
        (let
          ((this (js-this)))
          (let
            ((pattern-arg (if (= (len args) 0) "" (nth args 0)))
              (flags-arg
                (if (>= (len args) 2) (nth args 1) :js-undefined)))
            (let
              ((src
                 (cond
                   ((js-regex? pattern-arg) (get pattern-arg "source"))
                   ((js-undefined? pattern-arg) "")
                   ((= pattern-arg nil) "")
                   (else (js-to-string pattern-arg))))
                (fl
                 (cond
                   ((js-undefined? flags-arg)
                     (if (js-regex? pattern-arg) (get pattern-arg "flags") ""))
                   ((= flags-arg nil) "")
                   (else (js-to-string flags-arg)))))
              (let
                ((rx (js-regex-new src fl)))
                (cond
                  ((not (= (type-of this) "dict")) rx)
                  (else
                    (begin
                      (for-each
                        (fn (k) (dict-set! this k (get rx k)))
                        (keys rx))
                      this))))))))
    :prototype
      {:test
         (fn (s)
           (let ((rx (js-this)) (str (js-to-string s)))
             (js-regex-stub-test rx str)))
        :exec
         (fn (s)
           (let ((rx (js-this)) (str (js-to-string s)))
             (js-regex-stub-exec rx str)))
        :toString
         (fn ()
           (let ((rx (js-this)))
             (str "/" (get rx "source") "/" (get rx "flags"))))
        :compile
         (fn (&rest args)
           (let ((rx (js-this)))
             (cond
               ((>= (len args) 1)
                 (let
                   ((src (js-to-string (nth args 0)))
                     (fl (if (>= (len args) 2) (js-to-string (nth args 1)) "")))
                   (let
                     ((rx2 (js-regex-new src fl)))
                     (begin
                       (for-each
                         (fn (k) (dict-set! rx k (get rx2 k)))
                         (keys rx2))
                       rx))))
               (else rx))))}})

(define
  js-str-startswith?
  (fn
    (s prefix)
    (cond
      ((< (len s) (len prefix)) false)
      (else (= (js-string-slice s 0 (len prefix)) prefix)))))

(define
  js-wrap-exn
  (fn
    (e)
    (cond
      ((not (= (type-of e) "string")) e)
      ((js-str-startswith? e "Undefined symbol:")
        (js-new-call ReferenceError (js-args e)))
      ((js-str-startswith? e "TypeError:")
        (js-new-call TypeError (js-args (js-string-slice e 11 (len e)))))
      ((js-str-startswith? e "RangeError:")
        (js-new-call RangeError (js-args (js-string-slice e 12 (len e)))))
      ((js-str-startswith? e "SyntaxError:")
        (js-new-call SyntaxError (js-args (js-string-slice e 13 (len e)))))
      ((js-str-startswith? e "ReferenceError:")
        (js-new-call ReferenceError (js-args (js-string-slice e 16 (len e)))))
      ((js-str-startswith? e "URIError:")
        (js-new-call URIError (js-args (js-string-slice e 10 (len e)))))
      (else e))))

(define
  js-date-setter-arg
  (fn
    (args fallback i)
    (cond
      ((>= (len args) (+ i 1)) (js-to-number (nth args i)))
      (else fallback))))

(define
  js-date-setter
  (fn
    (d field args)
    (cond
      ((or (not (dict? d)) (not (contains? (keys d) "__js_is_date__")))
        (raise (js-new-call TypeError (js-args "this is not a Date object"))))
      (else
        (let
          ((ms-raw (get d "__date_value__")))
          (let
            ((ms-orig
               (cond
                 ((or (= ms-raw nil) (js-undefined? ms-raw)) (js-nan-value))
                 ((= (type-of ms-raw) "rational") (exact->inexact ms-raw))
                 (else ms-raw))))
            (let
              ((parts (js-date-decompose ms-orig)))
              (let
                ((y
                   (cond
                     ((= field "fullYear") (js-date-setter-arg args (js-nan-value) 0))
                     (else (nth parts 0))))
                  (mo
                    (cond
                      ((= field "fullYear") (js-date-setter-arg args (nth parts 1) 1))
                      ((= field "month") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 1))))
                  (da
                    (cond
                      ((= field "fullYear") (js-date-setter-arg args (nth parts 2) 2))
                      ((= field "month") (js-date-setter-arg args (nth parts 2) 1))
                      ((= field "date") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 2))))
                  (hh
                    (cond
                      ((= field "hours") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 3))))
                  (mm
                    (cond
                      ((= field "hours") (js-date-setter-arg args (nth parts 4) 1))
                      ((= field "minutes") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 4))))
                  (ss
                    (cond
                      ((= field "hours") (js-date-setter-arg args (nth parts 5) 2))
                      ((= field "minutes") (js-date-setter-arg args (nth parts 5) 1))
                      ((= field "seconds") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 5))))
                  (msv
                    (cond
                      ((= field "hours") (js-date-setter-arg args (nth parts 6) 3))
                      ((= field "minutes") (js-date-setter-arg args (nth parts 6) 2))
                      ((= field "seconds") (js-date-setter-arg args (nth parts 6) 1))
                      ((= field "ms") (js-date-setter-arg args (js-nan-value) 0))
                      (else (nth parts 6)))))
                (cond
                  ((or (js-number-is-nan y) (js-number-is-nan mo) (js-number-is-nan da)
                       (js-number-is-nan hh) (js-number-is-nan mm) (js-number-is-nan ss) (js-number-is-nan msv))
                    (begin (dict-set! d "__date_value__" (js-nan-value)) (js-nan-value)))
                  (else
                    (let
                      ((days (js-date-civil-to-days (js-num-to-int y) (+ (js-num-to-int mo) 1) (js-num-to-int da)))
                        (tod
                          (+
                            (* (js-num-to-int hh) 3600000)
                            (* (js-num-to-int mm) 60000)
                            (* (js-num-to-int ss) 1000)
                            (js-num-to-int msv))))
                      (let
                        ((new-ms (+ (* days 86400000) tod)))
                        (cond
                          ((or (> new-ms 8640000000000000) (< new-ms -8640000000000000))
                            (begin (dict-set! d "__date_value__" (js-nan-value)) (js-nan-value)))
                          (else
                            (begin (dict-set! d "__date_value__" new-ms) new-ms)))))))))))))))

(define
  js-date-decompose
  (fn
    (ms)
    (cond
      ((or (= ms nil) (js-undefined? ms) (not (number? ms)) (js-number-is-nan ms))
        (list 1970 0 1 0 0 0 0))
      (else
        (let
          ((days (floor (/ ms 86400000)))
            (tod
              (let ((m (modulo (js-num-to-int ms) 86400000)))
                (if (< m 0) (+ m 86400000) m))))
          (let
            ((ymd (js-date-days-to-ymd days)))
            (list
              (nth ymd 0)
              (- (nth ymd 1) 1)
              (nth ymd 2)
              (js-math-trunc (/ tod 3600000))
              (js-math-trunc (/ (modulo tod 3600000) 60000))
              (js-math-trunc (/ (modulo tod 60000) 1000))
              (modulo tod 1000))))))))

(define
  js-date-time-value
  (fn
    (d)
    (cond
      ((or (not (dict? d)) (not (contains? (keys d) "__js_is_date__")))
        (raise (js-new-call TypeError (js-args "this is not a Date object"))))
      (else (get d "__date_value__")))))

(define
  js-date-getter
  (fn
    (d field)
    (let
      ((ms-raw (js-date-time-value d)))
      (let
        ((ms (if (= (type-of ms-raw) "rational") (exact->inexact ms-raw) ms-raw)))
        (cond
          ((or (= ms nil) (js-undefined? ms) (not (number? ms)))
            (js-nan-value))
          ((js-number-is-nan ms) (js-nan-value))
          (else
            (let
              ((days (floor (/ ms 86400000)))
                (tod
                  (let ((m (modulo (js-num-to-int ms) 86400000)))
                    (if (< m 0) (+ m 86400000) m))))
              (cond
                ((= field "ms") (modulo tod 1000))
                ((= field "seconds") (js-math-trunc (/ (modulo tod 60000) 1000)))
                ((= field "minutes") (js-math-trunc (/ (modulo tod 3600000) 60000)))
                ((= field "hours") (js-math-trunc (/ tod 3600000)))
                ((= field "day")
                  (let ((dow (modulo (+ days 4) 7)))
                    (if (< dow 0) (+ dow 7) dow)))
                (else
                  (let ((ymd (js-date-days-to-ymd days)))
                    (cond
                      ((= field "year") (nth ymd 0))
                      ((= field "month") (- (nth ymd 1) 1))
                      ((= field "date") (nth ymd 2))
                      (else (js-nan-value)))))))))))))

(define
  js-date-from-one
  (fn
    (v)
    (cond
      ((number? v) v)
      ((= (type-of v) "rational") (exact->inexact v))
      ((= (type-of v) "string") (js-date-parse-string v))
      ((js-undefined? v) (js-nan-value))
      (else (js-to-number v)))))

(define
  js-date-parse-string
  (fn
    (s)
    (cond
      ((>= (len s) 4)
        (let
          ((year-part (js-string-slice s 0 4)))
          (cond
            ((js-is-numeric-string? year-part)
              (let
                ((y (js-num-to-int (js-string-to-number year-part))))
                (* (- y 1970) 31557600000)))
            (else 0))))
      (else 0))))

(define
  js-date-from-parts
  (fn
    (args)
    (let
      ((year-raw (js-num-to-int (js-to-number (nth args 0)))))
      (let
        ((year (if (and (>= year-raw 0) (<= year-raw 99)) (+ year-raw 1900) year-raw))
          (month
           (if (>= (len args) 2) (js-num-to-int (js-to-number (nth args 1))) 0))
          (day
           (if (>= (len args) 3) (js-num-to-int (js-to-number (nth args 2))) 1))
          (hour
           (if (>= (len args) 4) (js-num-to-int (js-to-number (nth args 3))) 0))
          (mins
           (if (>= (len args) 5) (js-num-to-int (js-to-number (nth args 4))) 0))
          (secs
           (if (>= (len args) 6) (js-num-to-int (js-to-number (nth args 5))) 0))
          (ms
           (if (>= (len args) 7) (js-num-to-int (js-to-number (nth args 6))) 0)))
        (let
          ((days (js-date-civil-to-days year (+ month 1) day)))
          (+
            (* days 86400000)
            (* hour 3600000)
            (* mins 60000)
            (* secs 1000)
            ms))))))

(define
  js-date-args-have-nan?
  (fn
    (args i)
    (cond
      ((>= i (len args)) false)
      (else
        (let
          ((n (js-to-number (nth args i))))
          (cond
            ((or (number? n) (= (type-of n) "rational"))
              (let
                ((nf (if (= (type-of n) "rational") (exact->inexact n) n)))
                (cond
                  ((js-number-is-nan nf) true)
                  ((= nf (js-infinity-value)) true)
                  ((= nf (- 0 (js-infinity-value))) true)
                  (else (js-date-args-have-nan? args (+ i 1))))))
            (else (js-date-args-have-nan? args (+ i 1)))))))))

(define
  js-date-civil-to-days
  (fn
    (y m d)
    (let
      ((y2 (if (<= m 2) (- y 1) y)))
      (let
        ((era (if (>= y2 0) (js-math-trunc (/ y2 400)) (js-math-trunc (/ (- y2 399) 400)))))
        (let
          ((yoe (- y2 (* era 400))))
          (let
            ((doy
               (+
                 (js-math-trunc (/ (+ (* 153 (if (> m 2) (- m 3) (+ m 9))) 2) 5))
                 (- d 1))))
            (let
              ((doe
                 (+
                   (* yoe 365)
                   (+
                     (- (js-math-trunc (/ yoe 4)) (js-math-trunc (/ yoe 100)))
                     doy))))
              (+ (* era 146097) (- doe 719468)))))))))

(define
  js-date-format-now
  (fn () "[Date stub]"))

(define
  js-date-pad2
  (fn (n) (if (< n 10) (str "0" (js-to-string n)) (js-to-string n))))

(define
  js-date-pad3
  (fn
    (n)
    (cond
      ((< n 10) (str "00" (js-to-string n)))
      ((< n 100) (str "0" (js-to-string n)))
      (else (js-to-string n)))))

(define
  Date
  {:length 7
    :name "Date"
    :__callable__
      (fn
        (&rest args)
        (let
          ((this (js-this)))
          (cond
            ((not (= (type-of this) "dict")) (js-date-format-now))
            (else
              (begin
                (dict-set!
                  this
                  "__date_value__"
                  (cond
                    ((= (len args) 0) 0)
                    ((= (len args) 1) (js-date-from-one (nth args 0)))
                    ((js-date-args-have-nan? args 0) (js-nan-value))
                    (else (js-date-from-parts args))))
                (dict-set! this "__js_is_date__" true)
                this)))))
    :now (fn () 0)
    :parse (fn (s) (js-date-parse-string (js-to-string s)))
    :UTC
      (fn
        (&rest args)
        (cond
          ((= (len args) 0) (js-nan-value))
          ((js-date-args-have-nan? args 0) (js-nan-value))
          (else
            (let
              ((ms (js-date-from-parts args)))
              (cond
                ((or (> ms 8640000000000000) (< ms -8640000000000000))
                  (js-nan-value))
                (else ms))))))
    :prototype
      {:getTime (fn () (js-date-time-value (js-this)))
        :valueOf (fn () (js-date-time-value (js-this)))
        :getFullYear (fn () (js-date-getter (js-this) "year"))
        :getUTCFullYear (fn () (js-date-getter (js-this) "year"))
        :getMonth (fn () (js-date-getter (js-this) "month"))
        :getUTCMonth (fn () (js-date-getter (js-this) "month"))
        :getDate (fn () (js-date-getter (js-this) "date"))
        :getUTCDate (fn () (js-date-getter (js-this) "date"))
        :getDay (fn () (js-date-getter (js-this) "day"))
        :getUTCDay (fn () (js-date-getter (js-this) "day"))
        :getHours (fn () (js-date-getter (js-this) "hours"))
        :getUTCHours (fn () (js-date-getter (js-this) "hours"))
        :getMinutes (fn () (js-date-getter (js-this) "minutes"))
        :getUTCMinutes (fn () (js-date-getter (js-this) "minutes"))
        :getSeconds (fn () (js-date-getter (js-this) "seconds"))
        :getUTCSeconds (fn () (js-date-getter (js-this) "seconds"))
        :getMilliseconds (fn () (js-date-getter (js-this) "ms"))
        :getUTCMilliseconds (fn () (js-date-getter (js-this) "ms"))
        :getTimezoneOffset (fn () 0)
        :setTime
          (fn (v)
            (let ((t (js-this)))
              (let
                ((n (js-to-number v)))
                (cond
                  ((or (js-number-is-nan n) (> n 8640000000000000) (< n -8640000000000000))
                    (begin (dict-set! t "__date_value__" (js-nan-value)) (js-nan-value)))
                  (else (begin (dict-set! t "__date_value__" n) n))))))
        :setFullYear (fn (&rest args) (js-date-setter (js-this) "fullYear" args))
        :setUTCFullYear (fn (&rest args) (js-date-setter (js-this) "fullYear" args))
        :setMonth (fn (&rest args) (js-date-setter (js-this) "month" args))
        :setUTCMonth (fn (&rest args) (js-date-setter (js-this) "month" args))
        :setDate (fn (&rest args) (js-date-setter (js-this) "date" args))
        :setUTCDate (fn (&rest args) (js-date-setter (js-this) "date" args))
        :setHours (fn (&rest args) (js-date-setter (js-this) "hours" args))
        :setUTCHours (fn (&rest args) (js-date-setter (js-this) "hours" args))
        :setMinutes (fn (&rest args) (js-date-setter (js-this) "minutes" args))
        :setUTCMinutes (fn (&rest args) (js-date-setter (js-this) "minutes" args))
        :setSeconds (fn (&rest args) (js-date-setter (js-this) "seconds" args))
        :setUTCSeconds (fn (&rest args) (js-date-setter (js-this) "seconds" args))
        :setMilliseconds (fn (&rest args) (js-date-setter (js-this) "ms" args))
        :setUTCMilliseconds (fn (&rest args) (js-date-setter (js-this) "ms" args))
        :toISOString (fn () (js-date-iso (js-this)))
        :toJSON (fn () (js-date-iso (js-this)))
        :toString (fn () (js-date-iso (js-this)))
        :toUTCString (fn () (js-date-iso (js-this)))
        :toDateString (fn () (js-date-iso (js-this)))
        :toTimeString (fn () "00:00:00 GMT+0000")
        :toLocaleString (fn () (js-date-iso (js-this)))
        :toLocaleDateString (fn () (js-date-iso (js-this)))
        :toLocaleTimeString (fn () "00:00:00")}})

(define
  js-date-iso
  (fn
    (d)
    (cond
      ((or (not (dict? d)) (not (contains? (keys d) "__js_is_date__")))
        (raise (js-new-call TypeError (js-args "this is not a Date object"))))
      (else
        (let
          ((ms-raw (get d "__date_value__")))
          (let
            ((ms (if (= (type-of ms-raw) "rational") (exact->inexact ms-raw) ms-raw)))
            (cond
              ((or (= ms nil) (js-undefined? ms))
                (raise (js-new-call RangeError (js-args "Invalid time value"))))
              ((not (number? ms))
                (raise (js-new-call RangeError (js-args "Invalid time value"))))
              ((js-number-is-nan ms)
                (raise (js-new-call RangeError (js-args "Invalid time value"))))
              ((or (> ms 8640000000000000) (< ms -8640000000000000))
                (raise (js-new-call RangeError (js-args "Invalid time value"))))
              (else (js-date-iso-format ms)))))))))

(define
  js-date-iso-format
  (fn
    (ms)
    (let
      ((day-ms 86400000) (sec-ms 1000) (min-ms 60000) (hr-ms 3600000))
      (let
        ((days (floor (/ ms day-ms)))
          (time-of-day
            (let ((m (modulo (js-num-to-int ms) day-ms)))
              (if (< m 0) (+ m day-ms) m))))
        (let
          ((hh (js-math-trunc (/ time-of-day hr-ms)))
            (mm (js-math-trunc (/ (modulo time-of-day hr-ms) min-ms)))
            (ss (js-math-trunc (/ (modulo time-of-day min-ms) sec-ms)))
            (msec (modulo time-of-day sec-ms))
            (ymd (js-date-days-to-ymd days)))
          (let
            ((y (nth ymd 0)) (mo (nth ymd 1)) (d (nth ymd 2)))
            (str
              (js-date-iso-year y)
              "-"
              (js-pad2 mo)
              "-"
              (js-pad2 d)
              "T"
              (js-pad2 hh)
              ":"
              (js-pad2 mm)
              ":"
              (js-pad2 ss)
              "."
              (js-pad3 msec)
              "Z")))))))

(define
  js-date-iso-year
  (fn
    (y)
    (cond
      ((or (< y 0) (> y 9999))
        (let
          ((sign (if (< y 0) "-" "+"))
            (ay (if (< y 0) (- 0 y) y)))
          (str sign (js-date-year-6 ay))))
      ((< y 10) (str "000" (js-to-string y)))
      ((< y 100) (str "00" (js-to-string y)))
      ((< y 1000) (str "0" (js-to-string y)))
      (else (js-to-string y)))))

(define
  js-date-year-6
  (fn
    (y)
    (cond
      ((< y 10) (str "00000" (js-to-string y)))
      ((< y 100) (str "0000" (js-to-string y)))
      ((< y 1000) (str "000" (js-to-string y)))
      ((< y 10000) (str "00" (js-to-string y)))
      ((< y 100000) (str "0" (js-to-string y)))
      (else (js-to-string y)))))

(define js-pad2 (fn (n) (if (< n 10) (str "0" (js-to-string n)) (js-to-string n))))

(define
  js-pad3
  (fn
    (n)
    (cond
      ((< n 10) (str "00" (js-to-string n)))
      ((< n 100) (str "0" (js-to-string n)))
      (else (js-to-string n)))))

(define
  js-date-days-to-ymd
  (fn
    (days-since-epoch)
    (let
      ((d (+ days-since-epoch 719468)))
      (let
        ((era (if (>= d 0) (js-math-trunc (/ d 146097)) (js-math-trunc (/ (- d 146096) 146097)))))
        (let
          ((doe (- d (* era 146097))))
          (let
            ((yoe
               (js-math-trunc
                 (/
                   (-
                     (+
                       (- doe (js-math-trunc (/ doe 1460)))
                       (js-math-trunc (/ doe 36524)))
                     (js-math-trunc (/ doe 146096)))
                   365))))
            (let
              ((y (+ yoe (* era 400)))
                (doy
                  (-
                    doe
                    (+
                      (* yoe 365)
                      (-
                        (js-math-trunc (/ yoe 4))
                        (js-math-trunc (/ yoe 100)))))))
              (let
                ((mp (js-math-trunc (/ (+ (* 5 doy) 2) 153))))
                (let
                  ((day (- (+ doy 1) (js-math-trunc (/ (+ (* 153 mp) 2) 5))))
                    (month (if (< mp 10) (+ mp 3) (- mp 9))))
                  (list
                    (if (<= month 2) (+ y 1) y)
                    month
                    day))))))))))

(define
  js-date-year-pad
  (fn
    (y)
    (cond
      ((>= y 0)
        (cond
          ((< y 10) (str "000" (js-to-string y)))
          ((< y 100) (str "00" (js-to-string y)))
          ((< y 1000) (str "0" (js-to-string y)))
          (else (js-to-string y))))
      (else (str "-" (js-date-year-pad (- 0 y)))))))
(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! p "__proto__" (get Object "prototype"))
                  (dict-set! p "constructor" ctor)
                  (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) "rational") "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-object-tostring-class
  (fn
    (v)
    (cond
      ((js-undefined? v) "[object Undefined]")
      ((= v nil) "[object Null]")
      ((= (type-of v) "list") "[object Array]")
      ((= (type-of v) "string") "[object String]")
      ((= (type-of v) "number") "[object Number]")
      ((= (type-of v) "rational") "[object Number]")
      ((= (type-of v) "boolean") "[object Boolean]")
      ((or (= (type-of v) "lambda") (= (type-of v) "function") (= (type-of v) "component"))
        "[object Function]")
      ((= (type-of v) "dict")
        (cond
          ((contains? (keys v) "__callable__") "[object Function]")
          ((contains? (keys v) "__js_string_value__") "[object String]")
          ((contains? (keys v) "__js_number_value__") "[object Number]")
          ((contains? (keys v) "__js_boolean_value__") "[object Boolean]")
          ((contains? (keys v) "__js_error_data__") "[object Error]")
          ((contains? (keys v) "__js_is_date__") "[object Date]")
          ((contains? (keys v) "__map_keys__") "[object Map]")
          ((contains? (keys v) "__set_items__") "[object Set]")
          ((= v (get Number "prototype")) "[object Number]")
          ((= v (get String "prototype")) "[object String]")
          ((= v (get Boolean "prototype")) "[object Boolean]")
          ((= v (get Array "prototype")) "[object Array]")
          (else "[object Object]")))
      (else "[object Object]"))))

(define
  js-to-boolean
  (fn
    (v)
    (cond
      ((js-undefined? v) false)
      ((= v nil) false)
      ((= v false) false)
      ((= v 0) false)
      ((and (= (type-of v) "number") (js-number-is-nan v)) 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) "rational") (exact->inexact v))
      ((= (type-of v) "string") (js-string-to-number v))
      ((or (= (type-of v) "lambda") (= (type-of v) "function") (= (type-of v) "component"))
        (js-nan-value))
      ((= (type-of v) "list") (if (= (len v) 0) 0 (if (= (len v) 1) (js-to-number (nth v 0)) (js-nan-value))))
      ((= (type-of v) "dict")
        (cond
          ((contains? (keys v) "__js_number_value__")
            (get v "__js_number_value__"))
          ((contains? (keys v) "__js_boolean_value__")
            (if (get v "__js_boolean_value__") 1 0))
          ((contains? (keys v) "__js_string_value__")
            (js-string-to-number (get v "__js_string_value__")))
          (else
            (let
              ((valueof-fn (js-get-prop v "valueOf")))
              (if
                (= (type-of valueof-fn) "lambda")
                (let
                  ((result (js-call-with-this v valueof-fn ())))
                  (if
                    (and (not (= (type-of result) "dict")) (not (js-function? result)))
                    (js-to-number result)
                    (let
                      ((tostr-fn (js-get-prop v "toString")))
                      (if
                        (= (type-of tostr-fn) "lambda")
                        (let
                          ((result2 (js-call-with-this v tostr-fn ())))
                          (if
                            (and (not (= (type-of result2) "dict")) (not (js-function? result2)))
                            (js-to-number result2)
                            (raise (js-new-call TypeError (list "Cannot convert object to primitive value")))))
                        (js-nan-value)))))
                (js-nan-value))))))
      (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
                ((parsed (string->number trimmed)))
                (if
                  (= parsed nil)
                  (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 (pow 10 e))))
                  parsed))
              (js-parse-decimal
                trimmed
                0
                0
                1
                false
                0))))))))

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

(define
  js-make-list
  (fn
    (&rest args)
    (let
      ((r (list)))
      (begin
        (for-each (fn (x) (append! r x)) args)
        r))))

(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)
    (let
      ((cc (char-code c)))
      (or
        (= cc 9)
        (= cc 10)
        (= cc 11)
        (= cc 12)
        (= cc 13)
        (= cc 32)
        (= cc 160)
        (= cc 5760)
        (and (>= cc 8192) (<= cc 8202))
        (= cc 8232)
        (= cc 8233)
        (= cc 8239)
        (= cc 8287)
        (= cc 12288)
        (= cc 65279)))))

(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))
      ((= (type-of v) "rational") (js-number-to-string (exact->inexact v)))
      (else
        (if
          (= (type-of v) "dict")
          (cond
            ((contains? (keys v) "__js_string_value__")
              (get v "__js_string_value__"))
            ((contains? (keys v) "__js_number_value__")
              (js-number-to-string (get v "__js_number_value__")))
            ((contains? (keys v) "__js_boolean_value__")
              (if (get v "__js_boolean_value__") "true" "false"))
            (else
              (let
                ((tostr-fn (js-get-prop v "toString")))
                (if
                  (= (type-of tostr-fn) "lambda")
                  (let
                    ((result (js-call-with-this v tostr-fn ())))
                    (if
                      (or (= (type-of result) "dict") (js-function? result))
                      (let
                        ((valueof-fn (js-get-prop v "valueOf")))
                        (if
                          (= (type-of valueof-fn) "lambda")
                          (let
                            ((result2 (js-call-with-this v valueof-fn ())))
                            (if
                              (or (= (type-of result2) "dict") (js-function? result2))
                              (raise
                                (js-new-call
                                  TypeError
                                  (list
                                    "Cannot convert object to primitive value")))
                              (js-to-string result2)))
                          "[object Object]"))
                      (js-to-string result)))
                  "[object Object]"))))
          (cond
            ((= (type-of v) "list")
              (let
                ((tostr-fn (js-dict-get-walk (get Array "prototype") "toString")))
                (if
                  (= (type-of tostr-fn) "lambda")
                  (let
                    ((result (js-call-with-this v tostr-fn ())))
                    (if (= (type-of result) "string") result (js-list-join v ",")))
                  (js-list-join v ","))))
            ((js-function? v)
              (let
                ((tostr-fn (js-dict-get-walk (get js-function-global "prototype") "toString")))
                (if
                  (= (type-of tostr-fn) "lambda")
                  (let
                    ((result (js-call-with-this v tostr-fn ())))
                    (if (= (type-of result) "string") result "function () { [native code] }"))
                  "function () { [native code] }")))
            (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-big-int-str-loop
  (fn
    (n acc)
    (if
      (< n 1)
      (if (= acc "") "0" acc)
      (let
        ((d (floor (- n (* 10 (floor (/ n 10)))))))
        (js-big-int-str-loop
          (floor (/ n 10))
          (str (js-string-slice "0123456789" d (+ d 1)) acc))))))

(define
  js-find-decimal-k
  (fn
    (n k)
    (if
      (> k 17)
      17
      (let
        ((big-int (round (* n (js-pow-int 10 k)))))
        (if
          (= (/ big-int (js-pow-int 10 k)) n)
          k
          (js-find-decimal-k n (+ k 1)))))))

(define
  js-format-decimal-digits
  (fn
    (digits k)
    (if
      (= k 0)
      digits
      (let
        ((dlen (len digits)))
        (if
          (> dlen k)
          (str
            (js-string-slice digits 0 (- dlen k))
            "."
            (js-string-slice digits (- dlen k) dlen))
          (if
            (= dlen k)
            (str "0." digits)
            (str "0." (js-string-repeat "0" (- k dlen)) digits)))))))

(define
  js-expand-sci-notation
  (fn
    (mant exp-n)
    (let
      ((di (js-string-index-of mant "." 0)))
      (let
        ((int-part (if (< di 0) mant (js-string-slice mant 0 di)))
          (frac-part
            (if
              (< di 0)
              ""
              (js-string-slice mant (+ di 1) (len mant)))))
        (let
          ((all-digits (str int-part frac-part))
            (frac-len
              (if
                (< di 0)
                0
                (- (- (len mant) di) 1))))
          (if
            (>= exp-n 0)
            (if
              (>= exp-n frac-len)
              (str all-digits (js-string-repeat "0" (- exp-n frac-len)))
              (let
                ((dot-pos (+ (len int-part) exp-n)))
                (str
                  (js-string-slice all-digits 0 dot-pos)
                  "."
                  (js-string-slice all-digits dot-pos (len all-digits)))))
            (str
              "0."
              (js-string-repeat "0" (- (- 0 exp-n) 1))
              all-digits)))))))

(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
        (let
          ((pos-n (if (< n 0) (- 0 n) n)))
          (let
            ((s0 (js-normalize-num-str (str pos-n))))
            (let
              ((n2 (js-to-number s0)))
              (let
                ((precise (if (= n2 pos-n) (let ((ei (js-string-index-of s0 "e" 0))) (if (< ei 0) s0 (let ((exp-n (js-to-number (js-string-slice s0 (+ ei 1) (len s0))))) (if (and (>= exp-n -6) (<= exp-n 20)) (js-expand-sci-notation (js-string-slice s0 0 ei) exp-n) (if (>= exp-n 0) (str (js-string-slice s0 0 (+ ei 1)) "+" (str exp-n)) s0))))) (if (and (>= pos-n 1e-06) (< pos-n 1e+21)) (let ((k (js-find-decimal-k pos-n 0))) (let ((big-int (round (* pos-n (js-pow-int 10 k))))) (js-format-decimal-digits (js-big-int-str-loop big-int "") k))) (let ((ei (js-string-index-of s0 "e" 0))) (if (< ei 0) s0 (let ((exp-n (js-to-number (js-string-slice s0 (+ ei 1) (len s0))))) (if (>= exp-n 0) (str (js-string-slice s0 0 (+ ei 1)) "+" (str exp-n)) s0))))))))
                (if (< n 0) (str "-" precise) precise)))))))))

(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-unwrap
  (fn
    (v)
    (cond
      ((or (= (type-of v) "lambda") (= (type-of v) "function") (= (type-of v) "component"))
        (let ((s (js-to-string v))) s))
      ((not (= (type-of v) "dict")) v)
      ((contains? (keys v) "__js_string_value__")
        (get v "__js_string_value__"))
      ((contains? (keys v) "__js_number_value__")
        (get v "__js_number_value__"))
      ((contains? (keys v) "__js_boolean_value__")
        (get v "__js_boolean_value__"))
      ((contains? (keys v) "__js_is_date__")
        (js-add-call-method v "toString"))
      (else (js-add-toprim-default v)))))

(define
  js-add-toprim-default
  (fn
    (v)
    (let
      ((via-valueof (js-add-call-method v "valueOf")))
      (cond
        ((not (= (type-of via-valueof) "dict")) via-valueof)
        (else (js-add-call-method v "toString"))))))

(define
  js-add-call-method
  (fn
    (v name)
    (let
      ((m (js-dict-get-walk v name)))
      (cond
        ((js-undefined? m) v)
        ((not (js-function? m)) v)
        (else (js-call-with-this v m (list)))))))

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

(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) (exact->inexact (js-to-number b)))))

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

(define
  js-unsigned-rshift
  (fn
    (l r)
    (let
      ((lu32 (modulo (js-math-trunc (js-to-number l)) 4294967296))
        (shift (modulo (js-math-trunc (js-to-number r)) 32)))
      (floor (/ lu32 (js-math-pow 2 shift))))))

(define
  js-to-uint32
  (fn (n) (modulo (js-math-trunc (js-to-number n)) 4294967296)))

(define
  js-uint32-to-int32
  (fn (u) (if (>= u 2147483648) (- u 4294967296) u)))

(define js-to-int32 (fn (n) (js-uint32-to-int32 (js-to-uint32 n))))

(define
  js-bitwise-loop
  (fn
    (op au bu i acc bit)
    (if
      (>= i 32)
      acc
      (let
        ((abit (modulo (floor (/ au bit)) 2))
          (bbit (modulo (floor (/ bu bit)) 2)))
        (let
          ((rbit
             (cond
               ((= op "and") (* abit bbit))
               ((= op "or") (if (or (= abit 1) (= bbit 1)) 1 0))
               ((= op "xor") (if (= abit bbit) 0 1))
               (else 0))))
          (js-bitwise-loop
            op au bu (+ i 1) (+ acc (* rbit bit)) (* bit 2)))))))

(define
  js-bitwise-binop
  (fn
    (op a b)
    (js-uint32-to-int32
      (js-bitwise-loop op (js-to-uint32 a) (js-to-uint32 b) 0 0 1))))

(define js-bitand (fn (a b) (js-bitwise-binop "and" a b)))

(define js-bitor (fn (a b) (js-bitwise-binop "or" a b)))

(define js-bitxor (fn (a b) (js-bitwise-binop "xor" a b)))

(define
  js-shl
  (fn
    (a b)
    (let
      ((au (js-to-uint32 a))
        (sh (modulo (js-math-trunc (js-to-number b)) 32)))
      (js-uint32-to-int32 (modulo (* au (js-math-pow 2 sh)) 4294967296)))))

(define
  js-shr
  (fn
    (a b)
    (let
      ((ai (js-to-int32 a))
        (sh (modulo (js-math-trunc (js-to-number b)) 32)))
      (if (= sh 0) ai (floor (/ ai (js-math-pow 2 sh)))))))

(define
  js-pow-spec
  (fn
    (b e)
    (let
      ((bn (js-to-number b)) (en (js-to-number e)))
      (let
        ((inf (js-infinity-value)) (abs-b (if (< bn 0) (- 0 bn) bn)))
        (cond
          ((js-number-is-nan en) (js-nan-value))
          ((= en 0) 1)
          ((js-number-is-nan bn) (js-nan-value))
          ((and (= abs-b 1) (or (= en inf) (= en (- 0 inf))))
            (js-nan-value))
          (else (pow bn en)))))))

(define js-pow (fn (a b) (js-pow-spec a b)))

(define js-neg (fn (a) (* -1 (exact->inexact (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-numeric-type?
  (fn (v) (or (= (type-of v) "number") (= (type-of v) "rational"))))

(define
  js-numeric-norm
  (fn (v) (if (= (type-of v) "rational") (exact->inexact v) v)))

(define
  js-strict-eq
  (fn
    (a b)
    (cond
      ((and (js-undefined? a) (js-undefined? b)) true)
      ((or (js-undefined? a) (js-undefined? b)) false)
      ((and (js-numeric-type? a) (js-numeric-type? b))
        (let
          ((an (js-numeric-norm a)) (bn (js-numeric-norm b)))
          (if (or (js-number-is-nan an) (js-number-is-nan bn)) false (= an bn))))
      ((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 (js-numeric-type? a) (= (type-of b) "string"))
        (let ((an (js-numeric-norm a)) (bn (js-to-number b)))
          (cond
            ((or (js-number-is-nan an) (js-number-is-nan bn)) false)
            (else (= an bn)))))
      ((and (= (type-of a) "string") (js-numeric-type? b))
        (let ((an (js-to-number a)) (bn (js-numeric-norm b)))
          (cond
            ((or (js-number-is-nan an) (js-number-is-nan bn)) false)
            (else (= an bn)))))
      ((= (type-of a) "boolean") (js-loose-eq (js-to-number a) b))
      ((= (type-of b) "boolean") (js-loose-eq a (js-to-number b)))
      ((and (dict? a) (contains? (keys a) "__js_string_value__"))
        (js-loose-eq (get a "__js_string_value__") b))
      ((and (dict? b) (contains? (keys b) "__js_string_value__"))
        (js-loose-eq a (get b "__js_string_value__")))
      ((and (dict? a) (contains? (keys a) "__js_number_value__"))
        (js-loose-eq (get a "__js_number_value__") b))
      ((and (dict? b) (contains? (keys b) "__js_number_value__"))
        (js-loose-eq a (get b "__js_number_value__")))
      ((and (dict? a) (contains? (keys a) "__js_boolean_value__"))
        (js-loose-eq (get a "__js_boolean_value__") b))
      ((and (dict? b) (contains? (keys b) "__js_boolean_value__"))
        (js-loose-eq a (get b "__js_boolean_value__")))
      (else false))))

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

(define
  js-lt
  (fn
    (a b)
    (let
      ((ap (js-add-unwrap a)) (bp (js-add-unwrap b)))
      (cond
        ((and (= (type-of ap) "string") (= (type-of bp) "string"))
          (js-str-lt ap bp))
        (else
          (let
            ((an (js-to-number ap)) (bn (js-to-number bp)))
            (cond
              ((or (js-number-is-nan an) (js-number-is-nan bn)) false)
              (else (< an bn)))))))))

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

(define
  js-le
  (fn
    (a b)
    (let
      ((ap (js-add-unwrap a)) (bp (js-add-unwrap b)))
      (cond
        ((and (= (type-of ap) "string") (= (type-of bp) "string"))
          (or (js-str-lt ap bp) (= ap bp)))
        (else
          (let
            ((an (js-to-number ap)) (bn (js-to-number bp)))
            (cond
              ((or (js-number-is-nan an) (js-number-is-nan bn)) false)
              (else (<= an bn)))))))))

(define js-ge (fn (a b) (js-le b a)))

(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 (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-map-loop f arr this-arg 0 (list)))))
      ((= name "filter")
        (fn (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-filter-loop f arr this-arg 0 (list)))))
      ((= name "forEach")
        (fn
          (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (begin (js-list-foreach-loop f arr this-arg 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 (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-find-loop f arr this-arg 0))))
      ((= name "findIndex")
        (fn (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-find-index-loop f arr this-arg 0))))
      ((= name "some")
        (fn (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-some-loop f arr this-arg 0))))
      ((= name "every")
        (fn (&rest args)
          (let
            ((f (if (empty? args) :js-undefined (nth args 0)))
              (this-arg
                (cond
                  ((< (len args) 2) js-global-this)
                  ((js-undefined? (nth args 1)) js-global-this)
                  ((= (nth args 1) nil) js-global-this)
                  (else (nth args 1)))))
            (js-list-every-loop f arr this-arg 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 js-global-this 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 this-arg i acc)
    (cond
      ((>= i (len arr)) acc)
      (else
        (do
          (append!
            acc
            (js-call-with-this this-arg f (list (nth arr i) i arr)))
          (js-list-map-loop f arr this-arg (+ i 1) acc))))))

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

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

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

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

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

(define
  js-list-some-loop
  (fn
    (f arr this-arg i)
    (cond
      ((>= i (len arr)) false)
      ((js-to-boolean (js-call-with-this this-arg f (list (nth arr i) i arr)))
        true)
      (else (js-list-some-loop f arr this-arg (+ 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 this-arg i)
    (cond
      ((>= i (len arr)) true)
      ((not (js-to-boolean (js-call-with-this this-arg f (list (nth arr i) i arr))))
        false)
      (else (js-list-every-loop f arr this-arg (+ 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
        (js-call-with-this js-undefined f (list acc (nth arr i) i arr))
        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)
    (js-string-repeat-loop s n "")))

(define
  js-string-repeat-loop
  (fn
    (s n acc)
    (if
      (<= n 0)
      acc
      (js-string-repeat-loop 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 (js-to-number i))))
            (if
              (and (>= idx 0) (< idx (len s)))
              (char-code (char-at s idx))
              (js-nan-value)))))
      ((= name "indexOf")
        (fn
          (&rest args)
          (if
            (empty? args)
            -1
            (js-string-index-of
              s
              (js-to-string (nth args 0))
              (if
                (< (len args) 2)
                0
                (max 0 (js-num-to-int (nth args 1))))))))
      ((= 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
          (&rest args)
          (let
            ((sep (if (= (len args) 0) :js-undefined (nth args 0)))
              (limit-raw (if (< (len args) 2) :js-undefined (nth args 1))))
            (let
              ((limit
                 (cond
                   ((js-undefined? limit-raw) -1)
                   (else (js-num-to-int (js-to-number limit-raw))))))
              (cond
                ((js-undefined? sep) (js-make-list s))
                ((= limit 0) (js-make-list))
                (else
                  (let
                    ((result (js-string-split s (js-to-string sep))))
                    (if (< limit 0) result (js-list-take result limit)))))))))
      ((= 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)
          (let
            ((nn (js-to-number n)))
            (cond
              ((or (< nn 0) (= nn (js-infinity-value)))
                (raise
                  (js-new-call RangeError
                    (js-args "Invalid count value"))))
              (else (js-string-repeat-loop s (js-num-to-int nn) ""))))))
      ((= 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))) (append! res needle) res))))))))
      ((= name "matchAll")
        (fn
          (&rest args)
          (if
            (empty? args)
            (list)
            (let
              ((needle (js-to-string (nth args 0))))
              (let
                ((loop (fn (start acc) (let ((idx (js-string-index-of s needle start))) (if (= idx -1) acc (let ((m (list))) (begin (append! m needle) (dict-set! m "index" idx) (loop (+ idx (max 1 (len needle))) (begin (append! acc m) acc)))))))))
                (loop 0 (list)))))))
      ((= 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))))
              (let
                ((default-start (- (len s) (len needle)))
                  (from
                    (if
                      (< (len args) 2)
                      -1
                      (js-num-to-int (nth args 1)))))
                (js-string-last-index-of
                  s
                  needle
                  (if
                    (< from 0)
                    default-start
                    (min from default-start))))))))
      ((= 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-list-take
  (fn
    (lst n)
    (if
      (or (<= n 0) (empty? lst))
      (list)
      (cons (first lst) (js-list-take (rest lst) (- n 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))
            ((and (= (type-of key) "string") (js-is-numeric-string? key))
              (let
                ((idx (js-num-to-int (js-string-to-number key))))
                (if
                  (and (>= idx 0) (< idx (len obj)))
                  (nth obj idx)
                  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-dict-get-walk (get Array "prototype") "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-dict-get-walk (get Array "prototype") (js-to-string key)))))
        ((= (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
              (let
                ((proto (get String "prototype")))
                (if
                  (and (dict? proto) (contains? (keys proto) key))
                  (get proto key)
                  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 (= obj Error) (= (js-to-string key) "isError"))
          js-error-is-error)
        ((and (js-function? obj) (or (= key "prototype") (= key "name") (= key "length") (= key "call") (= key "apply") (= key "bind") (= key "constructor")))
          (cond
            ((= key "prototype") (js-get-ctor-proto obj))
            ((= key "name") (js-extract-fn-name obj))
            ((= key "length") (js-fn-length obj))
            ((= key "constructor") js-function-global)
            (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)
        ((or (= (type-of obj) "lambda") (= (type-of obj) "function") (= (type-of obj) "component"))
          (js-dict-get-walk (get js-function-global "prototype") skey))
        ((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))
        ((not (= obj (get Object "prototype")))
          (js-dict-get-walk (get Object "prototype") 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)))

(define
  js-make-obj
  (fn ()
    (let ((d (dict)))
      (begin
        (dict-set! d "__js_order__" (list))
        (dict-set! d "__proto__" (get Object "prototype"))
        d))))

(define
  js-obj-order-add!
  (fn
    (obj k)
    (cond
      ((not (dict? obj)) nil)
      ((not (contains? (keys obj) "__js_order__")) nil)
      (else
        (let
          ((order (get obj "__js_order__")))
          (if (contains? order k) nil (append! order k)))))))

(define
  js-obj-order-remove!
  (fn
    (obj k)
    (cond
      ((not (dict? obj)) nil)
      ((not (contains? (keys obj) "__js_order__")) nil)
      (else
        (dict-set!
          obj
          "__js_order__"
          (filter (fn (x) (not (= x k))) (get obj "__js_order__")))))))

(define
  js-obj-set!
  (fn
    (obj key val)
    (let
      ((sk (js-to-string key)))
      (begin
        (if (not (contains? (keys obj) sk)) (js-obj-order-add! obj sk) nil)
        (dict-set! obj sk val)
        val))))

(define
  js-obj-spread!
  (fn
    (target src)
    (cond
      ((or (= src nil) (js-undefined? src)) target)
      ((dict? src)
        (begin
          (for-each
            (fn
              (k)
              (if
                (js-key-internal? k)
                nil
                (js-obj-set! target k (get src k))))
            (js-object-keys src))
          target))
      ((= (type-of src) "string")
        (let
          ((n (len src)))
          (begin (js-obj-spread-string-loop! target src 0 n) target)))
      ((list? src)
        (let
          ((n (len src)))
          (begin (js-obj-spread-list-loop! target src 0 n) target)))
      (else target))))

(define
  js-obj-spread-string-loop!
  (fn
    (target s i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-obj-set! target (js-to-string i) (char-at s i))
          (js-obj-spread-string-loop! target s (+ i 1) n))))))

(define
  js-obj-spread-list-loop!
  (fn
    (target arr i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-obj-set! target (js-to-string i) (nth arr i))
          (js-obj-spread-list-loop! target arr (+ i 1) n))))))

(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")
          (let
            ((sk (js-to-string key)))
            (begin
              (if (not (contains? (keys obj) sk)) (js-obj-order-add! obj sk) nil)
              (dict-set! obj sk val)
              val)))
        ((= (type-of obj) "list") (do (js-list-set! obj key val) val))
        ((and
           (or (= (type-of obj) "lambda") (= (type-of obj) "function") (= (type-of obj) "component"))
           (= (js-to-string key) "prototype"))
          (let
            ((id (js-ctor-id obj)))
            (begin (dict-set! __js_proto_table__ id 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 1000000) nil)
              ((< i n) (set-nth! lst i val))
              ((= i n) (append! lst val))
              (else (do (js-pad-list! lst n i) (append! lst val))))))
        ((and (= (type-of key) "string") (js-is-numeric-string? key))
          (js-list-set! lst (js-string-to-number key) val))
        ((= key "length")
          (let
            ((target (js-num-to-int (js-to-number val))) (n (len lst)))
            (cond
              ((< target 0) nil)
              ((>= target 1000000) nil)
              ((> target n) (js-pad-list! lst n target))
              (else 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)
    (let
      ((n (js-to-number x)))
      (cond
        ((js-number-is-nan n) (js-nan-value))
        ((= n (js-infinity-value)) n)
        ((= n (- 0 (js-infinity-value))) n)
        ((= n 0) n)
        (else (floor (+ n 0.5)))))))

(define
  js-math-max
  (fn
    (&rest args)
    (cond
      ((empty? args) (- 0 (js-infinity-value)))
      (else (js-math-max-loop (js-to-number (first args)) (rest args))))))

(define
  js-math-max-loop
  (fn
    (acc xs)
    (cond
      ((js-number-is-nan acc) (js-nan-value))
      ((empty? xs) acc)
      (else
        (let
          ((h (js-to-number (first xs))))
          (cond
            ((js-number-is-nan h) (js-nan-value))
            ((> h acc) (js-math-max-loop h (rest xs)))
            ((and (= h 0) (= acc 0))
              (js-math-max-loop (if (js-is-positive-zero? h) h acc) (rest xs)))
            (else (js-math-max-loop acc (rest xs)))))))))

(define
  js-math-min
  (fn
    (&rest args)
    (cond
      ((empty? args) (js-infinity-value))
      (else (js-math-min-loop (js-to-number (first args)) (rest args))))))

(define
  js-math-min-loop
  (fn
    (acc xs)
    (cond
      ((js-number-is-nan acc) (js-nan-value))
      ((empty? xs) acc)
      (else
        (let
          ((h (js-to-number (first xs))))
          (cond
            ((js-number-is-nan h) (js-nan-value))
            ((< h acc) (js-math-min-loop h (rest xs)))
            ((and (= h 0) (= acc 0))
              (js-math-min-loop (if (js-is-positive-zero? h) acc h) (rest xs)))
            (else (js-math-min-loop acc (rest xs)))))))))

(define
  js-is-positive-zero?
  (fn
    (n)
    (cond
      ((not (= n 0)) false)
      ((= (type-of n) "rational") true)
      (else (= (/ 1.0 (exact->inexact n)) (js-infinity-value))))))

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

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

(define js-math-pow (fn (a b) (js-pow-spec a 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)))
      (cond
        ((js-number-is-nan n) (js-nan-value))
        ((= n (js-infinity-value)) (js-infinity-value))
        ((= n (- 0 (js-infinity-value))) n)
        ((= n 0) n)
        ((< n 0) (- 0 (pow (- 0 n) (/ 1.0 3.0))))
        (else (pow n (/ 1.0 3.0)))))))

(define
  js-math-hypot
  (fn
    (&rest args)
    (let
      ((status (js-math-hypot-scan args false false 0)))
      (cond
        ((= (first status) "inf") (js-infinity-value))
        ((= (first status) "nan") (js-nan-value))
        (else (sqrt (nth status 1)))))))

(define
  js-math-hypot-scan
  (fn
    (args saw-inf? saw-nan? acc)
    (cond
      ((empty? args)
        (cond
          (saw-inf? (list "inf"))
          (saw-nan? (list "nan"))
          (else (list "ok" acc))))
      (else
        (let
          ((n (js-to-number (first args))))
          (cond
            ((= n (js-infinity-value))
              (js-math-hypot-scan (rest args) true saw-nan? acc))
            ((= n (- 0 (js-infinity-value)))
              (js-math-hypot-scan (rest args) true saw-nan? acc))
            ((js-number-is-nan n)
              (js-math-hypot-scan (rest args) saw-inf? true acc))
            (else
              (js-math-hypot-scan (rest args) saw-inf? saw-nan? (+ acc (* n n))))))))))

(begin
  (define js-math-sin (fn (x) (sin (js-to-number x))))
  (define js-math-cos (fn (x) (cos (js-to-number x))))
  (define js-math-tan (fn (x) (tan (js-to-number x))))
  (define js-math-asin (fn (x) (asin (js-to-number x))))
  (define js-math-acos (fn (x) (acos (js-to-number x))))
  (define js-math-atan (fn (x) (atan (js-to-number x))))
  (define
    js-math-atan2
    (fn (y x) (atan2 (js-to-number y) (js-to-number x))))
  (define js-math-sinh (fn (x) (sinh (js-to-number x))))
  (define js-math-cosh (fn (x) (cosh (js-to-number x))))
  (define js-math-tanh (fn (x) (tanh (js-to-number x))))
  (define js-math-asinh (fn (x) (asinh (js-to-number x))))
  (define js-math-acosh (fn (x) (acosh (js-to-number x))))
  (define js-math-atanh (fn (x) (atanh (js-to-number x))))
  (define js-math-exp (fn (x) (exp (js-to-number x))))
  (define js-math-log (fn (x) (log (js-to-number x))))
  (define js-math-log2 (fn (x) (log2 (js-to-number x))))
  (define js-math-log10 (fn (x) (log10 (js-to-number x))))
  (define js-math-expm1 (fn (x) (expm1 (js-to-number x))))
  (define js-math-log1p (fn (x) (log1p (js-to-number x))))
  (define
    js-math-clz32
    (fn
      (&rest args)
      (let
        ((x (if (empty? args) 0 (js-to-number (nth args 0)))))
        (let
          ((n (modulo (floor x) 4294967296)))
          (if (= n 0) 32 (- 31 (floor (log2 n))))))))
  (define
    js-math-imul
    (fn
      (a b)
      (let
        ((a32 (modulo (floor (js-to-number a)) 4294967296))
          (b32 (modulo (floor (js-to-number b)) 4294967296)))
        (let
          ((result (modulo (* a32 b32) 4294967296)))
          (if (>= result 2147483648) (- result 4294967296) result)))))
  (define js-math-fround (fn (x) (js-to-number x)))
  (define Math {:atan js-math-atan :sign js-math-sign :LN2 0.693147 :cos js-math-cos :imul js-math-imul :min js-math-min :acos js-math-acos :log10 js-math-log10 :LOG10E 0.434294 :tanh js-math-tanh :abs js-math-abs :round js-math-round :log js-math-log :sqrt js-math-sqrt :cosh js-math-cosh :tan js-math-tan :floor js-math-floor :exp js-math-exp :asin js-math-asin :clz32 js-math-clz32 :random js-math-random :LN10 2.30259 :SQRT1_2 0.707107 :sinh js-math-sinh :E 2.71828 :fround js-math-fround :cbrt js-math-cbrt :log1p js-math-log1p :SQRT2 1.41421 :max js-math-max :log2 js-math-log2 :ceil js-math-ceil :pow js-math-pow :sin js-math-sin :hypot js-math-hypot :LOG2E 1.4427 :atanh js-math-atanh :asinh js-math-asinh :acosh js-math-acosh :PI 3.14159 :atan2 js-math-atan2 :trunc js-math-trunc :expm1 js-math-expm1}))

(define
  js-number-is-finite
  (fn
    (v)
    (and
      (number? v)
      (not (js-number-is-nan v))
      (not (= v inf))
      (not (= v -inf)))))

(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
  js-number-this-val
  (fn ()
    (let ((this-val (js-this)))
      (cond
        ((or (= (type-of this-val) "number") (= (type-of this-val) "rational"))
          (js-numeric-norm this-val))
        ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_number_value__"))
          (get this-val "__js_number_value__"))
        (else
          (raise (js-new-call TypeError (js-args "Number.prototype method requires a Number"))))))))

(define Number {:MIN_SAFE_INTEGER -9007199254740991 :MIN_VALUE 4.94066e-324 :isNaN js-number-is-nan :isSafeInteger js-number-is-safe-integer :NEGATIVE_INFINITY (- 0 (js-infinity-value)) :NaN (js-nan-value) :prototype {:toFixed {:__callable__ (fn (d) (js-number-to-fixed (js-number-this-val) (if (= d nil) 0 (js-to-number d)))) :length 1 :name "toFixed"} :toExponential {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toExponential"} :toLocaleString {:__callable__ (fn () (js-number-to-string (js-number-this-val))) :length 0 :name "toLocaleString"} :toString {:__callable__ (fn (&rest args) (let ((this-val (js-number-this-val)) (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)))) :length 1 :name "toString"} :toPrecision {:__callable__ (fn (&rest args) (js-number-to-string (js-number-this-val))) :length 1 :name "toPrecision"} :valueOf {:__callable__ (fn (&rest args) (js-number-this-val)) :length 0 :name "valueOf"}} :isInteger js-number-is-integer :__callable__ js-to-number :MAX_VALUE (js-max-value-approx) :POSITIVE_INFINITY (js-infinity-value) :isFinite js-number-is-finite :MAX_SAFE_INTEGER 9007199254740991 :EPSILON 2.22045e-16})

(dict-set! Number "length" 1)

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

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

(dict-set!
  Number
  "__callable__"
  (fn
    (&rest args)
    (let
      ((raw (if (= (len args) 0) 0 (js-to-number (nth args 0)))))
      (let
        ((this-val (js-this)))
        (if
          (and
            (dict? this-val)
            (contains? (keys this-val) "__proto__")
            (= (get this-val "__proto__") (get Number "prototype")))
          (begin (dict-set! this-val "__js_number_value__" raw) this-val)
          raw)))))

(dict-set!
  (get Number "prototype")
  "valueOf"
  (fn
    ()
    (let
      ((this-val (js-this)))
      (if
        (and
          (dict? this-val)
          (contains? (keys this-val) "__js_number_value__"))
        (get this-val "__js_number_value__")
        this-val))))

(dict-set!
  (get Number "prototype")
  "toString"
  (fn
    (&rest args)
    (let
      ((this-raw (js-this)))
      (let
        ((this-val (if (and (dict? this-raw) (contains? (keys this-raw) "__js_number_value__")) (get this-raw "__js_number_value__") this-raw)))
        (let
          ((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)))))))

(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-key-internal?
  (fn
    (k)
    (or (= k "__js_order__") (= k "__proto__"))))

(define
  js-for-in-keys
  (fn
    (o)
    (let
      ((result (list)))
      (begin (js-for-in-walk o result) result))))

(define
  js-for-in-walk
  (fn
    (o acc)
    (cond
      ((not (dict? o)) nil)
      ((= o (get Object "prototype")) nil)
      ((= o (get Array "prototype")) nil)
      ((= o (get Number "prototype")) nil)
      ((= o (get String "prototype")) nil)
      ((= o (get Boolean "prototype")) nil)
      ((= o (get Date "prototype")) nil)
      ((= o (get RegExp "prototype")) nil)
      ((= o (get Map "prototype")) nil)
      ((= o (get Set "prototype")) nil)
      ((= o (get js-function-global "prototype")) nil)
      (else
        (let
          ((own (js-object-keys o)))
          (begin
            (for-each
              (fn (k) (if (contains? acc k) nil (append! acc k)))
              own)
            (cond
              ((contains? (keys o) "__proto__")
                (js-for-in-walk (get o "__proto__") acc))
              (else nil))))))))

(define
  js-object-keys
  (fn
    (o)
    (cond
      ((or (= o nil) (js-undefined? o))
        (raise (js-new-call TypeError (js-args "Object.keys called on null or undefined"))))
      ((= (type-of o) "string")
        (let ((result (list)) (n (len o)))
          (begin
            (js-string-keys-loop result 0 n)
            result)))
      ((list? o)
        (let ((result (list)) (n (len o)))
          (begin
            (js-string-keys-loop result 0 n)
            result)))
      ((dict? o)
        (cond
          ((contains? (keys o) "__js_order__")
            (let
              ((result (list)))
              (begin
                (for-each
                  (fn (k) (if (js-key-internal? k) nil (append! result k)))
                  (get o "__js_order__"))
                result)))
          (else
            (let
              ((result (list)))
              (begin
                (for-each
                  (fn (k) (if (js-key-internal? k) nil (append! result k)))
                  (keys o))
                result)))))
      (else (list)))))

(define
  js-string-keys-loop
  (fn
    (acc i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (append! acc (js-to-string i))
          (js-string-keys-loop acc (+ i 1) n))))))

(define
  js-object-values
  (fn
    (o)
    (cond
      ((or (= o nil) (js-undefined? o))
        (raise (js-new-call TypeError (js-args "Object.values called on null or undefined"))))
      ((= (type-of o) "string")
        (let ((result (list)) (n (len o)))
          (begin
            (js-string-values-loop result o 0 n)
            result)))
      ((dict? o)
        (let
          ((result (list)))
          (for-each
            (fn (k) (if (js-key-internal? k) nil (append! result (get o k))))
            (js-object-keys o))
          result))
      (else (list)))))

(define
  js-string-values-loop
  (fn
    (acc s i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (append! acc (char-at s i))
          (js-string-values-loop acc s (+ i 1) n))))))

(define
  js-object-entries
  (fn
    (o)
    (cond
      ((or (= o nil) (js-undefined? o))
        (raise (js-new-call TypeError (js-args "Object.entries called on null or undefined"))))
      ((= (type-of o) "string")
        (let ((result (list)) (n (len o)))
          (begin
            (js-string-entries-loop result o 0 n)
            result)))
      ((dict? o)
        (let
          ((result (list)))
          (for-each
            (fn
              (k)
              (if
                (js-key-internal? k)
                nil
                (let
                  ((pair (list)))
                  (begin
                    (append! pair k)
                    (append! pair (get o k))
                    (append! result pair)))))
            (js-object-keys o))
          result))
      (else (list)))))

(define
  js-string-entries-loop
  (fn
    (acc s i n)
    (cond
      ((>= i n) nil)
      (else
        (let ((pair (list)))
          (begin
            (append! pair (js-to-string i))
            (append! pair (char-at s i))
            (append! acc pair)
            (js-string-entries-loop acc s (+ i 1) n)))))))

(define
  js-object-assign
  (fn
    (&rest args)
    (cond
      ((= (len args) 0)
        (raise (js-new-call TypeError (js-args "Object.assign called on null or undefined"))))
      (else
        (let
          ((raw-target (nth args 0)))
          (cond
            ((or (= raw-target nil) (js-undefined? raw-target))
              (raise (js-new-call TypeError (js-args "Object.assign called on null or undefined"))))
            (else
              (let
                ((target (js-coerce-this-arg raw-target)))
                (for-each
                  (fn
                    (src)
                    (cond
                      ((or (= src nil) (js-undefined? src)) nil)
                      ((dict? src)
                        (for-each
                          (fn
                            (k)
                            (if (js-key-internal? k) nil (js-set-prop target k (get src k))))
                          (js-object-keys src)))
                      ((= (type-of src) "string")
                        (let
                          ((n (len src)))
                          (begin (js-object-assign-string-loop target src 0 n))))))
                  (rest args))
                target))))))))

(define
  js-object-assign-string-loop
  (fn
    (target s i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-set-prop target (js-to-string i) (char-at s i))
          (js-object-assign-string-loop target s (+ i 1) n))))))

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

(define __js_ctor_proto__ (dict))

(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)
        (cond
          ((contains? (keys o) "__proto__") (get o "__proto__"))
          (else nil)))
      ((js-function? o)
        (let
          ((id (js-ctor-id o)))
          (cond
            ((dict-has? __js_ctor_proto__ id)
              (get __js_ctor_proto__ id))
            (else 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
      ((or (= o nil) (js-undefined? o))
        (raise (js-new-call TypeError (js-args "Object.getOwnPropertyNames called on null or undefined"))))
      ((list? o)
        (let
          ((r (list)))
          (begin
            (js-list-keys-loop o 0 r)
            (append! r "length")
            r)))
      ((= (type-of o) "string")
        (let ((result (list)) (n (len o)))
          (begin
            (js-string-keys-loop result 0 n)
            (append! result "length")
            result)))
      ((dict? o) (js-own-property-names-ordered o))
      (else (list)))))

(define
  js-own-property-names-ordered
  (fn
    (o)
    (let
      ((all (js-object-keys o))
        (ints (list))
        (rest (list)))
      (begin
        (for-each
          (fn
            (k)
            (if (js-int-key? k) (append! ints k) (append! rest k)))
          all)
        (append (js-sort-int-keys ints) rest)))))

(define
  js-int-key?
  (fn
    (k)
    (cond
      ((not (= (type-of k) "string")) false)
      ((= (len k) 0) false)
      (else (js-int-key-loop? k 0 (len k))))))

(define
  js-int-key-loop?
  (fn
    (s i n)
    (cond
      ((>= i n) true)
      ((let ((c (char-code-at s i))) (and (>= c 48) (<= c 57)))
        (js-int-key-loop? s (+ i 1) n))
      (else false))))

(define
  js-sort-int-keys
  (fn
    (lst)
    (let
      ((nums (map js-string-to-number lst)))
      (begin
        (js-sort-numbers! nums)
        (map (fn (n) (str (js-num-to-int n))) nums)))))

(define
  js-sort-numbers!
  (fn
    (lst)
    (let ((n (len lst)))
      (js-bubble-sort! lst 0 n))))

(define
  js-bubble-sort!
  (fn
    (lst i n)
    (cond
      ((>= i n) nil)
      (else
        (begin (js-bubble-sort-inner! lst 0 (- n i 1)) (js-bubble-sort! lst (+ i 1) n))))))

(define
  js-bubble-sort-inner!
  (fn
    (lst j stop)
    (cond
      ((>= j stop) nil)
      ((> (nth lst j) (nth lst (+ j 1)))
        (let
          ((a (nth lst j)) (b (nth lst (+ j 1))))
          (begin
            (set-nth! lst j b)
            (set-nth! lst (+ j 1) a)
            (js-bubble-sort-inner! lst (+ j 1) stop))))
      (else (js-bubble-sort-inner! lst (+ j 1) stop)))))

(define
  js-object-get-own-property-descriptor
  (fn
    (o key)
    (let
      ((sk (js-to-string key)))
      (cond
        ((and (dict? o) (js-key-internal? sk)) :js-undefined)
        ((and (dict? o) (contains? (keys o) sk))
          {:configurable true :enumerable true :value (get o sk) :writable true})
        ((list? o)
          (cond
            ((= sk "length")
              {:configurable false :enumerable false :value (len o) :writable true})
            ((js-int-key? sk)
              (let
                ((i (js-num-to-int (js-string-to-number sk))))
                (cond
                  ((and (>= i 0) (< i (len o)))
                    {:configurable true :enumerable true :value (nth o i) :writable true})
                  (else :js-undefined))))
            (else :js-undefined)))
        ((and (= (type-of o) "string"))
          (cond
            ((= sk "length")
              {:configurable false :enumerable false :value (len o) :writable false})
            ((js-int-key? sk)
              (let
                ((i (js-num-to-int (js-string-to-number sk))))
                (cond
                  ((and (>= i 0) (< i (len o)))
                    {:configurable false :enumerable true :value (char-at o i) :writable false})
                  (else :js-undefined))))
            (else :js-undefined)))
        (else :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 {:keys js-object-keys :getPrototypeOf js-object-get-prototype-of :isSealed js-object-is-sealed :seal js-object-seal :create js-object-create :isExtensible js-object-is-extensible :is js-object-is :setPrototypeOf js-object-set-prototype-of :getOwnPropertyNames js-object-get-own-property-names :getOwnPropertyDescriptors js-object-get-own-property-descriptors :defineProperty js-object-define-property :fromEntries js-object-from-entries :getOwnPropertyDescriptor js-object-get-own-property-descriptor :assign js-object-assign :isFrozen js-object-is-frozen :freeze js-object-freeze :values js-object-values :hasOwn js-object-has-own :prototype {:hasOwnProperty (fn (k) (let ((o (js-this))) (js-object-has-own o k))) :toLocaleString (fn () "[object Object]") :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 () (js-object-tostring-class (js-this))) :propertyIsEnumerable (fn (k) (let ((o (js-this))) (js-object-has-own o k))) :valueOf (fn () (js-this))} :__callable__ (fn (&rest args) (let ((this-val (js-this))) (let ((is-new (and (dict? this-val) (contains? (keys this-val) "__proto__") (= (get this-val "__proto__") (get Object "prototype"))))) (cond ((= (len args) 0) (if is-new this-val (dict))) ((or (= (nth args 0) nil) (js-undefined? (nth args 0))) (if is-new this-val (dict))) ((= (type-of (nth args 0)) "string") (js-new-call String (list (nth args 0)))) ((= (js-typeof (nth args 0)) "number") (js-new-call Number (list (nth args 0)))) ((= (js-typeof (nth args 0)) "boolean") (js-new-call Boolean (list (nth args 0)))) (else (nth args 0)))))) :preventExtensions js-object-prevent-extensions :entries js-object-entries :defineProperties js-object-define-properties})

(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)
        (let
          ((sk (js-to-string key)))
          (begin
            (js-obj-order-remove! obj sk)
            (dict-delete! obj sk)
            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-length
  (fn
    (name)
    (cond
      ((= name "concat") 1)
      ((= name "copyWithin") 2)
      ((= name "every") 1)
      ((= name "fill") 1)
      ((= name "filter") 1)
      ((= name "find") 1)
      ((= name "findIndex") 1)
      ((= name "findLast") 1)
      ((= name "findLastIndex") 1)
      ((= name "flat") 0)
      ((= name "flatMap") 1)
      ((= name "forEach") 1)
      ((= name "includes") 1)
      ((= name "indexOf") 1)
      ((= name "join") 1)
      ((= name "lastIndexOf") 1)
      ((= name "map") 1)
      ((= name "push") 1)
      ((= name "reduce") 1)
      ((= name "reduceRight") 1)
      ((= name "slice") 2)
      ((= name "some") 1)
      ((= name "sort") 1)
      ((= name "splice") 2)
      ((= name "unshift") 1)
      ((= name "at") 1)
      ((= name "toSorted") 1)
      ((= name "toReversed") 0)
      ((= name "with") 2)
      (else 0))))

(define
  js-array-proto-fn
  (fn
    (name)
    {:__callable__
      (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))))
      :length (js-array-proto-fn-length name)
      :name name}))

(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)))
            (this-arg
              (if (or (< (len args) 3) (js-undefined? (nth args 2)) (= (nth args 2) nil))
                js-global-this
                (nth args 2))))
          (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)
                  (begin
                    (append! result (js-call-with-this this-arg map-fn (list x i)))
                    (set! i (+ i 1))))
                src)
              result)))))))

(define Array {:of js-array-of :from js-array-from :isArray js-array-is-array :prototype {:reverse (js-array-proto-fn "reverse") :fill (js-array-proto-fn "fill") :flatMap (js-array-proto-fn "flatMap") :keys (js-array-proto-fn "keys") :forEach (js-array-proto-fn "forEach") :toString (js-array-proto-fn "toString") :copyWithin (js-array-proto-fn "copyWithin") :toReversed (js-array-proto-fn "toReversed") :pop (js-array-proto-fn "pop") :at (js-array-proto-fn "at") :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") :indexOf (js-array-proto-fn "indexOf") :unshift (js-array-proto-fn "unshift") :every (js-array-proto-fn "every") :sort (js-array-proto-fn "sort") :findIndex (js-array-proto-fn "findIndex") :toLocaleString (js-array-proto-fn "toLocaleString") :find (js-array-proto-fn "find") :includes (js-array-proto-fn "includes") :findLastIndex (js-array-proto-fn "findLastIndex") :slice (js-array-proto-fn "slice") :reduce (js-array-proto-fn "reduce") :values (js-array-proto-fn "values") :join (js-array-proto-fn "join") :reduceRight (js-array-proto-fn "reduceRight") :shift (js-array-proto-fn "shift") :filter (js-array-proto-fn "filter") :findLast (js-array-proto-fn "findLast") :concat (js-array-proto-fn "concat") :lastIndexOf (js-array-proto-fn "lastIndexOf") :splice (js-array-proto-fn "splice") :entries (js-array-proto-fn "entries")} :__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)))})

(dict-set! Array "length" 1)

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

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

(dict-set!
  (get Array "prototype")
  "toString"
  (fn
    (&rest args)
    (let
      ((this-val (js-this)))
      (let
        ((items (cond ((list? this-val) this-val) ((and (dict? this-val) (contains? (keys this-val) "length")) (js-arraylike-to-list this-val)) (else (list)))))
        (js-list-join items ",")))))

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

(define
  js-string-from-code-point-loop
  (fn
    (args i acc)
    (if
      (>= i (len args))
      acc
      (let
        ((cp (floor (js-to-number (nth args i)))))
        (if
          (< cp 65536)
          (js-string-from-code-point-loop
            args
            (+ i 1)
            (str acc (js-code-to-char (js-num-to-int cp))))
          (let
            ((hi (+ 55296 (floor (/ (- cp 65536) 1024))))
              (lo (+ 56320 (modulo (- cp 65536) 1024))))
            (js-string-from-code-point-loop
              args
              (+ i 1)
              (str
                (str acc (js-code-to-char (js-num-to-int hi)))
                (js-code-to-char (js-num-to-int lo))))))))))

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

(define
  js-string-from-char-code-loop
  (fn
    (args i acc)
    (if
      (>= i (len args))
      acc
      (let
        ((n (js-to-number (nth args i))))
        (let
          ((code (if (js-global-is-nan n) 0 (modulo (js-math-trunc n) 65536))))
          (js-string-from-char-code-loop
            args
            (+ i 1)
            (str acc (char-from-code code))))))))

(define
  js-string-proto-fn-length
  (fn
    (name)
    (cond
      ((= name "concat") 1)
      ((= name "indexOf") 1)
      ((= name "lastIndexOf") 1)
      ((= name "slice") 2)
      ((= name "substring") 2)
      ((= name "substr") 2)
      ((= name "split") 2)
      ((= name "replace") 2)
      ((= name "replaceAll") 2)
      ((= name "match") 1)
      ((= name "matchAll") 1)
      ((= name "search") 1)
      ((= name "charAt") 1)
      ((= name "charCodeAt") 1)
      ((= name "codePointAt") 1)
      ((= name "at") 1)
      ((= name "padStart") 1)
      ((= name "padEnd") 1)
      ((= name "repeat") 1)
      ((= name "startsWith") 1)
      ((= name "endsWith") 1)
      ((= name "includes") 1)
      ((= name "localeCompare") 1)
      ((= name "normalize") 0)
      (else 0))))

(define
  js-string-proto-fn
  (fn
    (name)
    {:__callable__
      (fn
        (&rest args)
        (let
          ((this-val (js-this)))
          (let
            ((s (cond ((or (= this-val nil) (js-undefined? this-val)) (raise (js-new-call TypeError (js-args (str "String.prototype." name " called on null or undefined"))))) ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else (js-to-string this-val)))))
            (js-invoke-method s name args))))
      :length (js-string-proto-fn-length name)
      :name name}))

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

(dict-set! String "length" 1)

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

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

(dict-set! String "fromCodePoint" js-string-from-code-point)

(dict-set! String "fromCharCode" js-string-from-char-code)

(dict-set!
  String
  "__callable__"
  (fn
    (&rest args)
    (let
      ((raw (if (= (len args) 0) "" (js-to-string (nth args 0)))))
      (let
        ((this-val (js-this)))
        (if
          (and
            (dict? this-val)
            (contains? (keys this-val) "__proto__")
            (= (get this-val "__proto__") (get String "prototype")))
          (begin
            (dict-set! this-val "__js_string_value__" raw)
            (dict-set! this-val "length" (len raw))
            this-val)
          raw)))))

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

(dict-set!
  (get String "prototype")
  "matchAll"
  (js-string-proto-fn "matchAll"))

(dict-set! Boolean "length" 1)

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

(dict-set! Boolean "prototype" {:constructor Boolean})

(dict-set!
  Boolean
  "__callable__"
  (fn
    (&rest args)
    (let
      ((val (if (> (len args) 0) (js-to-boolean (nth args 0)) false)))
      (let
        ((this-val (js-this)))
        (if
          (dict? this-val)
          (begin
            (dict-set! this-val "__js_boolean_value__" val)
            (dict-set! this-val "__proto__" (get Boolean "prototype"))
            this-val)
          (if val true false))))))

(dict-set!
  (get Boolean "prototype")
  "valueOf"
  (fn
    (&rest args)
    (let
      ((this-val (js-this)))
      (cond
        ((= (type-of this-val) "boolean") this-val)
        ((and
           (= (type-of this-val) "dict")
           (contains? (keys this-val) "__js_boolean_value__"))
          (get this-val "__js_boolean_value__"))
        (else (raise (js-new-call TypeError (js-args "Boolean.prototype.valueOf requires a Boolean"))))))))

(dict-set!
  (get Boolean "prototype")
  "toString"
  (fn
    (&rest args)
    (let
      ((this-val (js-this)))
      (cond
        ((= (type-of this-val) "boolean") (if this-val "true" "false"))
        ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_boolean_value__"))
          (if (get this-val "__js_boolean_value__") "true" "false"))
        (else (raise (js-new-call TypeError (js-args "Boolean.prototype.toString requires a 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)
    (cond
      ((js-float-has-infinity-prefix? s 0)
        (js-infinity-value))
      ((and
         (>= (len s) 1)
         (= (char-at s 0) "+")
         (js-float-has-infinity-prefix? s 1))
        (js-infinity-value))
      ((and
         (>= (len s) 1)
         (= (char-at s 0) "-")
         (js-float-has-infinity-prefix? s 1))
        (- 0 (js-infinity-value)))
      (else
        (let
          ((end (js-float-prefix-end s 0 false false false)))
          (cond
            ((= end 0) (js-nan-value))
            ((not (js-str-has-digit? s 0 end)) (js-nan-value))
            (else (js-parse-num-safe (js-string-slice s 0 end)))))))))

(define
  js-str-has-digit?
  (fn
    (s i n)
    (cond
      ((>= i n) false)
      ((let ((c (char-at s i))) (and (>= (char-code c) 48) (<= (char-code c) 57)))
        true)
      (else (js-str-has-digit? s (+ i 1) n)))))

(define
  js-float-has-infinity-prefix?
  (fn
    (s i)
    (and
      (>= (len s) (+ i 8))
      (= (js-string-slice s i (+ i 8)) "Infinity"))))

(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) (let ((s (js-to-string v))) (js-uri-decode s 0 "" false))))

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

(define
  decodeURI
  (fn (v) (let ((s (js-to-string v))) (js-uri-decode s 0 "" true))))

(define
  js-uri-malformed!
  (fn () (raise (js-new-call URIError (js-args "URI malformed")))))

(define
  js-uri-reserved-byte?
  (fn
    (b)
    (or
      (= b 35)
      (= b 36)
      (= b 38)
      (= b 43)
      (= b 44)
      (= b 47)
      (= b 58)
      (= b 59)
      (= b 61)
      (= b 63)
      (= b 64))))

(define
  js-uri-hex-val
  (fn
    (c)
    (let
      ((cc (char-code c)))
      (cond
        ((and (>= cc 48) (<= cc 57)) (- cc 48))
        ((and (>= cc 65) (<= cc 70)) (- cc 55))
        ((and (>= cc 97) (<= cc 102)) (- cc 87))
        (else -1)))))

(define
  js-uri-hex-pair
  (fn
    (s i)
    (cond
      ((>= (+ i 1) (len s)) -1)
      (else
        (let
          ((d1 (js-uri-hex-val (char-at s i)))
            (d2 (js-uri-hex-val (char-at s (+ i 1)))))
          (cond
            ((or (= d1 -1) (= d2 -1)) -1)
            (else (+ (* d1 16) d2))))))))

(define
  js-uri-decode
  (fn
    (s i acc preserveReserved)
    (cond
      ((>= i (len s)) acc)
      ((not (= (char-at s i) "%"))
        (js-uri-decode
          s
          (+ i 1)
          (str acc (char-at s i))
          preserveReserved))
      ((> (+ i 3) (len s)) (js-uri-malformed!))
      (else
        (let
          ((b (js-uri-hex-pair s (+ i 1))))
          (cond
            ((= b -1) (js-uri-malformed!))
            ((< b 128)
              (cond
                ((and preserveReserved (js-uri-reserved-byte? b))
                  (js-uri-decode
                    s
                    (+ i 3)
                    (str acc (char-at s i) (char-at s (+ i 1)) (char-at s (+ i 2)))
                    preserveReserved))
                (else
                  (js-uri-decode
                    s
                    (+ i 3)
                    (str acc (char-from-code b))
                    preserveReserved))))
            (else (js-uri-decode-multi s i acc preserveReserved b))))))))

(define
  js-uri-decode-multi
  (fn
    (s i acc preserveReserved b1)
    (let
      ((n
         (cond
           ((< b1 192) -1)
           ((< b1 224) 2)
           ((< b1 240) 3)
           ((< b1 248) 4)
           (else -1)))
        (head-bits
         (cond
           ((< b1 192) 0)
           ((< b1 224) (mod b1 32))
           ((< b1 240) (mod b1 16))
           ((< b1 248) (mod b1 8))
           (else 0))))
      (cond
        ((= n -1) (js-uri-malformed!))
        (else
          (js-uri-decode-multi-loop s i acc preserveReserved n 1 head-bits))))))

(define
  js-uri-decode-multi-loop
  (fn
    (s i acc preserveReserved n k cp)
    (cond
      ((>= k n)
        (cond
          ((and (>= cp 55296) (<= cp 57343)) (js-uri-malformed!))
          ((> cp 1114111) (js-uri-malformed!))
          (else
            (js-uri-decode
              s
              (+ i (* 3 n))
              (str acc (char-from-code cp))
              preserveReserved))))
      (else
        (let
          ((p (+ i (* 3 k))))
          (cond
            ((>= (+ p 3) (+ (len s) 1)) (js-uri-malformed!))
            ((not (= (char-at s p) "%")) (js-uri-malformed!))
            (else
              (let
                ((b (js-uri-hex-pair s (+ p 1))))
                (cond
                  ((= b -1) (js-uri-malformed!))
                  ((or (< b 128) (>= b 192)) (js-uri-malformed!))
                  (else
                    (js-uri-decode-multi-loop
                      s
                      i
                      acc
                      preserveReserved
                      n
                      (+ k 1)
                      (+ (* cp 64) (mod b 64)))))))))))))

(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)
    (let
      ((value (if (= (len args) 0) :js-undefined (nth args 0)))
        (replacer (if (< (len args) 2) :js-undefined (nth args 1)))
        (space-raw (if (< (len args) 3) :js-undefined (nth args 2))))
      (let
        ((rep-fn (if (js-function? replacer) replacer nil))
          (rep-keys (if (list? replacer) (js-json-prop-list replacer) nil))
          (gap (js-json-space-gap space-raw)))
        (let
          ((wrapper (dict)))
          (begin
            (dict-set! wrapper "" value)
            (js-json-serialize-property "" wrapper rep-fn rep-keys gap "")))))))

(define
  js-json-prop-list
  (fn
    (arr)
    (let
      ((out (list)))
      (begin
        (for-each
          (fn
            (k)
            (cond
              ((= (type-of k) "string")
                (if (js-list-contains? out k) nil (append! out k)))
              ((number? k)
                (let ((s (js-number-to-string k)))
                  (if (js-list-contains? out s) nil (append! out s))))
              ((dict? k)
                (cond
                  ((contains? (keys k) "__js_string_value__")
                    (let ((s (get k "__js_string_value__")))
                      (if (js-list-contains? out s) nil (append! out s))))
                  ((contains? (keys k) "__js_number_value__")
                    (let ((s (js-number-to-string (get k "__js_number_value__"))))
                      (if (js-list-contains? out s) nil (append! out s))))
                  (else nil)))
              (else nil)))
          arr)
        out))))

(define
  js-list-contains?
  (fn
    (lst v)
    (cond
      ((empty? lst) false)
      ((= (first lst) v) true)
      (else (js-list-contains? (rest lst) v)))))

(define
  js-json-space-gap
  (fn
    (sp)
    (cond
      ((js-undefined? sp) "")
      ((= sp nil) "")
      ((number? sp)
        (let
          ((n (cond ((js-number-is-nan sp) 0) ((< sp 0) 0) ((> sp 10) 10) (else (floor sp)))))
          (js-string-repeat " " n)))
      ((and (dict? sp) (contains? (keys sp) "__js_number_value__"))
        (js-json-space-gap (get sp "__js_number_value__")))
      ((and (dict? sp) (contains? (keys sp) "__js_string_value__"))
        (js-json-space-gap (get sp "__js_string_value__")))
      ((= (type-of sp) "string")
        (if (> (len sp) 10) (js-string-slice sp 0 10) sp))
      (else ""))))

(define
  js-json-unwrap-primitive
  (fn
    (v)
    (cond
      ((not (dict? v)) v)
      ((contains? (keys v) "__js_number_value__")
        (get v "__js_number_value__"))
      ((contains? (keys v) "__js_string_value__")
        (get v "__js_string_value__"))
      ((contains? (keys v) "__js_boolean_value__")
        (get v "__js_boolean_value__"))
      (else v))))

(define
  js-json-serialize-property
  (fn
    (key holder rep-fn rep-keys gap indent)
    (let
      ((value0 (if (dict? holder) (get holder key) (if (list? holder) (nth holder (js-num-to-int (js-to-number key))) :js-undefined))))
      (let
        ((value1
           (cond
             ((and
                (or (dict? value0) (list? value0))
                (let ((tj (js-get-prop value0 "toJSON")))
                  (and (not (js-undefined? tj)) (js-function? tj))))
               (js-call-with-this value0 (js-get-prop value0 "toJSON") (list key)))
             (else value0))))
        (let
          ((value
             (if rep-fn
               (js-call-with-this holder rep-fn (list key value1))
               value1)))
          (let
            ((vu (js-json-unwrap-primitive value)))
            (cond
              ((= vu nil) "null")
              ((js-undefined? vu) :js-undefined)
              ((= (type-of vu) "boolean") (if vu "true" "false"))
              ((or (number? vu) (= (type-of vu) "rational"))
                (let ((n (if (= (type-of vu) "rational") (exact->inexact vu) vu)))
                  (cond
                    ((js-number-is-nan n) "null")
                    ((= n (js-infinity-value)) "null")
                    ((= n (- 0 (js-infinity-value))) "null")
                    (else (js-number-to-string n)))))
              ((= (type-of vu) "string") (js-json-escape-string vu))
              ((js-function? vu) :js-undefined)
              ((list? vu)
                (js-json-serialize-array vu rep-fn rep-keys gap indent))
              ((dict? vu)
                (js-json-serialize-object vu rep-fn rep-keys gap indent))
              (else :js-undefined))))))))

(define
  js-json-serialize-array
  (fn
    (arr rep-fn rep-keys gap indent)
    (let
      ((step-back indent) (new-indent (str indent gap)) (parts (list)))
      (begin
        (js-json-array-loop arr rep-fn rep-keys gap new-indent 0 parts)
        (cond
          ((empty? parts) "[]")
          ((= gap "")
            (str "[" (join "," parts) "]"))
          (else
            (str
              "[\n"
              new-indent
              (join (str ",\n" new-indent) parts)
              "\n"
              step-back
              "]")))))))

(define
  js-json-array-loop
  (fn
    (arr rep-fn rep-keys gap new-indent i parts)
    (cond
      ((>= i (len arr)) nil)
      (else
        (let
          ((s (js-json-serialize-property (js-number-to-string i) arr rep-fn rep-keys gap new-indent)))
          (begin
            (if (js-undefined? s) (append! parts "null") (append! parts s))
            (js-json-array-loop arr rep-fn rep-keys gap new-indent (+ i 1) parts)))))))

(define
  js-json-serialize-object
  (fn
    (obj rep-fn rep-keys gap indent)
    (let
      ((step-back indent) (new-indent (str indent gap)) (parts (list))
        (sep (if (= gap "") ":" ": "))
        (key-list (if rep-keys rep-keys (js-object-keys obj))))
      (begin
        (for-each
          (fn
            (k)
            (cond
              ((js-key-internal? k) nil)
              (else
                (let
                  ((s (js-json-serialize-property k obj rep-fn rep-keys gap new-indent)))
                  (if
                    (js-undefined? s)
                    nil
                    (append! parts (str (js-json-escape-string k) sep s)))))))
          key-list)
        (cond
          ((empty? parts) "{}")
          ((= gap "")
            (str "{" (join "," parts) "}"))
          (else
            (str
              "{\n"
              new-indent
              (join (str ",\n" new-indent) parts)
              "\n"
              step-back
              "}")))))))


(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)
      (raise (js-new-call SyntaxError (js-args "Unexpected token undefined")))
      (let
        ((st (dict)))
        (dict-set! st "s" (js-to-string (nth args 0)))
        (dict-set! st "i" 0)
        (let
          ((result (js-json-parse-value st)))
          (begin
            (js-json-skip-ws! st)
            (if
              (< (get st "i") (len (get st "s")))
              (raise
                (js-new-call SyntaxError
                  (js-args (str "Unexpected token at position " (get st "i")))))
              result)))))))

(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)) (raise (js-new-call SyntaxError (js-args "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))
          (raise (js-new-call SyntaxError (js-args "JSON: unterminated string"))))
        ((= (char-at s i) "\"") nil)
        ((< (char-code (char-at s i)) 32)
          (raise
            (js-new-call SyntaxError
              (js-args "JSON: control character in string"))))
        ((= (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 (raise (js-new-call SyntaxError (js-args "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")) ":"))
        (raise (js-new-call SyntaxError (js-args "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 (raise (js-new-call SyntaxError (js-args "JSON: expected , or }")))))))))

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

(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)))))
          (cond
            ((or (js-undefined? impl) (= impl nil))
              (js-regex-stub-test rx arg))
            (else (impl rx arg)))))
      ((= name "exec")
        (let
          ((impl (get __js_regex_platform__ "exec"))
            (arg
              (if
                (= (len args) 0)
                ""
                (js-to-string (nth args 0)))))
          (cond
            ((or (js-undefined? impl) (= impl nil))
              (js-regex-stub-exec rx arg))
            (else (impl rx arg)))))
      ((= name "toString")
        (str "/" (get rx "source") "/" (get rx "flags")))
      (else js-undefined))))

(define
  js-list-find-index
  (fn
    (lst v i n)
    (cond
      ((>= i n) -1)
      ((= (nth lst i) v) i)
      (else (js-list-find-index lst v (+ i 1) n)))))

(define
  js-list-remove-at!
  (fn
    (lst i)
    (let
      ((n (len lst)) (kept (list)))
      (begin
        (js-list-remove-at-loop lst i n 0 kept)
        kept))))

(define
  js-list-remove-at-loop
  (fn
    (src skip n j out)
    (cond
      ((>= j n) nil)
      ((= j skip) (js-list-remove-at-loop src skip n (+ j 1) out))
      (else
        (begin
          (append! out (nth src j))
          (js-list-remove-at-loop src skip n (+ j 1) out))))))

(define
  js-map-ctor-fn
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (cond
        ((not (= (type-of this) "dict"))
          (raise (js-new-call TypeError (js-args "Map must be constructed with new"))))
        (else
          (begin
            (dict-set! this "__map_keys__" (list))
            (dict-set! this "__map_vals__" (list))
            (dict-set! this "size" 0)
            (if
              (and
                (>= (len args) 1)
                (not (js-undefined? (nth args 0)))
                (not (= (nth args 0) nil)))
              (js-map-init this (nth args 0))
              nil)
            this))))))

(define
  js-map-init
  (fn
    (m iter)
    (let
      ((entries (js-iterable-to-list iter)))
      (for-each
        (fn
          (entry)
          (cond
            ((list? entry)
              (js-map-do-set m (nth entry 0) (nth entry 1)))
            (else nil)))
        entries))))

(define
  js-map-check!
  (fn
    (m method)
    (cond
      ((or (not (dict? m)) (not (contains? (keys m) "__map_keys__")))
        (raise
          (js-new-call TypeError
            (js-args (str "Map.prototype." method " called on non-Map object")))))
      (else nil))))

(define
  js-map-do-set
  (fn
    (m k v)
    (begin
      (js-map-check! m "set")
      (let
        ((ks (get m "__map_keys__")) (vs (get m "__map_vals__")))
        (let
          ((idx (js-list-find-index ks k 0 (len ks))))
          (cond
            ((>= idx 0) (begin (set-nth! vs idx v) m))
            (else
              (begin
                (append! ks k)
                (append! vs v)
                (dict-set! m "size" (len ks))
                m))))))))

(define
  js-map-do-get
  (fn
    (m k)
    (begin
      (js-map-check! m "get")
      (let
        ((ks (get m "__map_keys__")) (vs (get m "__map_vals__")))
        (let
          ((idx (js-list-find-index ks k 0 (len ks))))
          (cond ((>= idx 0) (nth vs idx)) (else js-undefined)))))))

(define
  js-map-do-has
  (fn
    (m k)
    (begin
      (js-map-check! m "has")
      (let
        ((ks (get m "__map_keys__")))
        (>= (js-list-find-index ks k 0 (len ks)) 0)))))

(define
  js-map-do-delete
  (fn
    (m k)
    (begin
      (js-map-check! m "delete")
      (let
        ((ks (get m "__map_keys__")) (vs (get m "__map_vals__")))
        (let
          ((idx (js-list-find-index ks k 0 (len ks))))
          (cond
            ((< idx 0) false)
            (else
              (let
                ((new-ks (js-list-remove-at! ks idx))
                  (new-vs (js-list-remove-at! vs idx)))
                (begin
                  (dict-set! m "__map_keys__" new-ks)
                  (dict-set! m "__map_vals__" new-vs)
                  (dict-set! m "size" (len new-ks))
                  true)))))))))

(define
  js-map-do-clear
  (fn
    (m)
    (begin
      (js-map-check! m "clear")
      (dict-set! m "__map_keys__" (list))
      (dict-set! m "__map_vals__" (list))
      (dict-set! m "size" 0)
      js-undefined)))

(define
  js-map-do-foreach
  (fn
    (m cb &rest opts)
    (let
      ((ks (get m "__map_keys__"))
        (vs (get m "__map_vals__"))
        (this-arg
          (cond
            ((empty? opts) js-global-this)
            ((js-undefined? (nth opts 0)) js-global-this)
            ((= (nth opts 0) nil) js-global-this)
            (else (nth opts 0)))))
      (begin
        (js-map-foreach-loop ks vs cb this-arg m 0 (len ks))
        js-undefined))))

(define
  js-map-foreach-loop
  (fn
    (ks vs cb this-arg m i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-call-with-this this-arg cb (list (nth vs i) (nth ks i) m))
          (js-map-foreach-loop ks vs cb this-arg m (+ i 1) n))))))

(define
  Map
  {:length 0
    :name "Map"
    :__callable__ js-map-ctor-fn
    :prototype
      {:get (fn (k) (js-map-do-get (js-this) k))
        :set (fn (k v) (js-map-do-set (js-this) k v))
        :has (fn (k) (js-map-do-has (js-this) k))
        :delete (fn (k) (js-map-do-delete (js-this) k))
        :clear (fn () (js-map-do-clear (js-this)))
        :forEach (fn (&rest args) (let ((cb (if (empty? args) :js-undefined (nth args 0))) (ta (if (>= (len args) 2) (nth args 1) :js-undefined))) (js-map-do-foreach (js-this) cb ta)))
        :keys (fn () (let ((ks (get (js-this) "__map_keys__"))) (js-list-copy ks)))
        :values (fn () (let ((vs (get (js-this) "__map_vals__"))) (js-list-copy vs)))
        :entries
          (fn ()
            (let
              ((ks (get (js-this) "__map_keys__"))
                (vs (get (js-this) "__map_vals__"))
                (out (list)))
              (begin
                (js-map-entries-loop ks vs 0 (len ks) out)
                out)))}})

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

(define
  js-list-copy
  (fn
    (src)
    (let
      ((out (list)))
      (begin (for-each (fn (x) (append! out x)) src) out))))

(define
  js-map-entries-loop
  (fn
    (ks vs i n out)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (append! out (list (nth ks i) (nth vs i)))
          (js-map-entries-loop ks vs (+ i 1) n out))))))

(define
  js-set-ctor-fn
  (fn
    (&rest args)
    (let
      ((this (js-this)))
      (cond
        ((not (= (type-of this) "dict"))
          (raise (js-new-call TypeError (js-args "Set must be constructed with new"))))
        (else
          (begin
            (dict-set! this "__set_items__" (list))
            (dict-set! this "size" 0)
            (if
              (and
                (>= (len args) 1)
                (not (js-undefined? (nth args 0)))
                (not (= (nth args 0) nil)))
              (js-set-init this (nth args 0))
              nil)
            this))))))

(define
  js-set-init
  (fn
    (s iter)
    (let
      ((items (js-iterable-to-list iter)))
      (for-each (fn (x) (js-set-do-add s x)) items))))

(define
  js-set-check!
  (fn
    (s method)
    (cond
      ((or (not (dict? s)) (not (contains? (keys s) "__set_items__")))
        (raise
          (js-new-call TypeError
            (js-args (str "Set.prototype." method " called on non-Set object")))))
      (else nil))))

(define
  js-set-do-add
  (fn
    (s v)
    (begin
      (js-set-check! s "add")
      (let
        ((items (get s "__set_items__")))
        (let
          ((idx (js-list-find-index items v 0 (len items))))
          (cond
            ((>= idx 0) s)
            (else
              (begin
                (append! items v)
                (dict-set! s "size" (len items))
                s))))))))

(define
  js-set-do-has
  (fn
    (s v)
    (begin
      (js-set-check! s "has")
      (let
        ((items (get s "__set_items__")))
        (>= (js-list-find-index items v 0 (len items)) 0)))))

(define
  js-set-do-delete
  (fn
    (s v)
    (begin
      (js-set-check! s "delete")
      (let
        ((items (get s "__set_items__")))
        (let
          ((idx (js-list-find-index items v 0 (len items))))
          (cond
            ((< idx 0) false)
            (else
              (let
                ((new-items (js-list-remove-at! items idx)))
                (begin
                  (dict-set! s "__set_items__" new-items)
                  (dict-set! s "size" (len new-items))
                  true)))))))))

(define
  js-set-do-clear
  (fn
    (s)
    (begin
      (js-set-check! s "clear")
      (dict-set! s "__set_items__" (list))
      (dict-set! s "size" 0)
      js-undefined)))

(define
  js-set-do-foreach
  (fn
    (s cb &rest opts)
    (let
      ((items (get s "__set_items__"))
        (this-arg
          (cond
            ((empty? opts) js-global-this)
            ((js-undefined? (nth opts 0)) js-global-this)
            ((= (nth opts 0) nil) js-global-this)
            (else (nth opts 0)))))
      (begin
        (js-set-foreach-loop items cb this-arg s 0 (len items))
        js-undefined))))

(define
  js-set-foreach-loop
  (fn
    (items cb this-arg s i n)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (js-call-with-this
            this-arg
            cb
            (list (nth items i) (nth items i) s))
          (js-set-foreach-loop items cb this-arg s (+ i 1) n))))))

(define
  Set
  {:length 0
    :name "Set"
    :__callable__ js-set-ctor-fn
    :prototype
      {:add (fn (v) (js-set-do-add (js-this) v))
        :has (fn (v) (js-set-do-has (js-this) v))
        :delete (fn (v) (js-set-do-delete (js-this) v))
        :clear (fn () (js-set-do-clear (js-this)))
        :forEach (fn (&rest args) (let ((cb (if (empty? args) :js-undefined (nth args 0))) (ta (if (>= (len args) 2) (nth args 1) :js-undefined))) (js-set-do-foreach (js-this) cb ta)))
        :keys (fn () (js-list-copy (get (js-this) "__set_items__")))
        :values (fn () (js-list-copy (get (js-this) "__set_items__")))
        :entries
          (fn ()
            (let
              ((items (get (js-this) "__set_items__")) (out (list)))
              (begin
                (js-set-entries-loop items 0 (len items) out)
                out)))}})

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

(define
  js-set-entries-loop
  (fn
    (items i n out)
    (cond
      ((>= i n) nil)
      (else
        (begin
          (append! out (list (nth items i) (nth items i)))
          (js-set-entries-loop items (+ i 1) n out))))))

(begin
  (dict-set! Object "__proto__" (get js-function-global "prototype"))
  (dict-set! Array "__proto__" (get js-function-global "prototype"))
  (dict-set! Number "__proto__" (get js-function-global "prototype"))
  (dict-set! String "__proto__" (get js-function-global "prototype"))
  (dict-set! Boolean "__proto__" (get js-function-global "prototype"))
  (dict-set! Map "__proto__" (get js-function-global "prototype"))
  (dict-set! Set "__proto__" (get js-function-global "prototype"))
  (dict-set! Date "__proto__" (get js-function-global "prototype"))
  (dict-set! RegExp "__proto__" (get js-function-global "prototype"))
  (dict-set! __js_ctor_proto__ (js-ctor-id TypeError) Error)
  (dict-set! __js_ctor_proto__ (js-ctor-id RangeError) Error)
  (dict-set! __js_ctor_proto__ (js-ctor-id SyntaxError) Error)
  (dict-set! __js_ctor_proto__ (js-ctor-id ReferenceError) Error)
  (dict-set! __js_ctor_proto__ (js-ctor-id URIError) Error)
  (dict-set! __js_ctor_proto__ (js-ctor-id EvalError) Error)
  (dict-set! (js-get-ctor-proto TypeError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto RangeError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto SyntaxError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto ReferenceError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto URIError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto EvalError) "__proto__" (js-get-ctor-proto Error))
  (dict-set! (js-get-ctor-proto Error) "__proto__" (get Object "prototype"))
  (dict-set! (js-get-ctor-proto Error) "name" "Error")
  (dict-set! (js-get-ctor-proto Error) "message" "")
  (dict-set! (js-get-ctor-proto Error) "constructor" Error)
  (dict-set! (js-get-ctor-proto TypeError) "name" "TypeError")
  (dict-set! (js-get-ctor-proto TypeError) "constructor" TypeError)
  (dict-set! (js-get-ctor-proto RangeError) "name" "RangeError")
  (dict-set! (js-get-ctor-proto RangeError) "constructor" RangeError)
  (dict-set! (js-get-ctor-proto SyntaxError) "name" "SyntaxError")
  (dict-set! (js-get-ctor-proto SyntaxError) "constructor" SyntaxError)
  (dict-set! (js-get-ctor-proto ReferenceError) "name" "ReferenceError")
  (dict-set! (js-get-ctor-proto ReferenceError) "constructor" ReferenceError)
  (dict-set! (js-get-ctor-proto URIError) "name" "URIError")
  (dict-set! (js-get-ctor-proto URIError) "constructor" URIError)
  (dict-set! (js-get-ctor-proto EvalError) "name" "EvalError")
  (dict-set! (js-get-ctor-proto EvalError) "constructor" EvalError)
  (dict-set! (get Array "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Number "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get String "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Boolean "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Map "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Set "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Date "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Date "prototype") "constructor" Date)
  (dict-set! (get RegExp "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get RegExp "prototype") "constructor" RegExp)
  (dict-set! (get js-function-global "prototype") "__proto__" (get Object "prototype"))
  (dict-set! (get Number "prototype") "__js_number_value__" 0)
  (dict-set! (get String "prototype") "__js_string_value__" "")
  (dict-set! (get Boolean "prototype") "__js_boolean_value__" false))

(define js-global {:undefined js-undefined :JSON JSON :parseInt parseInt :Object Object :isNaN js-global-is-nan :Infinity inf :NaN 0 :String String :Boolean Boolean :Array Array :Math Math :parseFloat parseFloat :Number Number :console console :isFinite js-global-is-finite :Map Map :Set Set :Date Date :RegExp RegExp :Function js-function-global :Error Error :TypeError TypeError :RangeError RangeError :SyntaxError SyntaxError :ReferenceError ReferenceError :URIError URIError :EvalError EvalError :encodeURI encodeURI :decodeURI decodeURI :encodeURIComponent encodeURIComponent :decodeURIComponent decodeURIComponent :eval js-global-eval :Promise Promise :Symbol :js-undefined :AggregateError :js-undefined :SuppressedError :js-undefined :globalThis nil})

(set! js-global-this js-global)

(dict-set! js-global "globalThis" js-global)
