;; lib/flow/tests/suspend.sx — Phase 3: suspend / resume / cancel (deterministic replay).

(define flow-sus-pass 0)
(define flow-sus-fail 0)
(define flow-sus-fails (list))

(define
  flow-sus-test
  (fn
    (name actual expected)
    (if
      (= actual expected)
      (set! flow-sus-pass (+ flow-sus-pass 1))
      (begin
        (set! flow-sus-fail (+ flow-sus-fail 1))
        (append! flow-sus-fails {:name name :expected expected :actual actual})))))

(define flow-s (fn (src) (flow-run src)))

;; ── flow/start ──────────────────────────────────────────────────
(flow-sus-test
  "start: non-suspending flow returns the raw result"
  (flow-s "(flow/start (lambda (x) (* x 2)) 5)")
  10)
(flow-sus-test
  "start: a suspending flow returns a flow-suspended state"
  (flow-s
    "(defflow w (sequence (lambda (x) (+ x 1)) (lambda (g) (suspend (quote await))) (lambda (c) c))) (car (flow/start w 10))")
  "flow-suspended")
(flow-sus-test
  "start: suspended state carries a numeric id"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote await)))) (car (cdr (flow/start w 10)))")
  1)
(flow-sus-test
  "start: suspended state carries the suspend tag"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote await)))) (car (cdr (cdr (flow/start w 10))))")
  "await")

;; ── flow/resume ─────────────────────────────────────────────────
(flow-sus-test
  "resume: injects the value and completes"
  (flow-s
    "(defflow w (sequence (lambda (x) (+ x 1)) (lambda (g) (suspend (quote await))) (lambda (c) (list (quote done) c)))) (define s (flow/start w 10)) (flow/resume (car (cdr s)) 777)")
  (list "done" 777))
(flow-sus-test
  "resume: injected value threads into the next node"
  (flow-s
    "(defflow w (sequence (lambda (x) (suspend (quote v))) (lambda (n) (* n 3)))) (define s (flow/start w 0)) (flow/resume (car (cdr s)) 14)")
  42)
(flow-sus-test
  "resume: replays earlier suspends (recompute is deterministic)"
  (flow-s
    "(define runs 0) (defflow w (sequence (lambda (x) (begin (set! runs (+ runs 1)) (+ x 1))) (lambda (g) (suspend (quote await))) (lambda (c) c))) (define s (flow/start w 10)) (flow/resume (car (cdr s)) 99) runs")
  2)

;; ── multi-step suspension ───────────────────────────────────────
(flow-sus-test
  "multi: first resume suspends at the next tag"
  (flow-s
    "(defflow two (sequence (lambda (x) (suspend (quote a))) (lambda (x) (suspend (quote b))) (lambda (x) (list (quote end) x)))) (define s (flow/start two 0)) (define s2 (flow/resume (car (cdr s)) 100)) (car (cdr (cdr s2)))")
  "b")
(flow-sus-test
  "multi: second resume completes with the latest value"
  (flow-s
    "(defflow two (sequence (lambda (x) (suspend (quote a))) (lambda (x) (suspend (quote b))) (lambda (x) (list (quote end) x)))) (define id (car (cdr (flow/start two 0)))) (flow/resume id 100) (flow/resume id 200)")
  (list "end" 200))

;; ── error / lifecycle guards ────────────────────────────────────
(flow-sus-test
  "resume: completed flow cannot be resumed again"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote q)))) (define id (car (cdr (flow/start w 0)))) (flow/resume id 1) (flow/resume id 2)")
  (list "flow-error" "not-suspended"))
(flow-sus-test
  "resume: unknown id errors"
  (flow-s "(flow/resume 999 1)")
  (list "flow-error" "no-such-flow"))

;; ── flow/cancel ─────────────────────────────────────────────────
(flow-sus-test
  "cancel: returns a flow-cancelled state"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote q)))) (define id (car (cdr (flow/start w 0)))) (flow/cancel id)")
  (list "flow-cancelled" 1))
(flow-sus-test
  "cancel: a cancelled flow cannot be resumed (stale resume rejected)"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote q)))) (define id (car (cdr (flow/start w 0)))) (flow/cancel id) (flow/resume id 5)")
  (list "flow-error" "not-suspended"))
(flow-sus-test
  "cancel: unknown id errors"
  (flow-s "(flow/cancel 999)")
  (list "flow-error" "no-such-flow"))

;; ── composition ─────────────────────────────────────────────────
(flow-sus-test
  "suspend inside a branch arm"
  (flow-s
    "(defflow gate (branch (lambda (x) (> x 0)) (lambda (x) (suspend (quote approve))) (flow-const (quote rejected)))) (define s (flow/start gate 5)) (flow/resume (car (cdr s)) (quote approved))")
  "approved")
(flow-sus-test
  "two independent runs get independent ids"
  (flow-s
    "(defflow w (lambda (x) (suspend (quote q)))) (list (car (cdr (flow/start w 0))) (car (cdr (flow/start w 0))))")
  (list 1 2))
(flow-sus-test
  "suspend reason may be a structured value"
  (flow-s
    "(defflow w (lambda (x) (suspend (list (quote needs) (quote approval))))) (car (cdr (cdr (flow/start w 0))))")
  (list "needs" "approval"))

(define flow-sus-tests-run! (fn () {:total (+ flow-sus-pass flow-sus-fail) :passed flow-sus-pass :failed flow-sus-fail :fails flow-sus-fails}))
