-module(log_server).
-behaviour(gen_server).
-export([start_link/2, start_link/3,
         append/2, tip/1, entries/1, replay/3,
         segments/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

%% Step 3c.b — gen_server in front of `log` that owns a single
%% per-actor disk-backed log state and serialises concurrent
%% appenders through `gen_server:call`.
%%
%% Architecture: the pure `log` module from Step 3c.a remains the
%% canonical substrate (open_disk, append, tip, replay, entries,
%% segments). This wrapper owns one log state per process; every
%% public op (append/tip/entries/replay/segments) routes through
%% gen_server:call so that the on-disk segment writer sees one
%% append at a time, regardless of how many writer processes are
%% pushing concurrently.
%%
%% Port notes carried from Step 5b's registry_server:
%%   * `gen_server:start_link/2` returns the raw Pid, not `{ok,Pid}`.
%%   * Spawned processes don't survive across separate
%%     `erlang-eval-ast` invocations — every concurrency test has
%%     to start the server, spin writers, join them, and assert all
%%     within one eval expression.
%%
%% API takes the server Pid (not a registered name) so multiple
%% per-actor servers can coexist without colliding on the registry.

%% --- public API ---

start_link(ActorId, BasePath) ->
    gen_server:start_link(log_server, [ActorId, BasePath, []]).

start_link(ActorId, BasePath, Opts) ->
    gen_server:start_link(log_server, [ActorId, BasePath, Opts]).

append(Pid, Activity) ->
    gen_server:call(Pid, {append, Activity}).

tip(Pid) ->
    gen_server:call(Pid, tip).

entries(Pid) ->
    gen_server:call(Pid, entries).

replay(Pid, InitAcc, Fun) ->
    %% The fold runs server-side so the state stays consistent
    %% with concurrent writers; the caller's Fun is closed over
    %% the message and shipped opaque through gen_server:call.
    gen_server:call(Pid, {replay, InitAcc, Fun}).

segments(Pid) ->
    gen_server:call(Pid, segments).

stop(Pid) ->
    gen_server:call(Pid, '$gen_stop').

%% --- gen_server callbacks ---

init([ActorId, BasePath, Opts]) ->
    case Opts of
        [] ->
            {ok, LogState} = log:open_disk(ActorId, BasePath),
            {ok, LogState};
        _ ->
            {ok, LogState} = log:open_disk(ActorId, BasePath, Opts),
            {ok, LogState}
    end.

handle_call({append, Activity}, _From, State) ->
    {ok, NewState, Seq} = log:append(State, Activity),
    {reply, {ok, Seq}, NewState};
handle_call(tip, _From, State) ->
    {reply, log:tip(State), State};
handle_call(entries, _From, State) ->
    {reply, log:entries(State), State};
handle_call({replay, InitAcc, Fun}, _From, State) ->
    {reply, log:replay(State, InitAcc, Fun), State};
handle_call(segments, _From, State) ->
    {reply, log:segments(State), State}.

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

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