;; content-on-sx — global find/replace across every text-bearing field.
;;
;; Replaces every occurrence of `from` with `to` in the text-bearing fields of
;; a document, tree-wide (via the transform layer):
;;   - the `text` of text / heading / code / quote / callout blocks
;;   - the `alt` of image blocks
;;   - each item of list blocks
;;   - every header and cell of table blocks
;; This is exactly the set asText / stats / summary draw prose from, so a rename
;; via content/find-replace and a word count over asText stay consistent.
;; Immutable; case-sensitive.
;;
;; Requires (loaded by harness): block.sx, transform.sx (content/map-blocks),
;; table.sx (CtTable ivars).

(define
  fr-in?
  (fn
    (x xs)
    (cond
      ((= (len xs) 0) false)
      ((= (first xs) x) true)
      (else (fr-in? x (rest xs))))))

(define fr-rep (fn (s from to) (replace (str s) from to)))

;; Blocks whose prose content find/replace rewrites (matches asText's set).
(define
  fr-has-text?
  (fn
    (b)
    (fr-in?
      (blk-type b)
      (list "text" "heading" "code" "quote" "callout" "image" "list" "table"))))

;; Per-type field rewrite. Each branch returns a new (copy-on-write) block.
(define
  fr-rewrite
  (fn
    (b from to)
    (let
      ((t (blk-type b)))
      (cond
        ((= t "image")
          (blk-set b "alt" (fr-rep (blk-get b "alt") from to)))
        ((= t "list")
          (let
            ((items (blk-get b "items")))
            (if
              (list? items)
              (blk-set b "items" (map (fn (it) (fr-rep it from to)) items))
              b)))
        ((= t "table")
          (let
            ((hs (blk-get b "headers")) (rs (blk-get b "rows")))
            (let
              ((b1 (if (list? hs) (blk-set b "headers" (map (fn (h) (fr-rep h from to)) hs)) b)))
              (if
                (list? rs)
                (blk-set
                  b1
                  "rows"
                  (map
                    (fn
                      (r)
                      (if (list? r) (map (fn (c) (fr-rep c from to)) r) r))
                    rs))
                b1))))
        (else (blk-set b "text" (fr-rep (blk-get b "text") from to)))))))

(define
  content/find-replace
  (fn
    (doc from to)
    (content/map-blocks doc fr-has-text? (fn (b) (fr-rewrite b from to)))))
