; feed/thread — conversation threading. A reply carries :reply-to <parent-object>
; (normalize preserves it). A thread is the transitive closure over :reply-to from
; a root object: root + replies + replies-to-replies, gathered chronologically.
;
; Requires: lib/feed/normalize.sx, lib/feed/stream.sx, lib/feed/fanout.sx
; (feed/-elem?, feed/-distinct).

; direct replies to an object
(define
  feed/replies
  (fn
    (stream object)
    (feed/filter stream (fn (a) (equal? (get a :reply-to) object)))))

(define
  feed/reply-count
  (fn (stream object) (feed/count (feed/replies stream object))))

; iterate f from x until the result stops growing (set-closure fixpoint)
(define
  feed/-fixpoint
  (fn
    (f x)
    (let
      ((nx (f x)))
      (if (= (len nx) (len x)) x (feed/-fixpoint f nx)))))

; the set of object-ids in the thread rooted at `root`
(define
  feed/thread-objects
  (fn
    (stream root)
    (let
      ((all (feed/items stream)))
      (feed/-fixpoint
        (fn
          (acc)
          (feed/-distinct
            (append
              acc
              (map
                (fn (a) (get a :object))
                (filter (fn (a) (feed/-elem? (get a :reply-to) acc)) all)))))
        (list root)))))

; the full thread as a chronological stream (root + all descendants)
(define
  feed/thread
  (fn
    (stream root)
    (let
      ((objs (feed/thread-objects stream root)))
      (feed/sort-by-at
        (feed/filter stream (fn (a) (feed/-elem? (get a :object) objs)))))))

; how many activities are in the thread (root counts as 1)
(define
  feed/thread-size
  (fn (stream root) (feed/count (feed/thread stream root))))
