;; Conformance fixture: drain/depth-limit
;; Exercises the drain-depth-exceeded error. A handler that recursively
;; dispatches itself triggers the configured drain depth limit.
;;
;; Per Spec 002 §Drain versus event — the epoch unit and §Run-to-completion
;; rule 3: the epoch boundary is the dequeued EVENT, not the drain. The
;; runaway events that already ran each settled their OWN durable :ok
;; epoch (and their own db write) before the limit tripped — each settled
;; event is independently atomic, and there is no whole-drain rollback.
;; The depth limit stops the NEXT (halting) event; the runtime commits a
;; trailing :halted-depth :rf/epoch-record for it (Spec-Schemas
;; §:rf/epoch-record §Outcomes) so devtools (Xray, re-frame2-pair) get a
;; clear "drain halted here" marker following the durable :ok epochs. The
;; halting event never ran, so its record's :db-before / :db-after both
;; equal the durable last-settled db.

{:fixture/id           :drain/depth-limit
 :fixture/spec-version "1.0"
 :fixture/capabilities #{:core/event-handler :core/error :core/trace}
 :fixture/doc          "A handler that always dispatches itself recursively. Drain hits the configured depth limit and emits :rf.error/drain-depth-exceeded. Per Spec 002 §Drain versus event (per-event epochs) the events that already ran are durable :ok epochs (no whole-drain rollback); the runtime commits a trailing :halted-depth :rf/epoch-record for the halting event (which never ran) so devtools receive a halt marker."

 :fixture/registry
 {:event {:recursive {:doc "Always dispatches itself."}}
  :sub   {:depth {:doc "How many times the handler ran (durable per-event writes)."}}}

 :fixture/handlers
 {:event {:recursive [[:update [:depth] [:fn :inc]]
                      [:fx :dispatch [:recursive]]]}
  :sub   {:depth [[:get [:depth]]]}}

 :fixture/frame-config
 {:drain-depth 5}                                         ;; small limit for testing

 :fixture/dispatches
 [[:recursive]]

 :fixture/expect
 {;; The handler ran 5 times (the depth limit) before the runtime stopped
  ;; the 6th. Each :recursive event settled its own durable epoch + db
  ;; write — there is no whole-drain rollback — so the final app-db
  ;; reflects the five completed writes.
  :final-app-db {:depth 5}

  :trace-emissions
  [{:operation :rf.event/run-start :tags {:rf.trace/event-id :recursive}}
   {:operation :rf.event/run-start :tags {:rf.trace/event-id :recursive}}
   {:operation :rf.event/run-start :tags {:rf.trace/event-id :recursive}}
   {:operation :rf.event/run-start :tags {:rf.trace/event-id :recursive}}
   {:operation :rf.event/run-start :tags {:rf.trace/event-id :recursive}}
   ;; Drain depth exceeded — the 6th (halting) event is not run. There
   ;; is no whole-drain rollback, so :rollback? is false; the
   ;; already-settled events are durable.
   {:operation :rf.error/drain-depth-exceeded
    :op-type   :error
    :tags      {:category   :rf.error/drain-depth-exceeded
                :depth      5
                :queue-size 1
                :last-event [:recursive]
                :rollback?  false}
    :recovery  :no-recovery}
   ;; The trailing :halted-depth marker (the router's commit-halt-record!
   ;; call). The :rf.epoch/snapshotted trace carries :outcome :halted-depth
   ;; so listeners discriminate it from the durable :ok records. (Each :ok
   ;; event also emits a :rf.epoch/snapshotted; the matcher is a
   ;; subsequence match, so this trailing one is found last.)
   {:operation :rf.epoch/snapshotted
    :tags      {:frame   :rf/default
                :outcome :halted-depth}}
   ;; Paired with :rf.epoch/snapshotted the runtime emits :rf.epoch/outcome
   ;; carrying the consumer-facing {:ok :blocked :error} summary.
   ;; :halted-depth projects to :blocked per the canonical mapping
   ;; (`re-frame.epoch.assembly/outcome->consumer-facing`).
   {:operation :rf.epoch/outcome
    :tags      {:frame   :rf/default
                :outcome :blocked}}]

  ;; Per Spec 002 §Drain versus event: five durable :ok epochs (one per
  ;; dequeued :recursive event, db chaining {:depth 1}…{:depth 5}) followed
  ;; by the trailing :halted-depth marker. The marker's :db-before /
  ;; :db-after both equal the durable last-settled db ({:depth 5}); the
  ;; halting event never ran.
  :epoch-records
  [{:frame :rf/default :record {:outcome :ok :event-id :recursive
                                :db-before {} :db-after {:depth 1}}}
   {:frame :rf/default :record {:outcome :ok :event-id :recursive
                                :db-before {:depth 1} :db-after {:depth 2}}}
   {:frame :rf/default :record {:outcome :ok :event-id :recursive
                                :db-before {:depth 2} :db-after {:depth 3}}}
   {:frame :rf/default :record {:outcome :ok :event-id :recursive
                                :db-before {:depth 3} :db-after {:depth 4}}}
   {:frame :rf/default :record {:outcome :ok :event-id :recursive
                                :db-before {:depth 4} :db-after {:depth 5}}}
   {:frame :rf/default
    :record {:outcome   :halted-depth
             :event-id  :recursive
             :db-before {:depth 5}
             :db-after  {:depth 5}
             :halt-reason
             {:operation  :rf.error/drain-depth-exceeded
              :depth      5
              :queue-size 1
              :last-event [:recursive]}}}]}}
