;; Go runtime tests — goroutines + channels.

(define go-rt-test-count 0)
(define go-rt-test-pass 0)
(define go-rt-test-fails (list))

(define
  go-rt-test
  (fn
    (name actual expected)
    (set! go-rt-test-count (+ go-rt-test-count 1))
    (if
      (= actual expected)
      (set! go-rt-test-pass (+ go-rt-test-pass 1))
      (append! go-rt-test-fails {:name name :expected expected :actual actual}))))

;; ── channel primitives (direct API, no source parsing) ─────────
(go-rt-test "chan: make returns a chan value" (go-chan? (go-make-chan)) true)

(go-rt-test
  "chan: distinct channels have distinct identity"
  (= (go-make-chan) (go-make-chan))
  false)

(go-rt-test
  "chan: send + recv round-trip"
  (let
    ((ch (go-make-chan)))
    (go-chan-send! ch 42)
    (go-chan-recv! ch))
  42)

(go-rt-test
  "chan: empty recv returns :empty marker"
  (let ((ch (go-make-chan))) (go-chan-recv! ch))
  :empty)

(go-rt-test
  "chan: FIFO order"
  (let
    ((ch (go-make-chan)))
    (go-chan-send! ch 1)
    (go-chan-send! ch 2)
    (go-chan-send! ch 3)
    (list (go-chan-recv! ch) (go-chan-recv! ch) (go-chan-recv! ch)))
  (list 1 2 3))

(go-rt-test
  "chan: closed? flag flips"
  (let
    ((ch (go-make-chan)))
    (let
      ((before (go-chan-closed? ch)))
      (go-chan-close! ch)
      (list before (go-chan-closed? ch))))
  (list false true))

;; ── source-level: make / send / recv / close ───────────────────
(go-rt-test
  "src: ch := make() returns chan"
  (go-chan?
    (let
      ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()")))))
      (go-env-lookup env "ch")))
  true)

(go-rt-test
  "src: ch <- 5 then <-ch = 5"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 5")))))
    (go-eval env (go-parse "<-ch")))
  5)

(go-rt-test
  "src: go + chan ping-pong"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func sender(c chan int) { c <- 99 }") (go-parse "ch := make()") (go-parse "go sender(ch)")))))
    (go-eval env (go-parse "<-ch")))
  99)

(go-rt-test
  "src: close(ch) marks it closed"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "close(ch)")))))
    (go-chan-closed? (go-env-lookup env "ch")))
  true)

(go-rt-test
  "src: multiple goroutines feeding one channel"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func push(c chan int, v int) { c <- v }") (go-parse "ch := make()") (go-parse "go push(ch, 1)") (go-parse "go push(ch, 2)") (go-parse "go push(ch, 3)")))))
    (list
      (go-eval env (go-parse "<-ch"))
      (go-eval env (go-parse "<-ch"))
      (go-eval env (go-parse "<-ch"))))
  (list 1 2 3))

(go-rt-test
  "src: worker pattern — send sum back"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func work(c chan int, a int, b int) { c <- a + b }") (go-parse "result := make()") (go-parse "go work(result, 7, 13)")))))
    (go-eval env (go-parse "<-result")))
  20)

;; ── report ─────────────────────────────────────────────────────
(go-rt-test
  "select: default runs when no case is ready"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "x := 0") (go-parse "select { case <-ch: x = 1 ; default: x = 99 }")))))
    (go-env-lookup env "x"))
  99)

(go-rt-test
  "select: recv case fires when ready"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 7") (go-parse "x := 0") (go-parse "select { case <-ch: x = 1 ; default: x = 99 }")))))
    (go-env-lookup env "x"))
  1)

(go-rt-test
  "select: recv-into-var binds the value"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 42") (go-parse "select { case v := <-ch: v }")))))
    (go-env-lookup env "v"))
  42)

(go-rt-test
  "select: send case (always ready in v0)"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "select { case ch <- 5: }")))))
    (go-chan-len (go-env-lookup env "ch")))
  1)

(go-rt-test
  "select: picks first ready case"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "b <- 100") (go-parse "x := 0") (go-parse "select { case <-a: x = 1 ; case <-b: x = 2 ; default: x = 99 }")))))
    (go-env-lookup env "x"))
  2)

