# Deadlined infinite-seq conformance harness (Phase 5 Step 0).
#
# Each case is [name expected-clj actual-clj]. The harness spawns a subprocess
# worker (test/support/lazy-eval.janet) that evaluates (= expected actual) and
# prints @@RESULT true/false. Workers run under a wall-clock deadline; a hang
# = a FAIL. This is the safety net that makes it safe to convert transformers
# to lazy — wrong answers hang instead of silently passing.
#
# Pattern mirrors clojure-test-suite-test.janet: os/spawn + ev/with-deadline
# + os/proc-kill on timeout. Never probe infinite cases in-process.

(def per-case-timeout 5)

(defn- run-case [expected actual]
  (def proc (os/spawn ["janet" "test/support/lazy-eval.janet" expected actual] :p {:out :pipe}))
  (def out (proc :out))
  (var data nil)
  (def ok
    (try
      (ev/with-deadline per-case-timeout
        (set data (ev/read out 0x10000))
        (os/proc-wait proc)
        true)
      ([err] false)))
  (when (not ok)
    (protect (os/proc-kill proc true))
    (protect (ev/with-deadline 2 (os/proc-wait proc))))
  (protect (:close out))
  (if (and ok data) (string data) nil))

(defn- parse-result [s]
  (def prefix-len (length "@@RESULT "))
  (if (string/has-prefix? "@@RESULT " s)
    (let [val (string/slice s prefix-len (dec (length s)))]
      [:ok val])
    (if (string/has-prefix? "@@ERROR " s)
      (let [msg (string/slice s (length "@@ERROR ") (dec (length s)))]
        [:error msg])
      nil)))

