;; content-on-sx — portable data serialization.
;;
;; Converts documents to/from a plain SX data form, decoupling storage and
;; transport from the Smalltalk instance shape. A document becomes
;;   {:id :title :slug :tags :blocks (list block-data)}
;; and a block becomes {:id :type :fields {...}} (section children recurse).
;; content/from-data reconstructs real block objects.
;;
;; Requires (loaded by harness): block.sx, doc.sx, meta.sx, section.sx
;; (mk-section), table.sx (mk-table).

;; ── to-data ──
(define
  content/-fd-loop
  (fn
    (ks ivs acc)
    (if
      (= (len ks) 0)
      acc
      (let
        ((k (first ks)))
        (if
          (= k "id")
          (content/-fd-loop (rest ks) ivs acc)
          (content/-fd-loop
            (rest ks)
            ivs
            (assoc
              acc
              k
              (if
                (= k "children")
                (map block->data (get ivs k))
                (get ivs k)))))))))

(define block->data (fn (b) {:fields (content/-fd-loop (keys (get b :ivars)) (get b :ivars) {}) :id (blk-id b) :type (blk-type b)}))

(define content/to-data (fn (doc) {:blocks (map block->data (doc-blocks doc)) :slug (doc-slug doc) :id (doc-id doc) :title (doc-title doc) :tags (doc-tags doc)}))

;; ── from-data ──
(define
  content/-field-pairs
  (fn (fields) (map (fn (k) (list k (get fields k))) (keys fields))))

(define
  data->block
  (fn
    (d)
    (let
      ((type (get d :type)) (id (get d :id)) (fields (get d :fields)))
      (cond
        ((= type "section")
          (mk-section id (map data->block (get fields "children"))))
        ((= type "table")
          (mk-table id (get fields "headers") (get fields "rows")))
        (else (mk-block type id (content/-field-pairs fields)))))))

(define
  content/-meta-of
  (fn
    (data)
    (let
      ((m1 (if (= (get data :title) nil) {} (assoc {} :title (get data :title)))))
      (let
        ((m2 (if (= (get data :slug) nil) m1 (assoc m1 :slug (get data :slug)))))
        (let
          ((tags (get data :tags)))
          (if
            (or (= tags nil) (= (len tags) 0))
            m2
            (assoc m2 :tags tags)))))))

(define
  content/from-data
  (fn
    (data)
    (doc-with-meta
      (doc-new (get data :id) (map data->block (get data :blocks)))
      (content/-meta-of data))))