(go-rt-test
  "select: no default + nothing ready → blocked error"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()")))))
    (go-eval-stmt env (go-parse "select { case <-ch: }") (list)))
  (list :eval-error :select-blocked-no-default))

(go-rt-test
  "select: combined with goroutine fan-in"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func push(c chan int, v int) { c <- v }") (go-parse "ch := make()") (go-parse "go push(ch, 7)") (go-parse "result := 0") (go-parse "select { case v := <-ch: result = v ; default: result = -1 }")))))
    (go-env-lookup env "result"))
  7)

(go-rt-test
  "range: slice — sum of 1..5"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var sum = 0") (go-parse "a := []int{1, 2, 3, 4, 5}") (go-parse "for i, v := range a { sum = sum + v }")))))
    (go-env-lookup env "sum"))
  15)

(go-rt-test
  "range: slice — key only (index)"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var s = 0") (go-parse "a := []int{10, 20, 30}") (go-parse "for i := range a { s = s + i }")))))
    (go-env-lookup env "s"))
  3)

(go-rt-test
  "range: map — sum values"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var s = 0") (go-parse "m := map[string]int{\"a\": 1, \"b\": 2, \"c\": 3}") (go-parse "for k, v := range m { s = s + v }")))))
    (go-env-lookup env "s"))
  6)

(go-rt-test
  "range: channel — collect all buffered"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 1") (go-parse "ch <- 2") (go-parse "ch <- 3") (go-parse "var sum = 0") (go-parse "for v := range ch { sum = sum + v }")))))
    (go-env-lookup env "sum"))
  6)

(go-rt-test
  "range: slice with break exits early"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var s = 0") (go-parse "a := []int{1, 2, 3, 4, 5}") (go-parse "for i, v := range a { if v == 3 { break } ; s = s + v }")))))
    (go-env-lookup env "s"))
  3)

(go-rt-test
  "range: slice with continue skips an element"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var s = 0") (go-parse "a := []int{1, 2, 3, 4, 5}") (go-parse "for i, v := range a { if v == 3 { continue } ; s = s + v }")))))
    (go-env-lookup env "s"))
  12)

(go-rt-test
  "range: empty slice — body never runs"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "var s = 0") (go-parse "a := []int{}") (go-parse "for v := range a { s = s + v }")))))
    (go-env-lookup env "s"))
  0)

(go-rt-test
  "range: chan + goroutine producer"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func emit(c chan int) { c <- 10 ; c <- 20 ; c <- 30 }") (go-parse "ch := make()") (go-parse "go emit(ch)") (go-parse "var total = 0") (go-parse "for v := range ch { total = total + v }")))))
    (go-env-lookup env "total"))
  60)

(go-rt-test
  "timer: after(d) returns a ready channel (v0 stub)"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "t := after(100)")))))
    (go-chan-len (go-env-lookup env "t")))
  1)

(go-rt-test
  "select with timer (after) — buffered value wins, timer is fallback"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func push99(c chan int) { c <- 99 }") (go-parse "c := make()") (go-parse "go push99(c)") (go-parse "t := after(0)") (go-parse "var v = 0") (go-parse "select { case x := <-c: v = x; case y := <-t: v = -1 }")))))
    (go-env-lookup env "v"))
  99)

(go-rt-test
  "fan-in: 3 producer goroutines, main sums their values"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func send10(c chan int) { c <- 10 }") (go-parse "func send20(c chan int) { c <- 20 }") (go-parse "func send30(c chan int) { c <- 30 }") (go-parse "c := make()") (go-parse "go send10(c)") (go-parse "go send20(c)") (go-parse "go send30(c)") (go-parse "var s = 0") (go-parse "for i := 0; i < 3; i = i + 1 { v := <-c ; s = s + v }")))))
    (go-env-lookup env "s"))
  60)

(go-rt-test
  "worker queue: range over closed buffered chan drains all jobs"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "jobs := make()") (go-parse "jobs <- 1") (go-parse "jobs <- 2") (go-parse "jobs <- 3") (go-parse "jobs <- 4") (go-parse "close(jobs)") (go-parse "var s = 0") (go-parse "for j := range jobs { s = s + j }")))))
    (go-env-lookup env "s"))
  10)

