;; content-on-sx — nested-tree CvRDT.
;;
;; Extends the flat CvRDT (crdt.sx) to a TREE: each element carries a `parent`
;; (the id of its containing section, "" = root) alongside its Logoot position.
;; Merge is still a join — it reuses crdt.sx's position/register/field merges and
;; adds parent (immutable, set once at insert). Materialisation rebuilds the
;; ordered tree: root = elements with parent "" (plus ORPHANS — elements whose
;; parent is not a live section, e.g. after a concurrent delete-section +
;; insert-child, so content is never silently lost); a section's children =
;; elements whose parent is that section's id. Commutative/associative/idempotent
;; like the flat layer.
;;
;; Requires (loaded by harness): crdt.sx (merge helpers + live/sort/materialise
;; bits + crdt-member?), block.sx, doc.sx, section.sx (mk-section).

(define ctt-merge-parent (fn (p1 p2) (if (= p1 nil) p2 p1)))

(define ctt-merge-element (fn (e1 e2) {:fields (crdt-merge-fields (get e1 :fields) (get e2 :fields)) :parent (ctt-merge-parent (get e1 :parent) (get e2 :parent)) :id (get e1 :id) :type (crdt-merge-type (get e1 :type) (get e2 :type)) :deleted (or (= (get e1 :deleted) true) (= (get e2 :deleted) true)) :pos (crdt-merge-pos (get e1 :pos) (get e2 :pos))}))

(define
  ctt-add-element
  (fn
    (state elem)
    (let
      ((elems (get state :elements)) (id (get elem :id)))
      (let
        ((existing (get elems id)))
        (assoc
          state
          :elements (assoc
            elems
            id
            (if (= existing nil) elem (ctt-merge-element existing elem))))))))

;; ── ops as partial-element contributions ──
(define
  crdt-tree-insert
  (fn
    (state id type pos parent fields ts actor)
    (ctt-add-element state {:fields (crdt-build-fields fields ts actor) :parent parent :id id :type type :deleted false :pos pos})))

(define
  crdt-tree-update
  (fn (state id fname value ts actor) (ctt-add-element state {:fields (assoc {} fname {:ts ts :actor actor :value value}) :parent nil :id id :type nil :deleted false :pos nil})))

(define crdt-tree-delete (fn (state id) (ctt-add-element state {:fields {} :parent nil :id id :type nil :deleted true :pos nil})))

;; ── state merge (join) ──
(define
  ctt-merge-loop
  (fn
    (ids ea eb acc)
    (if
      (= (len ids) 0)
      acc
      (let
        ((id (first ids)))
        (let
          ((x (get ea id)) (y (get eb id)))
          (ctt-merge-loop
            (rest ids)
            ea
            eb
            (assoc
              acc
              id
              (cond
                ((= x nil) y)
                ((= y nil) x)
                (else (ctt-merge-element x y))))))))))

(define crdt-tree-merge (fn (a b) {:elements (ctt-merge-loop (crdt-union-keys (get a :elements) (get b :elements)) (get a :elements) (get b :elements) {})}))

(define
  crdt-tree-merge-all
  (fn
    (states)
    (if
      (= (len states) 0)
      (crdt-empty)
      (if
        (= (len states) 1)
        (first states)
        (crdt-tree-merge (first states) (crdt-tree-merge-all (rest states)))))))

;; ── op interpreter ──
(define
  crdt-tree-op-insert
  (fn (id type pos parent fields ts actor) {:ts ts :fields fields :parent parent :id id :type type :op "insert" :actor actor :pos pos}))

(define crdt-tree-op-update (fn (id field value ts actor) {:ts ts :field field :id id :op "update" :actor actor :value value}))

(define crdt-tree-op-delete (fn (id) {:id id :op "delete"}))

(define
  crdt-tree-apply
  (fn
    (state op)
    (let
      ((k (get op :op)))
      (cond
        ((= k "insert")
          (crdt-tree-insert
            state
            (get op :id)
            (get op :type)
            (get op :pos)
            (get op :parent)
            (get op :fields)
            (get op :ts)
            (get op :actor)))
        ((= k "update")
          (crdt-tree-update
            state
            (get op :id)
            (get op :field)
            (get op :value)
            (get op :ts)
            (get op :actor)))
        ((= k "delete") (crdt-tree-delete state (get op :id)))
        (else (error (str "unknown crdt-tree op: " k)))))))

(define
  crdt-tree-apply-all
  (fn
    (state ops)
    (if
      (= (len ops) 0)
      state
      (crdt-tree-apply-all (crdt-tree-apply state (first ops)) (rest ops)))))

;; ── materialise to a Phase-1 document (rebuild the ordered tree) ──
(define
  ctt-live-section-ids
  (fn
    (state)
    (map
      (fn (e) (get e :id))
      (filter
        (fn (e) (= (get e :type) "section"))
        (crdt-live-elements state)))))

;; an element belongs at root if its parent is "" or its parent is not a live
;; section (orphan-reparenting: don't lose content when its section is deleted).
(define
  ctt-roots
  (fn
    (state)
    (let
      ((secids (ctt-live-section-ids state)))
      (crdt-sort-by-pos
        (filter
          (fn
            (e)
            (if
              (= (get e :parent) "")
              true
              (if (crdt-member? (get e :parent) secids) false true)))
          (crdt-live-elements state))))))

(define
  ctt-children
  (fn
    (state parent-id)
    (crdt-sort-by-pos
      (filter
        (fn (e) (= (get e :parent) parent-id))
        (crdt-live-elements state)))))

(define
  ctt-element->block
  (fn
    (state e)
    (if
      (= (get e :type) "section")
      (mk-section
        (get e :id)
        (map
          (fn (c) (ctt-element->block state c))
          (ctt-children state (get e :id))))
      (crdt-element->block e))))

(define
  crdt-tree-materialize
  (fn
    (doc-id state)
    (doc-new
      doc-id
      (map (fn (e) (ctt-element->block state e)) (ctt-roots state)))))

(define
  crdt-tree-order
  (fn (state) (map (fn (e) (get e :id)) (ctt-roots state))))
