-module(announce_state). -export([new/0, fold/2, fold_fn/0, announcers_for/2, announce_count/2, announced_cids/1, has_announced/3]). %% Announce-fanout projection. Folds Announce activities into a %% per-target-Cid set of announcer ActorIds so projections can %% answer "who re-broadcast this activity" / "how many announces %% does this Note have" / "what activities has X announced". %% %% Announce envelope shape (per next/genesis/activity-types/announce.sx): %% [{type, announce}, %% {actor, AnnouncerActorId}, %% {object, TargetCidBinary}, %% ...] %% %% State shape: %% [{TargetCid, [Announcer1, Announcer2, ...]}, ...] %% %% Set semantics — the same actor announcing the same target twice %% is a no-op (already in the list). Undo{Announce} retraction %% defers to a follow-up. new() -> []. fold_fn() -> fun (Activity, State) -> fold(Activity, State) end. fold(Activity, State) -> case envelope:get_field(type, Activity) of {ok, announce} -> fold_announce(Activity, State); _ -> State end. fold_announce(Activity, State) -> case {envelope:get_field(actor, Activity), envelope:get_field(object, Activity)} of {{ok, Actor}, {ok, Cid}} -> add_announcer(Cid, Actor, State); _ -> State end. add_announcer(Cid, Actor, State) -> Current = case find_keyed(Cid, State) of {ok, Set} -> Set; _ -> [] end, case contains(Actor, Current) of true -> State; false -> set_keyed(Cid, Current ++ [Actor], State) end. %% ── Read-side accessors ─────────────────────────────────────── announcers_for(Cid, State) -> case find_keyed(Cid, State) of {ok, Set} -> Set; _ -> [] end. announce_count(Cid, State) -> length(announcers_for(Cid, State)). announced_cids(State) -> [C || {C, _} <- State]. has_announced(Actor, Cid, State) -> contains(Actor, announcers_for(Cid, State)). %% ── Internal ────────────────────────────────────────────────── contains(_, []) -> false; contains(X, [X | _]) -> true; contains(X, [_ | Rest]) -> contains(X, Rest). find_keyed(_, []) -> {error, not_found}; find_keyed(K, [{K, V} | _]) -> {ok, V}; find_keyed(K, [_ | Rest]) -> find_keyed(K, Rest). set_keyed(K, V, []) -> [{K, V}]; set_keyed(K, V, [{K, _} | Rest]) -> [{K, V} | Rest]; set_keyed(K, V, [P | Rest]) -> [P | set_keyed(K, V, Rest)].