src/lightspeed@presence.erl

-module(lightspeed@presence).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/presence.gleam").
-export([new/0, join/4, leave/4, list_topic/2, get/3, key_count/2, ref_count/2, topic_labels/1, diff_topic/1, joins/1, leaves/1, join_count/1, leave_count/1, diff_label/1, meta_ref/1, meta_online_at_ms/1]).
-export_type([meta/0, entry/0, diff/0, topic_entries/0, tracker/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(" Deterministic presence tracker and diff model.\n").

-type meta() :: {meta, binary(), integer()}.

-type entry() :: {entry, binary(), list(meta())}.

-type diff() :: {diff, binary(), list(entry()), list(entry())}.

-type topic_entries() :: {topic_entries, binary(), list(entry())}.

-opaque tracker() :: {tracker, list(topic_entries())}.

-file("src/lightspeed/presence.gleam", 32).
?DOC(" New tracker.\n").
-spec new() -> tracker().
new() ->
    {tracker, []}.

-file("src/lightspeed/presence.gleam", 296).
-spec has_ref(list(meta()), binary()) -> boolean().
has_ref(Metas, Ref) ->
    case Metas of
        [] ->
            false;

        [Meta | Rest] ->
            case erlang:element(2, Meta) =:= Ref of
                true ->
                    true;

                false ->
                    has_ref(Rest, Ref)
            end
    end.

-file("src/lightspeed/presence.gleam", 190).
-spec join_entry(list(entry()), binary(), meta()) -> {list(entry()),
    gleam@option:option(meta())}.
join_entry(Entries_rev, Key, Meta) ->
    case Entries_rev of
        [] ->
            {[{entry, Key, [Meta]}], {some, Meta}};

        [Entry | Rest] ->
            case erlang:element(2, Entry) =:= Key of
                true ->
                    case has_ref(
                        erlang:element(3, Entry),
                        erlang:element(2, Meta)
                    ) of
                        true ->
                            {[Entry | Rest], none};

                        false ->
                            Updated = {entry,
                                erlang:element(2, Entry),
                                [Meta | erlang:element(3, Entry)]},
                            {[Updated | Rest], {some, Meta}}
                    end;

                false ->
                    {Updated_rest, Joined} = join_entry(Rest, Key, Meta),
                    {[Entry | Updated_rest], Joined}
            end
    end.

-file("src/lightspeed/presence.gleam", 154).
-spec join_topic(list(topic_entries()), binary(), binary(), meta()) -> {list(topic_entries()),
    gleam@option:option(meta())}.
join_topic(Topics_rev, Topic, Key, Meta) ->
    case Topics_rev of
        [] ->
            {[{topic_entries, Topic, [{entry, Key, [Meta]}]}], {some, Meta}};

        [Topic_entries | Rest] ->
            case erlang:element(2, Topic_entries) =:= Topic of
                true ->
                    {Entries_rev, Joined} = join_entry(
                        erlang:element(3, Topic_entries),
                        Key,
                        Meta
                    ),
                    {[{topic_entries,
                                erlang:element(2, Topic_entries),
                                Entries_rev} |
                            Rest],
                        Joined};

                false ->
                    {Updated_rest, Joined@1} = join_topic(
                        Rest,
                        Topic,
                        Key,
                        Meta
                    ),
                    {[Topic_entries | Updated_rest], Joined@1}
            end
    end.

-file("src/lightspeed/presence.gleam", 37).
?DOC(" Track one join presence and return resulting diff.\n").
-spec join(tracker(), binary(), binary(), meta()) -> {tracker(), diff()}.
join(Tracker, Topic, Key, Meta) ->
    {Topics_rev, Joined_meta} = join_topic(
        erlang:element(2, Tracker),
        Topic,
        Key,
        Meta
    ),
    Joins = case Joined_meta of
        {some, Joined} ->
            [{entry, Key, [Joined]}];

        none ->
            []
    end,
    {{tracker, Topics_rev}, {diff, Topic, Joins, []}}.

-file("src/lightspeed/presence.gleam", 281).
-spec remove_meta(list(meta()), binary()) -> {list(meta()),
    gleam@option:option(meta())}.
remove_meta(Metas, Ref) ->
    case Metas of
        [] ->
            {[], none};

        [Meta | Rest] ->
            case erlang:element(2, Meta) =:= Ref of
                true ->
                    {Rest, {some, Meta}};

                false ->
                    {Updated_rest, Removed} = remove_meta(Rest, Ref),
                    {[Meta | Updated_rest], Removed}
            end
    end.

-file("src/lightspeed/presence.gleam", 255).
-spec leave_entry(list(entry()), binary(), binary()) -> {list(entry()),
    gleam@option:option(meta())}.
leave_entry(Entries_rev, Key, Ref) ->
    case Entries_rev of
        [] ->
            {[], none};

        [Entry | Rest] ->
            case erlang:element(2, Entry) =:= Key of
                true ->
                    {Metas, Removed} = remove_meta(
                        erlang:element(3, Entry),
                        Ref
                    ),
                    case Metas of
                        [] ->
                            {Rest, Removed};

                        _ ->
                            {[{entry, erlang:element(2, Entry), Metas} | Rest],
                                Removed}
                    end;

                false ->
                    {Updated_rest, Removed@1} = leave_entry(Rest, Key, Ref),
                    {[Entry | Updated_rest], Removed@1}
            end
    end.

-file("src/lightspeed/presence.gleam", 218).
-spec leave_topic(list(topic_entries()), binary(), binary(), binary()) -> {list(topic_entries()),
    gleam@option:option(meta())}.
leave_topic(Topics_rev, Topic, Key, Ref) ->
    case Topics_rev of
        [] ->
            {[], none};

        [Topic_entries | Rest] ->
            case erlang:element(2, Topic_entries) =:= Topic of
                true ->
                    {Entries_rev, Removed_meta} = leave_entry(
                        erlang:element(3, Topic_entries),
                        Key,
                        Ref
                    ),
                    case Entries_rev of
                        [] ->
                            {Rest, Removed_meta};

                        _ ->
                            {[{topic_entries,
                                        erlang:element(2, Topic_entries),
                                        Entries_rev} |
                                    Rest],
                                Removed_meta}
                    end;

                false ->
                    {Updated_rest, Removed_meta@1} = leave_topic(
                        Rest,
                        Topic,
                        Key,
                        Ref
                    ),
                    {[Topic_entries | Updated_rest], Removed_meta@1}
            end
    end.

-file("src/lightspeed/presence.gleam", 57).
?DOC(" Track one leave presence and return resulting diff.\n").
-spec leave(tracker(), binary(), binary(), binary()) -> {tracker(), diff()}.
leave(Tracker, Topic, Key, Ref) ->
    {Topics_rev, Removed_meta} = leave_topic(
        erlang:element(2, Tracker),
        Topic,
        Key,
        Ref
    ),
    Leaves = case Removed_meta of
        {some, Left} ->
            [{entry, Key, [Left]}];

        none ->
            []
    end,
    {{tracker, Topics_rev}, {diff, Topic, [], Leaves}}.

-file("src/lightspeed/presence.gleam", 307).
-spec find_topic_entries(list(topic_entries()), binary()) -> list(entry()).
find_topic_entries(Topics_rev, Topic) ->
    case Topics_rev of
        [] ->
            [];

        [Topic_entries | Rest] ->
            case erlang:element(2, Topic_entries) =:= Topic of
                true ->
                    erlang:element(3, Topic_entries);

                false ->
                    find_topic_entries(Rest, Topic)
            end
    end.

-file("src/lightspeed/presence.gleam", 77).
?DOC(" List full presence state for one topic.\n").
-spec list_topic(tracker(), binary()) -> list(entry()).
list_topic(Tracker, Topic) ->
    find_topic_entries(erlang:element(2, Tracker), Topic).

-file("src/lightspeed/presence.gleam", 321).
-spec find_entry(list(entry()), binary()) -> gleam@option:option(entry()).
find_entry(Entries, Key) ->
    case Entries of
        [] ->
            none;

        [Entry | Rest] ->
            case erlang:element(2, Entry) =:= Key of
                true ->
                    {some, Entry};

                false ->
                    find_entry(Rest, Key)
            end
    end.

-file("src/lightspeed/presence.gleam", 82).
?DOC(" Get one key presence entry.\n").
-spec get(tracker(), binary(), binary()) -> gleam@option:option(entry()).
get(Tracker, Topic, Key) ->
    _pipe = list_topic(Tracker, Topic),
    find_entry(_pipe, Key).

-file("src/lightspeed/presence.gleam", 88).
?DOC(" Number of tracked keys in one topic.\n").
-spec key_count(tracker(), binary()) -> integer().
key_count(Tracker, Topic) ->
    erlang:length(list_topic(Tracker, Topic)).

-file("src/lightspeed/presence.gleam", 332).
-spec count_refs(list(entry()), integer()) -> integer().
count_refs(Entries, Total) ->
    case Entries of
        [] ->
            Total;

        [Entry | Rest] ->
            count_refs(Rest, Total + erlang:length(erlang:element(3, Entry)))
    end.

-file("src/lightspeed/presence.gleam", 93).
?DOC(" Number of tracked refs in one topic.\n").
-spec ref_count(tracker(), binary()) -> integer().
ref_count(Tracker, Topic) ->
    _pipe = list_topic(Tracker, Topic),
    count_refs(_pipe, 0).

-file("src/lightspeed/presence.gleam", 99).
?DOC(" Stable topic labels for logs and tests.\n").
-spec topic_labels(tracker()) -> list(binary()).
topic_labels(Tracker) ->
    _pipe = erlang:element(2, Tracker),
    gleam@list:map(
        _pipe,
        fun(Topic_entries) ->
            <<<<<<<<(erlang:element(2, Topic_entries))/binary, ":keys="/utf8>>/binary,
                        (erlang:integer_to_binary(
                            erlang:length(erlang:element(3, Topic_entries))
                        ))/binary>>/binary,
                    ":refs="/utf8>>/binary,
                (erlang:integer_to_binary(
                    count_refs(erlang:element(3, Topic_entries), 0)
                ))/binary>>
        end
    ).

-file("src/lightspeed/presence.gleam", 111).
?DOC(" Diff topic.\n").
-spec diff_topic(diff()) -> binary().
diff_topic(Diff) ->
    erlang:element(2, Diff).

-file("src/lightspeed/presence.gleam", 116).
?DOC(" Joined entries.\n").
-spec joins(diff()) -> list(entry()).
joins(Diff) ->
    erlang:element(3, Diff).

-file("src/lightspeed/presence.gleam", 121).
?DOC(" Left entries.\n").
-spec leaves(diff()) -> list(entry()).
leaves(Diff) ->
    erlang:element(4, Diff).

-file("src/lightspeed/presence.gleam", 126).
?DOC(" Number of join entries in one diff.\n").
-spec join_count(diff()) -> integer().
join_count(Diff) ->
    erlang:length(erlang:element(3, Diff)).

-file("src/lightspeed/presence.gleam", 131).
?DOC(" Number of leave entries in one diff.\n").
-spec leave_count(diff()) -> integer().
leave_count(Diff) ->
    erlang:length(erlang:element(4, Diff)).

-file("src/lightspeed/presence.gleam", 136).
?DOC(" Stable diff label for fixtures.\n").
-spec diff_label(diff()) -> binary().
diff_label(Diff) ->
    <<<<<<<<(erlang:element(2, Diff))/binary, ":joins="/utf8>>/binary,
                (erlang:integer_to_binary(join_count(Diff)))/binary>>/binary,
            ":leaves="/utf8>>/binary,
        (erlang:integer_to_binary(leave_count(Diff)))/binary>>.

-file("src/lightspeed/presence.gleam", 145).
?DOC(" Meta reference.\n").
-spec meta_ref(meta()) -> binary().
meta_ref(Meta) ->
    erlang:element(2, Meta).

-file("src/lightspeed/presence.gleam", 150).
?DOC(" Meta online timestamp.\n").
-spec meta_online_at_ms(meta()) -> integer().
meta_online_at_ms(Meta) ->
    erlang:element(3, Meta).