;; lib/events/federation.sx — cross-instance calendar federation (trust-gated).
;;
;; A peer is another events instance that publishes a schedule (an events
;; store). We merge a peer's agenda into ours ONLY if we trust it — trust is a
;; set of peer ids, re-checked on every merge, so revoking a peer takes effect
;; immediately. Merged occurrences carry :origin provenance (:local for ours, or
;; the peer id) so a consumer always knows where a slot came from.
;;
;; This is the trust-gated stub: peers publish plain schedules and we fold the
;; trusted ones into a single sorted agenda. Real transport (fed-sx / signed
;; fetch) slots in behind `ev/peer-agenda` without changing the merge.
;;
;; Federated FREE/BUSY follows the iCal model: a peer publishes BUSY intervals
;; for an actor (not event details — privacy-preserving), and we union local +
;; trusted-peer busy to answer "is this actor free?" across instances.

(define ev/peer (fn (id store) {:id id :busy (list) :store store}))

;; A peer that also publishes free/busy: `busy` is a list of
;; (actor ((start end) ...)) pairs.
(define ev/peer-with-busy (fn (id store busy) {:id id :busy busy :store store}))

(define ev/peer-id (fn (p) (get p :id)))
(define ev/peer-store (fn (p) (get p :store)))
(define ev/peer-busy-table (fn (p) (get p :busy)))

(define
  ev-fed-member?
  (fn
    (x xs)
    (cond
      ((empty? xs) false)
      ((= x (first xs)) true)
      (else (ev-fed-member? x (rest xs))))))

;; Do we trust this peer id? (trust is a list of trusted peer ids.)
(define ev/trusts? (fn (trust peer-id) (ev-fed-member? peer-id trust)))

;; The trusted subset of a peer list.
(define
  ev/trusted-peers
  (fn
    (peers trust)
    (filter (fn (p) (ev/trusts? trust (ev/peer-id p))) peers)))

;; Tag occurrences with provenance.
(define ev-tag-origin (fn (occs origin) (map (fn (o) {:id (get o :id) :start (get o :start) :end (get o :end) :origin origin}) occs)))

;; A peer's agenda over [ws, we), tagged with the peer's id as :origin.
(define
  ev/peer-agenda
  (fn
    (peer ws we)
    (ev-tag-origin (ev/agenda (ev/peer-store peer) ws we) (ev/peer-id peer))))

;; ---- merge (sorted by start, then origin for ties) ----

(define
  ev-fed-before?
  (fn
    (a c)
    (cond
      ((< (get a :start) (get c :start)) true)
      ((> (get a :start) (get c :start)) false)
      (else (< (str (get a :origin)) (str (get c :origin)))))))

(define
  ev-fed-insert
  (fn
    (x sorted)
    (cond
      ((empty? sorted) (list x))
      ((ev-fed-before? x (first sorted)) (cons x sorted))
      (else (cons (first sorted) (ev-fed-insert x (rest sorted)))))))

(define
  ev-fed-sort
  (fn (xs) (reduce (fn (acc x) (ev-fed-insert x acc)) (list) xs)))

;; Local agenda (origin :local) merged with every TRUSTED peer's agenda,
;; sorted by start. Untrusted peers contribute nothing.
(define
  ev/federated-agenda
  (fn
    (local-store peers trust ws we)
    (let
      ((acc (list)))
      (begin
        (for-each
          (fn (o) (append! acc o))
          (ev-tag-origin (ev/agenda local-store ws we) :local))
        (for-each
          (fn
            (peer)
            (when
              (ev/trusts? trust (ev/peer-id peer))
              (for-each
                (fn (o) (append! acc o))
                (ev/peer-agenda peer ws we))))
          peers)
        (ev-fed-sort acc)))))

;; Filter a federated agenda to occurrences from one origin.
(define
  ev/from-origin
  (fn
    (agenda origin)
    (filter (fn (o) (= (get o :origin) origin)) agenda)))

;; ---- federated free/busy ----

