;; Phase 2 — op log + versioning over persist. The log is the source of truth;
;; any version is a replay of the op stream up to a seq.

(st-bootstrap-classes!)
(content-bootstrap-blocks!)
(content-bootstrap-doc!)

(define B (persist/open))
(define h (mk-heading "h" 1 "Title"))
(define p (mk-text "p" "Body"))
(define img (mk-image "img" "/c.png" "cat"))

;; ── commit an op stream ──
(content/commit! B "post" (op-insert h nil) 10)
(content/commit! B "post" (op-insert p "h") 11)
(content/commit! B "post" (op-insert img "h") 12)
(content/commit! B "post" (op-update "p" "text" "Edited") 13)
(content/commit! B "post" (op-delete "img") 14)

(content-test "version-count" (content/version-count B "post") 5)
(content-test "log length" (len (content/log B "post")) 5)

;; ── head: latest materialised document ──
(content-test "head ids" (doc-ids (content/head B "post")) (list "h" "p"))
(content-test
  "head p edited"
  (str (blk-send (doc-find (content/head B "post") "p") "text"))
  "Edited")

;; ── replay to any version ──
(content-test
  "at seq1"
  (doc-ids (content/at B "post" 1))
  (list "h"))
(content-test
  "at seq2"
  (doc-ids (content/at B "post" 2))
  (list "h" "p"))
(content-test
  "at seq3"
  (doc-ids (content/at B "post" 3))
  (list "h" "img" "p"))
(content-test
  "at seq3 p original"
  (str (blk-send (doc-find (content/at B "post" 3) "p") "text"))
  "Body")
(content-test
  "at seq4 p edited"
  (str (blk-send (doc-find (content/at B "post" 4) "p") "text"))
  "Edited")
(content-test
  "at seq5 img gone"
  (doc-ids (content/at B "post" 5))
  (list "h" "p"))
(content-test
  "at seq0 empty"
  (doc-ids (content/at B "post" 0))
  (list))

;; ── ops accessor ──
(content-test
  "ops kinds"
  (map (fn (o) (get o :op)) (content/ops B "post"))
  (list "insert" "insert" "insert" "update" "delete"))

;; ── history metadata ──
(define hist (content/history B "post"))
(content-test "history length" (len hist) 5)
(content-test "history first seq" (get (first hist) :seq) 1)
(content-test "history first type" (get (first hist) :type) "insert")
(content-test "history first at" (get (first hist) :at) 10)
(content-test
  "history fourth type"
  (get (nth hist 3) :type)
  "update")

;; ── diff between versions ──
(define dvf (content/diff-versions B "post" 1 3))
(content-test "diff added" (get dvf :added) (list "img" "p"))
(content-test "diff removed empty" (get dvf :removed) (list))
(content-test "diff changed empty" (get dvf :changed) (list))

(define dvf2 (content/diff-versions B "post" 3 5))
(content-test "diff2 removed" (get dvf2 :removed) (list "img"))
(content-test "diff2 changed" (get dvf2 :changed) (list "p"))
(content-test "diff2 added empty" (get dvf2 :added) (list))

;; ── direct diff of two materialised docs ──
(define da (content/at B "post" 2))
(define db (content/at B "post" 5))
(content-test
  "direct diff changed"
  (get (content/diff da db) :changed)
  (list "p"))
(content-test
  "direct diff no-op"
  (get (content/diff da da) :changed)
  (list))

;; ── commit-all batch ──
(define B2 (persist/open))
(content/commit-all!
  B2
  "doc2"
  (list (op-insert h nil) (op-insert p "h"))
  1)
(content-test "commit-all count" (content/version-count B2 "doc2") 2)
(content-test
  "commit-all head"
  (doc-ids (content/head B2 "doc2"))
  (list "h" "p"))

;; ── stream isolation ──
(content-test
  "separate stream empty"
  (content/version-count B "doc2")
  0)
(content-test
  "head of empty stream"
  (doc-ids (content/head B "never"))
  (list))

;; ── op-log carries non-core block types (callout/media) through replay ──
(content-bootstrap-callout!)
(content-bootstrap-media!)
(define B3 (persist/open))
(content/commit!
  B3
  "rich"
  (op-insert (mk-callout "co" "note" "hi") nil)
  1)
(content/commit!
  B3
  "rich"
  (op-insert (mk-media "v" "video" "/c.mp4") "co")
  2)
(content/commit! B3 "rich" (op-update "co" "text" "edited") 3)
(content-test
  "op-log rich ids"
  (doc-ids (content/head B3 "rich"))
  (list "co" "v"))
(content-test
  "op-log callout type"
  (blk-type (doc-find (content/head B3 "rich") "co"))
  "callout")
(content-test
  "op-log callout update"
  (str (blk-send (doc-find (content/head B3 "rich") "co") "text"))
  "edited")
(content-test
  "op-log media type"
  (blk-type (doc-find (content/head B3 "rich") "v"))
  "media")

;; ── op-log update/delete reach NESTED blocks (tree-wide by id) ──
(content-bootstrap-section!)
(define B4 (persist/open))
(content/commit!
  B4
  "nest"
  (op-insert (mk-section "sec" (list (mk-text "n" "orig"))) nil)
  1)
(content/commit! B4 "nest" (op-update "n" "text" "edited") 2)
(content-test
  "op-log nested update"
  (str (blk-send (doc-deep-find (content/head B4 "nest") "n") "text"))
  "edited")
(content-test
  "op-log nested update tree intact"
  (doc-tree-ids (content/head B4 "nest"))
  (list "sec" "n"))
(content/commit! B4 "nest" (op-delete "n") 3)
(content-test
  "op-log nested delete"
  (doc-tree-ids (content/head B4 "nest"))
  (list "sec"))
(content-test
  "op-log nested delete via content/at seq2"
  (doc-tree-ids (content/at B4 "nest" 2))
  (list "sec" "n"))

;; ── diff is TREE-WIDE: nested-block add/change/remove are detected, and
;; section containers never appear in :changed (a top-level-only diff would miss
;; "n" entirely and instead flag the section). ──
(define dn01 (content/diff-versions B4 "nest" 0 1))
(content-test
  "diff nested added (section + child)"
  (get dn01 :added)
  (list "sec" "n"))
(content-test "diff nested added removed empty" (get dn01 :removed) (list))
(content-test "diff nested added changed empty" (get dn01 :changed) (list))

(define dn12 (content/diff-versions B4 "nest" 1 2))
(content-test
  "diff nested changed child only"
  (get dn12 :changed)
  (list "n"))
(content-test "diff nested changed no add" (get dn12 :added) (list))
(content-test "diff nested changed no remove" (get dn12 :removed) (list))

(define dn23 (content/diff-versions B4 "nest" 2 3))
(content-test "diff nested removed child" (get dn23 :removed) (list "n"))
(content-test "diff nested removed no change" (get dn23 :changed) (list))

(content-test
  "diff nested no-op"
  (get (content/diff-versions B4 "nest" 1 1) :changed)
  (list))
