-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,
         key_history/1, active_keys_at/2, find_key_by_id/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} ->
                            Published = published_seq(Activity),
                            NewProfile = apply_patch(Profile, Patch, Published),
                            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.

%% apply_patch/3 — same as merge_patch but special-cases two
%% key-rotation patch entries per design §9.6:
%%   {add_publicKey, KeyProplist}   — append a new key to :public_keys,
%%                                    defaulting :created to Published.
%%   {supersede, OldKeyId}          — mark the key with :id =:= OldKeyId
%%                                    as :superseded_at = Published.
%% Other patch entries fall through to last-write-wins per key.

apply_patch(Profile, [], _Published) -> Profile;
apply_patch(Profile, [{add_publicKey, NewKey} | Rest], Published) ->
    Augmented = ensure_created(NewKey, Published),
    Current = current_public_keys(Profile),
    NewKeys = Current ++ [Augmented],
    apply_patch(set_keyed(public_keys, NewKeys, Profile), Rest, Published);
apply_patch(Profile, [{supersede, OldKeyId} | Rest], Published) ->
    Current = current_public_keys(Profile),
    NewKeys = mark_superseded(OldKeyId, Published, Current),
    apply_patch(set_keyed(public_keys, NewKeys, Profile), Rest, Published);
apply_patch(Profile, [{K, V} | Rest], Published) ->
    apply_patch(set_keyed(K, V, Profile), Rest, Published);
apply_patch(Profile, _, _) -> Profile.

current_public_keys(Profile) ->
    case find_keyed(public_keys, Profile) of
        {ok, Keys} -> Keys;
        _          -> []
    end.

ensure_created(Key, Published) ->
    case find_keyed(created, Key) of
        {ok, _} -> Key;
        _       -> set_keyed(created, Published, Key)
    end.

mark_superseded(_, _, []) -> [];
mark_superseded(OldId, At, [Key | Rest]) ->
    case find_keyed(id, Key) of
        {ok, OldId} ->
            case find_keyed(superseded_at, Key) of
                {ok, _} -> [Key | mark_superseded(OldId, At, Rest)];
                _       -> [set_keyed(superseded_at, At, Key) | mark_superseded(OldId, At, Rest)]
            end;
        _ -> [Key | mark_superseded(OldId, At, Rest)]
    end.

%% Key-history view — full :public_keys list including superseded
%% entries (per §9.6: history is preserved so historical activities
%% verify against keys that were active at their :published time).

key_history(Profile) ->
    current_public_keys(Profile).

%% active_keys_at/2 — the subset of :public_keys active at Now,
%% mirroring envelope's is_active_at semantics (local copy: envelope
%% keeps the predicate private).

active_keys_at(Profile, Now) ->
    [K || K <- current_public_keys(Profile),
          key_active_at(K, Now)].

find_key_by_id(KeyId, Profile) ->
    find_key_by_id_in(KeyId, current_public_keys(Profile)).

find_key_by_id_in(_, []) -> not_found;
find_key_by_id_in(WantId, [K | Rest]) ->
    case find_keyed(id, K) of
        {ok, WantId} -> {ok, K};
        _            -> find_key_by_id_in(WantId, Rest)
    end.

key_active_at(Key, Now) ->
    case find_keyed(created, Key) of
        {ok, Created} when Now >= Created ->
            case find_keyed(superseded_at, Key) of
                {ok, SupAt} -> Now < SupAt;
                _           -> true
            end;
        _ -> false
    end.

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)].