(go-rt-test
  "pipeline: stage1 squares, stage2 sums via channels"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func sq(in chan int, out chan int) { for v := range in { out <- v * v } ; close(out) }") (go-parse "in := make()") (go-parse "out := make()") (go-parse "in <- 2") (go-parse "in <- 3") (go-parse "in <- 4") (go-parse "close(in)") (go-parse "go sq(in, out)") (go-parse "var s = 0") (go-parse "for v := range out { s = s + v }")))))
    (go-env-lookup env "s"))
  29)

(go-rt-test
  "fan-out then fan-in: split job stream across N workers, collect results"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func worker(in chan int, out chan int) { for v := range in { out <- v + 100 } }") (go-parse "jobs := make()") (go-parse "results := make()") (go-parse "jobs <- 1") (go-parse "jobs <- 2") (go-parse "jobs <- 3") (go-parse "close(jobs)") (go-parse "go worker(jobs, results)") (go-parse "close(results)") (go-parse "var s = 0") (go-parse "for r := range results { s = s + r }")))))
    (go-env-lookup env "s"))
  306)

(go-rt-test
  "select: first ready case wins (channel order = source order)"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "a <- 1") (go-parse "b <- 2") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = 10; case y := <-b: v = 20 }")))))
    (go-env-lookup env "v"))
  10)

(go-rt-test
  "select: only second case has a value, that branch executes"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "b <- 7") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = -1; case y := <-b: v = y }")))))
    (go-env-lookup env "v"))
  7)

(go-rt-test
  "select with default: no case ready → default fires"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = 1; case y := <-b: v = 2; default: v = 99 }")))))
    (go-env-lookup env "v"))
  99)

(go-rt-test
  "producer-consumer: one goroutine fills, main drains by count"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func fill5(c chan int) { c <- 1 ; c <- 2 ; c <- 3 ; c <- 4 ; c <- 5 }") (go-parse "c := make()") (go-parse "go fill5(c)") (go-parse "var s = 0") (go-parse "for i := 0; i < 5; i = i + 1 { v := <-c ; s = s + v }")))))
    (go-env-lookup env "s"))
  15)

(go-rt-test
  "two-stage pipeline: doubler + adder threaded through 3 channels"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func dbl(in chan int, mid chan int) { for v := range in { mid <- v * 2 } ; close(mid) }") (go-parse "func plus1(mid chan int, out chan int) { for v := range mid { out <- v + 1 } ; close(out) }") (go-parse "in := make()") (go-parse "mid := make()") (go-parse "out := make()") (go-parse "in <- 1") (go-parse "in <- 2") (go-parse "in <- 3") (go-parse "close(in)") (go-parse "go dbl(in, mid)") (go-parse "go plus1(mid, out)") (go-parse "var s = 0") (go-parse "for v := range out { s = s + v }")))))
    (go-env-lookup env "s"))
  15)

(go-rt-test
  "channel as counter: append integers, count buffer size"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func fillN(c chan int, n int) { for i := 0; i < n; i = i + 1 { c <- i } }") (go-parse "c := make()") (go-parse "go fillN(c, 7)")))))
    (go-chan-len (go-env-lookup env "c")))
  7)

(go-rt-test
  "after(0) + select with default: timer ready, default not taken"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "t := after(0)") (go-parse "var v = 0") (go-parse "select { case x := <-t: v = 7; default: v = -1 }")))))
    (go-env-lookup env "v"))
  7)

(go-rt-test
  "tick collector: timer + counter accumulates ticks via range count"
  (let
    ((env (go-eval-program go-env-builtins (list (go-parse "func emitN(c chan int, n int) { for i := 0; i < n; i = i + 1 { c <- 1 } ; close(c) }") (go-parse "ticks := make()") (go-parse "go emitN(ticks, 5)") (go-parse "var total = 0") (go-parse "for t := range ticks { total = total + t }")))))
    (go-env-lookup env "total"))
  5)

(define
  go-rt-test-summary
  (str "runtime " go-rt-test-pass "/" go-rt-test-count))
