;; Minimal `clojure.test` + `clojure.core-test.portability` shims.
;;
;; These exist solely so Jolt can load the external clojure-test-suite
;; (https://github.com/lread/clojure-test-suite, EPL) — 240 per-function
;; `.cljc` files written against `clojure.test`. They are NOT a full
;; clojure.test: just enough surface (deftest/is/testing/are + the suite's
;; portability helpers) to run the suite and tally pass/fail/error.
;;
;; Loaded by test/integration/clojure-test-suite-test.janet, which pre-loads
;; this file so the suite's `(require [clojure.test ...])` finds it already
;; populated (Jolt's require is a no-op for already-loaded namespaces).

(ns clojure.test)

;; --- result accumulator (plain atoms; no dynamic vars needed) -------------

(def jolt-report (atom {:pass 0 :fail 0 :error 0 :fails []}))
(def jolt-ctx (atom []))        ;; stack of `testing` context strings
(def registry (atom []))        ;; deftest fns defined since last reset

(defn reset-report! []
  (reset! jolt-report {:pass 0 :fail 0 :error 0 :fails []})
  (reset! jolt-ctx [])
  (reset! registry []))

(defn- ctx-str [] (apply str (interpose " " @jolt-ctx)))

(defn inc-pass! [] (swap! jolt-report update :pass inc))

(defn fail! [form]
  (swap! jolt-report
         (fn [r] (-> r (update :fail inc)
                     (update :fails conj (str (ctx-str) " FAIL: " form))))))

(defn err! [form]
  (swap! jolt-report
         (fn [r] (-> r (update :error inc)
                     (update :fails conj (str (ctx-str) " ERROR: " form))))))

(defn n-pass [] (:pass @jolt-report))
(defn n-fail [] (:fail @jolt-report))
(defn n-error [] (:error @jolt-report))
(defn failures [] (:fails @jolt-report))

;; --- assertion macros ------------------------------------------------------

;; `(is form)` / `(is form msg)` — the optional msg is absorbed and ignored.
(defmacro is [form & _]
  `(try
     (if ~form
       (clojure.test/inc-pass!)
       (clojure.test/fail! (pr-str '~form)))
     (catch :default e#
       (clojure.test/err! (str (pr-str '~form) " threw")))))

(defmacro testing [s & body]
  `(do
     (swap! clojure.test/jolt-ctx conj ~s)
     (try
       (do ~@body)
       (finally (swap! clojure.test/jolt-ctx pop)))))

(defmacro deftest [name & body]
  `(do
     (def ~name (fn [] ~@body))
     (swap! clojure.test/registry conj ~name)
     ~name))

;; `(are [bindings] expr & data)` — substitute each row of `data` into the
;; template and assert via `is`. Expands to a `do` of let+is forms.
(defmacro are [argv expr & data]
  (let [n (count argv)
        rows (partition n data)]
    `(do ~@(map (fn [row]
                  `(let [~@(interleave argv row)]
                     (clojure.test/is ~expr)))
                rows))))

;; Run every deftest registered since the last reset, isolating crashes.
(defn run-registered []
  (doseq [t @registry]
    (try (t) (catch :default e (clojure.test/err! (str "deftest crashed: " (clojure.core/ex-message e))))))
  nil)

;; clojure.test entry points the suite may call — no-ops; the Janet runner
;; drives execution via run-registered instead.
(defn run-tests [& _] nil)
(defn run-test [& _] nil)
(defn test-var [& _] nil)


;; --- clojure.core-test.portability ----------------------------------------

(ns clojure.core-test.portability)

;; Gate a test on whether its target var exists in this dialect. Jolt only
;; implements a subset of clojure.core, so unimplemented fns get skipped
;; cleanly rather than erroring.
;; Skips silently (no stdout) so the worker's stdout stays just the count line;
;; an unimplemented var simply contributes no assertions.
(defmacro when-var-exists [var-sym & body]
  (if (resolve var-sym)
    `(do ~@body)
    nil))

;; `(thrown? body)` — true iff evaluating body throws. The suite always uses
;; the single-arg (no exception-class) form via this portability helper.
(defmacro thrown? [& body]
  `(try (do ~@body false) (catch :default e# true)))

(defn big-int? [n]
  (and (integer? n) (not (int? n))))

(defn lazy-seq? [x]
  ;; Jolt has no public LazySeq type test; approximate with seq?-of-non-vector.
  (and (seq? x) (not (vector? x))))

(defn sleep [ms] nil)
