-module(actor_state). -export([fold/2, fold_fn/0, new/0, lookup/2, has/2, actors/1, profile_type/1, profile_name/1, profile_field/2]). %% Actor-state projection fold — Erlang-fun stand-in for the %% genesis `actor-state.sx` projection body. Tracks per-actor %% profiles, key-history, and Move pointers per design §9.1-§9.4. %% %% State shape: %% [{ActorId, Profile}, ...] %% %% Profile = [{type, person|service|group}, %% {name, Bin}, %% {preferredUsername, Bin}, %% {summary, Bin}, %% {icon, Bin}, %% {public_keys, [Key]}, %% {moved_to, ActorIdOrUrl}, %% {created, N}] %% %% Bridge note: the SX-source eval bridge would replace this fold %% body once available (same gap as Step 5d-pure / Step 6c-schema-pure). %% define_registry.erl is the structural twin. %% %% lists:keyfind/keymember aren't in this substrate (Step 1a noted %% same gap), so local `find_keyed`/`has_keyed`/`set_keyed` helpers %% handle the keyed-list ops. new() -> []. actors(State) -> [Id || {Id, _Profile} <- State]. has(ActorId, State) -> has_keyed(ActorId, State). lookup(ActorId, State) -> case find_keyed(ActorId, State) of {ok, Profile} -> {ok, Profile}; {error, _} -> not_found end. %% ── Fold dispatch ─────────────────────────────────────────────── fold(Activity, State) -> case envelope:get_field(type, Activity) of {ok, create} -> fold_create(Activity, State); {ok, update} -> fold_update(Activity, State); {ok, move} -> fold_move(Activity, State); _ -> State end. fold_create(Activity, State) -> case envelope:get_field(object, Activity) of {ok, Obj} -> case envelope:get_field(type, Obj) of {ok, ObjType} -> case is_actor_type(ObjType) of true -> register_actor(Activity, Obj, ObjType, State); false -> State end; _ -> State end; _ -> State end. register_actor(Activity, Obj, ObjType, State) -> case envelope:get_field(actor, Activity) of {ok, ActorId} -> case has_keyed(ActorId, State) of true -> State; false -> Created = published_seq(Activity), Profile = build_profile(ObjType, Obj, Created), State ++ [{ActorId, Profile}] end; _ -> State end. fold_update(Activity, State) -> case envelope:get_field(actor, Activity) of {ok, ActorId} -> case find_keyed(ActorId, State) of {ok, Profile} -> case envelope:get_field(patch, Activity) of {ok, Patch} -> NewProfile = merge_patch(Profile, Patch), set_keyed(ActorId, NewProfile, State); _ -> State end; _ -> State end; _ -> State end. fold_move(Activity, State) -> case envelope:get_field(actor, Activity) of {ok, ActorId} -> case find_keyed(ActorId, State) of {ok, Profile} -> case envelope:get_field(moved_to, Activity) of {ok, Target} -> NewProfile = set_keyed(moved_to, Target, Profile), set_keyed(ActorId, NewProfile, State); _ -> State end; _ -> State end; _ -> State end. %% ── Profile assembly ──────────────────────────────────────────── build_profile(ObjType, Obj, Created) -> Base = [{type, ObjType}, {created, Created}], Fields = [name, preferredUsername, summary, icon, public_keys], Base ++ collect_fields(Fields, Obj). collect_fields([], _) -> []; collect_fields([F | Rest], Obj) -> case envelope:get_field(F, Obj) of {ok, V} -> [{F, V} | collect_fields(Rest, Obj)]; _ -> collect_fields(Rest, Obj) end. merge_patch(Profile, []) -> Profile; merge_patch(Profile, [{K, V} | Rest]) -> merge_patch(set_keyed(K, V, Profile), Rest); merge_patch(Profile, _) -> Profile. published_seq(Activity) -> case envelope:get_field(published, Activity) of {ok, P} -> P; _ -> 0 end. is_actor_type(person) -> true; is_actor_type(service) -> true; is_actor_type(group) -> true; is_actor_type(_) -> false. %% ── Profile accessors ─────────────────────────────────────────── profile_type(Profile) -> case find_keyed(type, Profile) of {ok, T} -> T; _ -> nil end. profile_name(Profile) -> case find_keyed(name, Profile) of {ok, N} -> N; _ -> nil end. profile_field(F, Profile) -> case find_keyed(F, Profile) of {ok, V} -> {ok, V}; _ -> not_found end. %% ── Projection integration ────────────────────────────────────── fold_fn() -> fun (Activity, State) -> fold(Activity, State) end. %% ── Internal ──────────────────────────────────────────────────── has_keyed(_, []) -> false; has_keyed(K, [{K, _} | _]) -> true; has_keyed(K, [_ | Rest]) -> has_keyed(K, 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)].