; Phase 4 — visibility (ACL) + federation, and the end-to-end timeline.
; (feed-test name got expected)

; ---------- ACL visibility ----------
; pub: public. sec: bob, allows carol. dm: frank, allows dave.

(define
  C
  (feed/stream
    (list
      (feed/normalize {:actor "alice" :object "pub" :at 10})
      (feed/normalize {:actor "bob" :object "sec" :visible-to (list "carol") :at 20})
      (feed/normalize {:actor "frank" :object "dm" :visible-to (list "dave") :at 30}))))

(feed-test
  "public visible to anyone"
  (feed/count (feed/visible C "zoe" feed/permit-acl?))
  1)
(feed-test
  "carol sees allowlisted + public"
  (feed/count (feed/visible C "carol" feed/permit-acl?))
  2)
(feed-test
  "dave sees dm + public"
  (feed/count (feed/visible C "dave" feed/permit-acl?))
  2)
(feed-test
  "author always sees own private"
  (feed/count (feed/visible C "frank" feed/permit-acl?))
  2)
(feed-test
  "permit-public? lets all through"
  (feed/count (feed/visible C "zoe" feed/permit-public?))
  3)
(feed-test
  "visible objects for dave"
  (map
    (fn (a) (get a :object))
    (feed/items (feed/visible C "dave" feed/permit-acl?)))
  (list "pub" "dm"))

; per-viewer: same stream, different timelines
(feed-test
  "zoe timeline differs from carol"
  (not
    (=
      (feed/count (feed/visible C "zoe" feed/permit-acl?))
      (feed/count (feed/visible C "carol" feed/permit-acl?))))
  true)

; ---------- federation: merge / ingest ----------

(define
  L
  (feed/stream
    (list
      (feed/activity "alice" "post" "p1" 10 (list))
      (feed/activity "alice" "post" "p2" 20 (list)))))
(define
  P
  (feed/stream
    (list
      (feed/activity "alice" "post" "p2" 20 (list))
      (feed/activity "peer" "post" "p9" 25 (list)))))

(feed-test "merge concatenates" (feed/count (feed/merge L P)) 4)
(feed-test
  "ingest dedupes overlap"
  (feed/count (feed/ingest L P))
  3)

(feed-test
  "inbound normalizes + ingests"
  (feed/count (feed/inbound L (list {:actor "peer" :object "p9" :at 25} {:actor "alice" :object "p1" :at 10})))
  3)

; backfill via injected fetch-fn
(define peer-history (fn (peer-id) (list {:actor peer-id :object "h1" :at 1} {:actor peer-id :object "h2" :at 2})))
(feed-test
  "backfill merges peer history"
  (feed/count (feed/backfill L peer-history "remote"))
  4)
(feed-test
  "backfill objects present"
  (map
    (fn (a) (get a :object))
    (feed/items
      (feed/by-actor (feed/backfill L peer-history "remote") "remote")))
  (list "h1" "h2"))

; ---------- federation: outbound partition ----------

; bob (local), alice@remote + carol@remote (remote) follow star
(define
  Gf
  (feed/follow-graph
    (list
      (list "bob" "star")
      (list "alice@remote" "star")
      (list "carol@remote" "star"))))
(define
  Sf
  (feed/stream (list (feed/activity "star" "post" "s1" 1 (list)))))
(define
  remote?
  (fn (id) (feed/-elem? id (list "alice@remote" "carol@remote"))))
(define parts (feed/federate Sf Gf remote?))

(feed-test "local deliveries" (feed/count (get parts :local)) 1)
(feed-test "remote deliveries" (feed/count (get parts :remote)) 2)
(feed-test
  "local recipient is bob"
  (feed/recipients (get parts :local))
  (list "bob"))

; deliver: send-fn receives each remote event, local inbox returned
(define sent (list))
(define send-fn (fn (to act) (set! sent (append sent (list to)))))
(define local-inbox (feed/deliver Sf Gf remote? send-fn))
(feed-test "deliver returns local inbox" (feed/count local-inbox) 1)
(feed-test "deliver sent to both remotes" (len sent) 2)
(feed-test "deliver remote targets" sent (list "alice@remote" "carol@remote"))

; ---------- end-to-end: federated, ACL-filtered, ranked timeline ----------

(define
  base
  (feed/stream
    (list
      (feed/normalize {:actor "alice" :object "a1" :at 100})
      (feed/normalize {:actor "bob" :object "b1" :visible-to (list "carol") :at 90})
      (feed/normalize {:actor "eve" :object "e1" :visible-to (list "dave") :at 80}))))
(define federated (feed/inbound base (list {:actor "peer" :object "x1" :at 110})))
(define rec (feed/recency 120 10))
(define
  carol-tl
  (feed/timeline federated "carol" feed/permit-acl? rec 3))

; eve's :visible-to excludes carol -> filtered out; peer/alice public, bob allows carol
(feed-test "carol federated timeline count" (feed/count carol-tl) 3)
(feed-test
  "carol timeline order (recency)"
  (map (fn (a) (get a :object)) (feed/items carol-tl))
  (list "x1" "a1" "b1"))
(feed-test
  "eve dm excluded from carol"
  (feed/-elem? "e1" (map (fn (a) (get a :object)) (feed/items carol-tl)))
  false)
(feed-test
  "dave sees eve dm not bob"
  (map
    (fn (a) (get a :object))
    (feed/items
      (feed/timeline federated "dave" feed/permit-acl? rec 5)))
  (list "x1" "a1" "e1"))
