; Phase 4 — Execute: effect interpreter + content-addressed memo + incremental.

(define ex-RT (artdag/op-table-runner {:in (fn (params inputs) (get params :v)) :add (fn (params inputs) (+ (nth inputs 0) (nth inputs 1))) :inc (fn (params inputs) (+ 1 (first inputs)))}))

; two-leaf diamond: p,q leaves; b=inc(p); c=inc(q); d=add(b,c)
(define
  ex-D1
  (artdag/build
    (list
      (list "p" "in" (list) {:v 10})
      (list "q" "in" (list) {:v 20})
      (list "b" "inc" (list "p") {})
      (list "c" "inc" (list "q") {})
      (list "d" "add" (list "b" "c") {} true))))

; same shape, leaf q changed (20 -> 21)
(define
  ex-D2
  (artdag/build
    (list
      (list "p" "in" (list) {:v 10})
      (list "q" "in" (list) {:v 21})
      (list "b" "inc" (list "p") {})
      (list "c" "inc" (list "q") {})
      (list "d" "add" (list "b" "c") {} true))))

; a different dag that shares the p->b subgraph with ex-D1, plus z=inc(b)
(define
  ex-D3
  (artdag/build
    (list
      (list "p" "in" (list) {:v 10})
      (list "b" "inc" (list "p") {})
      (list "z" "inc" (list "b") {}))))

; ---- full execution ----

(artdag-test
  "full run: result is correct"
  (let
    ((cache (persist/open)))
    (artdag/result-of
      (artdag/run ex-D1 ex-RT cache)
      (artdag/dag-id ex-D1 "d")))
  32)

(artdag-test
  "full run: cold cache recomputes every node"
  (let
    ((cache (persist/open)))
    (artdag/recompute-count (artdag/run ex-D1 ex-RT cache)))
  5)

(artdag-test
  "full run: cold cache has no hits"
  (let
    ((cache (persist/open)))
    (artdag/hit-count (artdag/run ex-D1 ex-RT cache)))
  0)

; ---- memoization ----

(artdag-test
  "re-run unchanged: zero recomputes"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/recompute-count (artdag/run ex-D1 ex-RT cache))))
  0)

(artdag-test
  "re-run unchanged: all cache hits"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/hit-count (artdag/run ex-D1 ex-RT cache))))
  5)

(artdag-test
  "re-run unchanged: result preserved"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/result-of
        (artdag/run ex-D1 ex-RT cache)
        (artdag/dag-id ex-D1 "d"))))
  32)

; ---- incremental recompute (the keystone) ----

(artdag-test
  "leaf change recomputes only the dirty closure (count)"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/recompute-count (artdag/run ex-D2 ex-RT cache))))
  3)

(artdag-test
  "leaf change: unchanged nodes are cache hits"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/hit-count (artdag/run ex-D2 ex-RT cache))))
  2)

(artdag-test
  "leaf change: recomputed set is exactly q,c,d"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/recomputed (artdag/run ex-D2 ex-RT cache))))
  (artdag/sort-strings
    (list
      (artdag/dag-id ex-D2 "q")
      (artdag/dag-id ex-D2 "c")
      (artdag/dag-id ex-D2 "d"))))

(artdag-test
  "leaf change: untouched sibling p is reused"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/member?
        (artdag/dag-id ex-D2 "p")
        (get (artdag/run ex-D2 ex-RT cache) :hits))))
  true)

(artdag-test
  "leaf change: new result is correct"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/result-of
        (artdag/run ex-D2 ex-RT cache)
        (artdag/dag-id ex-D2 "d"))))
  33)

; ---- explicit dirty-only execution ----

(artdag-test
  "run-dirty: schedules only the changed closure"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/recompute-count
        (artdag/run-dirty ex-D2 (list (artdag/dag-id ex-D2 "q")) ex-RT cache))))
  3)

; ---- cross-dag cache sharing (content addressing) ----

(artdag-test
  "shared subgraph hits cache across different dags"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/recompute-count (artdag/run ex-D3 ex-RT cache))))
  1)

(artdag-test
  "shared subgraph: p and b reused across dags"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/hit-count (artdag/run ex-D3 ex-RT cache))))
  2)

(artdag-test
  "shared subgraph: z still computes correctly"
  (let
    ((cache (persist/open)))
    (begin
      (artdag/run ex-D1 ex-RT cache)
      (artdag/result-of
        (artdag/run ex-D3 ex-RT cache)
        (artdag/dag-id ex-D3 "z"))))
  12)
