-module(endorsement_state). -export([new/0, fold/2, fold_fn/0, counters_for/2, total_for/2, kinds_for/2, endorsers_for/3, has_endorsed/4]). %% Endorsement counter projection. Folds Endorse activities into a %% per-target-Cid + per-kind counter so projections can serve %% "how many likes does this Note have" / "list everyone who shared %% this Announce" queries. %% %% Endorse envelope shape (per next/genesis/activity-types/endorse.sx): %% [{type, endorse}, %% {actor, ActorId}, %% {object, TargetCidBinary}, %% {kind, KindAtomOrBinary}, %% ...] %% %% State shape: %% [{TargetCid, [{Kind, [{ActorId, Count}, ...]}, ...]}, ...] %% %% Each ActorId can endorse the same target multiple times under %% the same kind (e.g. like → unlike → like → ...); the counter %% tracks how many *net* endorsement events fired. Step 11b ships %% the additive counter only; the unlike / un-endorse semantics %% (Undo{Endorse}) and reaction-toggling defer 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, endorse} -> fold_endorse(Activity, State); _ -> State end. fold_endorse(Activity, State) -> case {envelope:get_field(actor, Activity), envelope:get_field(object, Activity), envelope:get_field(kind, Activity)} of {{ok, Actor}, {ok, Cid}, {ok, Kind}} -> bump(Cid, Kind, Actor, State); _ -> State end. bump(Cid, Kind, Actor, State) -> KindMap = case find_keyed(Cid, State) of {ok, KM} -> KM; _ -> [] end, ActorMap = case find_keyed(Kind, KindMap) of {ok, AM} -> AM; _ -> [] end, Current = case find_keyed(Actor, ActorMap) of {ok, N} -> N; _ -> 0 end, ActorMap1 = set_keyed(Actor, Current + 1, ActorMap), KindMap1 = set_keyed(Kind, ActorMap1, KindMap), set_keyed(Cid, KindMap1, State). %% ── Read-side accessors ─────────────────────────────────────── %% counters_for(Cid, State) -> [{Kind, TotalCount}, ...] %% Sum per-kind across all endorsers. counters_for(Cid, State) -> case find_keyed(Cid, State) of {ok, KindMap} -> [{K, sum_counts(AM)} || {K, AM} <- KindMap]; _ -> [] end. total_for(Cid, State) -> lists:foldl(fun ({_, N}, Acc) -> N + Acc end, 0, counters_for(Cid, State)). kinds_for(Cid, State) -> [K || {K, _} <- counters_for(Cid, State)]. endorsers_for(Cid, Kind, State) -> case find_keyed(Cid, State) of {ok, KindMap} -> case find_keyed(Kind, KindMap) of {ok, AM} -> [A || {A, _} <- AM]; _ -> [] end; _ -> [] end. has_endorsed(Actor, Cid, Kind, State) -> case find_keyed(Cid, State) of {ok, KindMap} -> case find_keyed(Kind, KindMap) of {ok, AM} -> case find_keyed(Actor, AM) of {ok, N} -> N > 0; _ -> false end; _ -> false end; _ -> false end. %% ── Internal ────────────────────────────────────────────────── sum_counts([]) -> 0; sum_counts([{_, N} | Rest]) -> N + sum_counts(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)].