-module(registry).
-behaviour(gen_server).
-export([new/0, kinds/0, register/4, lookup/3, list/2]).
-export([start_link/0, register/3, lookup/2, list/1, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

%% Pure-functional registry for the seven bootstrap kinds.
%%
%% State is a property list keyed by kind atom; each kind's value
%% is itself a property list of {Name, Entry} pairs. Entry is
%% opaque — typically a proplist with :cid, :schema, :semantics,
%% :supersedes fields, but the registry doesn't enforce that here.
%%
%% A gen_server wrapper (Step 5b) will own the global registry
%% process; the pure functions in this module remain the canonical
%% API and are usable for tests and for offline projection-replay.
%%
%% Return shapes:
%%   new/0            -> State
%%   kinds/0          -> [Atom, ...]
%%   register/4       -> {ok, NewState} | {error, unknown_kind}
%%   lookup/3         -> {ok, Entry} | not_found | {error, unknown_kind}
%%   list/2           -> [{Name, Entry}, ...] | {error, unknown_kind}

new() -> [].

kinds() ->
    [activity_types, object_types, projections,
     validators, codecs, sig_suites, audience].

register(Kind, Name, Entry, State) ->
    case is_valid_kind(Kind) of
        false -> {error, unknown_kind};
        true ->
            Entries = kind_entries(Kind, State),
            Updated = put_pair(Name, Entry, Entries),
            {ok, set_kind_entries(Kind, Updated, State)}
    end.

lookup(Kind, Name, State) ->
    case is_valid_kind(Kind) of
        false -> {error, unknown_kind};
        true ->
            find_pair(Name, kind_entries(Kind, State))
    end.

list(Kind, State) ->
    case is_valid_kind(Kind) of
        false -> {error, unknown_kind};
        true  -> kind_entries(Kind, State)
    end.

%% ── Internal ────────────────────────────────────────────────────

is_valid_kind(K) -> lists:member(K, kinds()).

kind_entries(Kind, State) ->
    case find_pair(Kind, State) of
        not_found -> [];
        {ok, V}   -> V
    end.

set_kind_entries(Kind, Entries, State) ->
    put_pair(Kind, Entries, State).

put_pair(K, V, []) -> [{K, V}];
put_pair(K, V, [{K, _} | Rest]) -> [{K, V} | Rest];
put_pair(K, V, [P | Rest]) -> [P | put_pair(K, V, Rest)].

find_pair(_, []) -> not_found;
find_pair(K, [{K, V} | _]) -> {ok, V};
find_pair(K, [_ | Rest]) -> find_pair(K, Rest).

%% ── Step 5b: gen_server wrapper ─────────────────────────────────
%%
%% The named process owns the registry state; concurrent readers
%% and writers serialize through gen_server:call. The pure /3 and
%% /4 functions remain available for offline projection-replay and
%% for tests that don't need a process at all.
%%
%% Port notes: gen_server:start_link returns the raw Pid (not
%% `{ok, Pid}` as in OTP). `?MODULE` macro is unsupported here, so
%% the registered name is the literal `registry` atom in every call.

start_link() ->
    Pid = gen_server:start_link(registry, []),
    erlang:register(registry, Pid),
    Pid.

stop() ->
    R = gen_server:call(registry, '$gen_stop'),
    erlang:unregister(registry),
    R.

register(Kind, Name, Entry) ->
    gen_server:call(registry, {register, Kind, Name, Entry}).

lookup(Kind, Name) ->
    gen_server:call(registry, {lookup, Kind, Name}).

list(Kind) ->
    gen_server:call(registry, {list, Kind}).

%% gen_server callbacks

init(_) -> {ok, new()}.

handle_call({register, Kind, Name, Entry}, _From, State) ->
    case register(Kind, Name, Entry, State) of
        {ok, NewState} -> {reply, ok, NewState};
        {error, R}     -> {reply, {error, R}, State}
    end;
handle_call({lookup, Kind, Name}, _From, State) ->
    {reply, lookup(Kind, Name, State), State};
handle_call({list, Kind}, _From, State) ->
    {reply, list(Kind, State), State}.

handle_cast(_, S) -> {noreply, S}.

handle_info(_, S) -> {noreply, S}.