;; A peer's published busy intervals for `actor` ((start end) ...), or empty.
(define
  ev/peer-busy
  (fn
    (peer actor)
    (let
      ((row (ev-fed-assoc actor (ev/peer-busy-table peer))))
      (if (nil? row) (list) (first (rest row))))))

(define
  ev-fed-assoc
  (fn
    (k pairs)
    (cond
      ((empty? pairs) nil)
      ((= (first (first pairs)) k) (first pairs))
      (else (ev-fed-assoc k (rest pairs))))))

;; All busy intervals for `actor` across the LOCAL availability db plus every
;; TRUSTED peer's published free/busy, merged and sorted by start.
;; `local-db` is an availability db (see availability.sx ev-build-avail).
(define
  ev/federated-busy
  (fn
    (local-db peers trust actor)
    (let
      ((acc (list)))
      (begin
        (for-each (fn (iv) (append! acc iv)) (ev-busy local-db actor))
        (for-each
          (fn
            (peer)
            (when
              (ev/trusts? trust (ev/peer-id peer))
              (for-each
                (fn (iv) (append! acc iv))
                (ev/peer-busy peer actor))))
          peers)
        (ev-sort-lists acc)))))

;; Half-open overlap of interval (s e) with window [qs, qe).
(define
  ev-fed-overlaps?
  (fn (iv qs qe) (and (< (first iv) qe) (< qs (first (rest iv))))))

;; Is `actor` free across [qs, qe) considering local + trusted-peer busy?
(define
  ev/federated-free?
  (fn
    (local-db peers trust actor qs qe)
    (not
      (some
        (fn (iv) (ev-fed-overlaps? iv qs qe))
        (ev/federated-busy local-db peers trust actor)))))

;; ---- injected transport (real fed-sx / signed fetch) ----
;; The in-process merge above expands a peer's local :store directly. In
;; production a peer's agenda arrives over a transport. `fetch` abstracts that:
;;   (fetch peer-id ws we) -> {:status :ok :occurrences (...)} | {:status :error :reason ...}
;; The same merge works for any transport; an unreachable peer (:error) is
;; skipped (graceful degradation), never breaking the agenda.

(define
  ev-find-peer
  (fn
    (peers pid)
    (cond
      ((empty? peers) nil)
      ((= (ev/peer-id (first peers)) pid) (first peers))
      (else (ev-find-peer (rest peers) pid)))))

;; In-process transport adapter: resolves a peer-id against a peer list and
;; expands its :store. Lets the in-process model run through the same `fetch`
;; interface a remote transport implements.
(define
  ev/peer-fetch
  (fn
    (peers)
    (fn
      (pid ws we)
      (let
        ((p (ev-find-peer peers pid)))
        (if
          (nil? p)
          {:status :error :reason :unknown-peer}
          {:status :ok :occurrences (ev/agenda (ev/peer-store p) ws we)})))))

;; Local agenda (:local) merged with each trusted peer's agenda fetched via the
;; injected `fetch` transport, sorted by start, tagged with :origin. Peers that
;; fail to fetch contribute nothing.
(define
  ev/federated-agenda-via
  (fn
    (local-store trusted-ids ws we fetch)
    (let
      ((acc (list)))
      (begin
        (for-each
          (fn (o) (append! acc o))
          (ev-tag-origin (ev/agenda local-store ws we) :local))
        (for-each
          (fn
            (pid)
            (let
              ((res (fetch pid ws we)))
              (when
                (= (get res :status) :ok)
                (for-each
                  (fn (o) (append! acc o))
                  (ev-tag-origin (get res :occurrences) pid)))))
          trusted-ids)
        (ev-fed-sort acc)))))

;; Reachability report: ((peer-id :ok|:error) ...) for the trusted peers.
(define
  ev/federation-status
  (fn
    (trusted-ids ws we fetch)
    (map
      (fn (pid) (list pid (get (fetch pid ws we) :status)))
      trusted-ids)))