# ---- Cases from phase-5.md §6.2 ----
# Expected values use Clojure quote syntax so the worker evaluates
# (= (quote ...) actual) with Clojure's = semantics.
(def cases
  [
   ["nth of map inc range"                "1001"                  "(nth (map inc (range)) 1000)"]
   ["first filter even? drop range"       "4"                     "(first (filter even? (drop 3 (range))))"]
   ["take 3 remove odd? range"            "(quote (0 2 4))"       "(take 3 (remove odd? (range)))"]
   ["take 3 drop-while <5 range"          "(quote (5 6 7))"       "(take 3 (drop-while (fn [x] (< x 5)) (range)))"]
   ["take 4 interleave range iterate"     "(quote (0 10 1 11))"   "(take 4 (interleave (range) (iterate inc 10)))"]
   ["take 4 reductions + range"           "(quote (0 1 3 6))"     "(take 4 (reductions + (range)))"]
   ["take 3 tree-seq infinite"            "(quote (0 0 0))"       "(take 3 (tree-seq (fn [_] true) (fn [n] [n]) 0))"]
   ["sequence xform lazy inf"             "(quote (1 2 3))"       "(take 3 (sequence (map inc) (range)))"]
   ["sequence comp xform inf"             "(quote (2 4 6))"       "(take 3 (sequence (comp (filter odd?) (map inc)) (range)))"]
   ["every? short-circuits on inf"        "false"                 "(every? pos? (range))"]
   ["not-every? short-circuits on inf"    "true"                  "(not-every? pos? (range))"]
   ["take 3 partition 2 range"            "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition 2 (range)))"]
   ["take 3 partition-all 2 range"        "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition-all 2 (range)))"]
   ["take 3 map-indexed vector range"     "(quote ([0 0] [1 1] [2 2]))" "(take 3 (map-indexed vector (range)))"]
   ["take 3 distinct cycle"               "(quote (1 2 3))"       "(take 3 (distinct (cycle [1 2 1 3 1])))"]
   ["take 6 mapcat dup range"             "(quote (0 0 1 1 2 2))" "(take 6 (mapcat (fn [x] [x x]) (range)))"]
   ["first rest lazy"                     "1"                     "(let [[a & r] (range)] (first r))"]
   ["take 3 rest lazy"                    "(quote (1 2 3))"       "(let [[a & r] (range)] (take 3 r))"]
   ["dedupe inf"             "(quote (1 2 1 2 1))" "(take 5 (dedupe (cycle [1 1 2 2])))"]
   ["take 3 take-nth 2 range"             "(quote (0 2 4))"       "(take 3 (take-nth 2 (range)))"]
   ["take 3 interpose :x range"           "(quote (0 :x 1))"       "(take 3 (interpose :x (range)))"]
   ["take 3 map vector range iterate"     "(quote ([0 100] [1 101] [2 102]))" "(take 3 (map vector (range) (iterate inc 100)))"]

   # §6.3 Laziness counter tests — realize exactly the demanded prefix. Under
   # Option A `take` is lazy, so the take result must be forced (dorun) to drive
   # realization; reading the counter without forcing would (correctly) see 0.
   ["LAZY map"            "3"  "(do (def c (atom 0)) (dorun (take 3 (map (fn [x] (swap! c inc) x) (range)))) @c)"]
   ["LAZY filter"         "6"  "(do (def c (atom 0)) (dorun (take 3 (filter (fn [x] (swap! c inc) (odd? x)) (range)))) @c)"]
   ["LAZY remove"         "6"  "(do (def c (atom 0)) (dorun (take 3 (remove (fn [x] (swap! c inc) (even? x)) (range)))) @c)"]
   ["LAZY take-while"     "6"  "(do (def c (atom 0)) (dorun (take-while (fn [x] (swap! c inc) (< x 5)) (range))) @c)"]
   ["LAZY drop-while"     "6"  "(do (def c (atom 0)) (dorun (take 3 (drop-while (fn [x] (swap! c inc) (< x 5)) (range)))) @c)"]
   ["LAZY distinct"       "4"  "(do (def c (atom 0)) (dorun (take 3 (distinct (map (fn [x] (swap! c inc) x) (cycle [1 2 1 3 1]))))) @c)"]
   # 5, was 7: the canonical lazy take-nth (40-lazy, jolt-ded batch 3) realizes
   # only the elements the taken outputs and their drops touch.
   ["LAZY take-nth"       "5"  "(do (def c (atom 0)) (dorun (take 3 (take-nth 2 (map (fn [x] (swap! c inc) x) (range))))) @c)"]
   ["LAZY map-indexed"    "3"  "(do (def c (atom 0)) (dorun (take 3 (map-indexed (fn [i x] (swap! c inc) [i x]) (range)))) @c)"]
   ["LAZY keep"           "6"  "(do (def c (atom 0)) (dorun (take 3 (keep (fn [x] (swap! c inc) (if (odd? x) x nil)) (range)))) @c)"]
   ["LAZY keep-indexed"   "6"  "(do (def c (atom 0)) (dorun (take 3 (keep-indexed (fn [i x] (swap! c inc) (if (odd? i) x)) (range)))) @c)"]
   ["LAZY interpose"      "2"  "(do (def c (atom 0)) (dorun (take 3 (interpose :x (map (fn [x] (swap! c inc) x) (range))))) @c)"]
   ["LAZY partition"      "6"  "(do (def c (atom 0)) (dorun (take 3 (partition 2 (map (fn [x] (swap! c inc) x) (range))))) @c)"]
   ["LAZY partition-all"  "6"  "(do (def c (atom 0)) (dorun (take 3 (partition-all 2 (map (fn [x] (swap! c inc) x) (range))))) @c)"]
   ["LAZY mapcat"         "3"  "(do (def c (atom 0)) (dorun (take 6 (mapcat (fn [x] (swap! c inc) [x x]) (range)))) @c)"]
   ["LAZY dedupe"         "9"  "(do (def c (atom 0)) (dorun (take 5 (dedupe (map (fn [x] (swap! c inc) x) (cycle [1 1 2 2]))))) @c)"]
   ["LAZY repeated inc"   "3"  "(do (def c (atom 0)) (dorun (take 3 (map (fn [x] (swap! c inc) x) (iterate inc 0)))) @c)"]

   # Already-working cases (guard against regression)
   ["take 5 iterate inc"                  "(quote (0 1 2 3 4))"   "(take 5 (iterate inc 0))"]
   ["take 3 range"                        "(quote (0 1 2))"       "(take 3 (range))"]
   ["take 3 repeat"                       "(quote (7 7 7))"       "(take 3 (repeat 7))"]
   ["take 3 cycle"                        "(quote (1 2 1))"       "(take 3 (cycle [1 2]))"]
   ["take 3 filter even? range"           "(quote (0 2 4))"       "(take 3 (filter even? (range)))"]
   ["take 5 lazily filtered from range"   "(quote (1 3 5 7 9))"   "(take 5 (filter odd? (range)))"]
  ])

# ---- Run ----
(var fails @[])
(var timeouts 0)
(var passed 0)

(each [name expected expr] cases
  (def out (run-case expected expr))
  (cond
    (nil? out)
    (do (++ timeouts) (array/push fails (string "TIMEOUT: " name)))
    (let [res (parse-result out)]
      (case (res 0)
        :ok (if (= "true" (res 1))
              (++ passed)
              (array/push fails (string "MISMATCH: " name " — expected " expected)))
        :error (array/push fails (string "ERROR: " name " — " (res 1)))
        (array/push fails (string "PARSE: " name " — raw: " (string/trim out)))))))

(printf "lazy-infinite: %d cases — %d passed / %d timeouts / %d failures"
        (length cases) passed timeouts (length fails))
(when (> (length fails) 0)
  (print "\nFailures:")
  (each f fails (printf "  %s" f)))

(if (or (> (length fails) 0) (> timeouts 0))
  (os/exit 1))
