; lib/artdag/execute.sx — Phase 4: interpret a plan with a content-addressed
; memo cache. A node's result is keyed by its content-id, so a node whose id is
; already in the cache is skipped (cache hit). Because changing a leaf changes
; the content-ids of its whole dirty closure, re-running recomputes exactly those
; nodes and cache-hits the rest — incremental recompute falls out of content
; addressing. Depends on dag.sx and plan.sx; the cache is a lib/persist/ backend.

; runner: (fn (op params input-results) -> result). The injected effect interface.
; In production this performs the op (perform -> JAX/IPFS adapter); in tests it
; dispatches a pure SX op over its already-computed input results.

; build a runner from a dict of op-name -> (fn (params inputs) -> result).
(define
  artdag/op-table-runner
  (fn (table) (fn (op params inputs) ((get table op) params inputs))))

; resolve an input id's result: this run's results first, then the warm cache.
(define
  artdag/-input-result
  (fn
    (results cache in)
    (if (has-key? results in) (get results in) (persist/kv-get cache in))))

(define
  artdag/-exec-node
  (fn
    (dag runner cache acc id)
    (let
      ((node (artdag/dag-get dag id)))
      (if
        (persist/kv-has? cache id)
        (assoc
          acc
          :results (assoc (get acc :results) id (persist/kv-get cache id))
          :hits (concat (get acc :hits) (list id)))
        (let
          ((inputs (map (fn (in) (artdag/-input-result (get acc :results) cache in)) (artdag/node-inputs node))))
          (let
            ((result (runner (artdag/node-op node) (artdag/node-params node) inputs)))
            (begin
              (persist/kv-put cache id result)
              (assoc
                acc
                :results (assoc (get acc :results) id result)
                :recomputed (concat (get acc :recomputed) (list id))))))))))

; execute a plan against a memo cache, returning {:results :recomputed :hits}.
(define
  artdag/execute
  (fn
    (dag plan runner cache)
    (reduce
      (fn (acc id) (artdag/-exec-node dag runner cache acc id))
      {:recomputed (list) :results {} :hits (list)}
      (artdag/plan-flatten plan))))

; full run over every node, unlimited width.
(define
  artdag/run
  (fn
    (dag runner cache)
    (artdag/execute dag (artdag/plan dag 0) runner cache)))

; incremental run: schedule only the dirty closure of the changed nodes.
(define
  artdag/run-dirty
  (fn
    (dag changed runner cache)
    (artdag/execute
      dag
      (artdag/plan-dirty dag changed 0)
      runner
      cache)))

; ---- result inspection ----

(define artdag/result-of (fn (exec id) (get (get exec :results) id)))
(define
  artdag/recomputed
  (fn (exec) (artdag/sort-strings (get exec :recomputed))))
(define artdag/recompute-count (fn (exec) (len (get exec :recomputed))))
(define artdag/hit-count (fn (exec) (len (get exec :hits))))
