src/rally_runtime@effect.erl

-module(rally_runtime@effect).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally_runtime/effect.gleam").
-export([none/0, from/1, send_to_server/1, rpc/2, get_ws_page/0, send_to_client/1, broadcast_to_page/1, broadcast_to_app/1, send_to_client_context/1, navigate/1, set_dark_mode/1, set_lang/1, read_dark_mode/0, read_lang/0, get_ws_session/0, broadcast_to_session/1, put_ws_state/3, get_stored_server_context/0, get_ws_conn/0, drain_outgoing_frames/0, put_ws_session/1, decode_rally_push/1, decode_rally_push_json/1, put_ws_identity/1, get_ws_identity/0, put_ws_hostname/1, get_ws_hostname/0, put_ws_auth_timestamp/1, get_ws_auth_timestamp/0, clear_ws_auth_state/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(
    " The API that page modules import for server communication, broadcast,\n"
    " and navigation.\n"
    "\n"
    " This module has a split personality by design. Each function has two\n"
    " implementations: the server-side version here (which queues push frames\n"
    " via the process dictionary or is a no-op), and a client-side version\n"
    " in the generated rally_runtime/effect.gleam shim (which calls the\n"
    " browser WebSocket transport). The codegen rewrites imports so the\n"
    " client package uses the shim, not this file.\n"
    "\n"
    " Two server communication models:\n"
    "\n"
    "   rpc(msg, on_response:)    Stateless request-response. Define a\n"
    "                             ServerX message type and server_x handler.\n"
    "                             Client sends, server returns a value.\n"
    "                             Use this by default.\n"
    "\n"
    "   send_to_server(msg)       Stateful bidirectional. Define ToServer/\n"
    "                             ToClient types and server_init/server_update.\n"
    "                             Server keeps a ServerModel per connection\n"
    "                             and can push ToClient messages any time.\n"
    "                             Use when the server needs state between calls.\n"
).

-file("src/rally_runtime/effect.gleam", 30).
-spec none() -> lustre@effect:effect(any()).
none() ->
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 34).
-spec from(fun((fun((ALBJ) -> nil)) -> nil)) -> lustre@effect:effect(ALBJ).
from(F) ->
    lustre@effect:from(F).

-file("src/rally_runtime/effect.gleam", 42).
?DOC(
    " Send a ToServer variant to the server over WebSocket.\n"
    " Part of the stateful model (ToServer/ToClient/ServerModel).\n"
    " On the server this is a no-op. On the client, the generated\n"
    " transport module provides the real implementation.\n"
).
-spec send_to_server(any()) -> lustre@effect:effect(any()).
send_to_server(_) ->
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 50).
?DOC(
    " Call a server_* RPC handler and deliver the return value to on_response.\n"
    " Part of the stateless RPC model (ServerX type + server_x function).\n"
    " On the server this is a no-op. On the client, the generated transport\n"
    " module encodes the message and sends it over WebSocket.\n"
).
-spec rpc(any(), fun((any()) -> ALBQ)) -> lustre@effect:effect(ALBQ).
rpc(_, _) ->
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 166).
?DOC(" Get the current page name for this WS connection.\n").
-spec get_ws_page() -> binary().
get_ws_page() ->
    rally_runtime_ffi:get_ws_page().

-file("src/rally_runtime/effect.gleam", 141).
-spec do_push(any()) -> nil.
do_push(Msg) ->
    Page = rally_runtime_ffi:get_ws_page(),
    Frame = rally_runtime_ffi:encode_push_frame(Page, Msg),
    rally_runtime_ffi:push_outgoing_frame(Frame).

-file("src/rally_runtime/effect.gleam", 57).
?DOC(
    " Send a ToClient variant to the connected client.\n"
    " Encodes the message as a protocol-specific push frame and queues it for\n"
    " the WebSocket handler to send after the current dispatch.\n"
).
-spec send_to_client(any()) -> lustre@effect:effect(any()).
send_to_client(Msg) ->
    do_push(Msg),
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 65).
?DOC(
    " Broadcast a message to all connections viewing the current page.\n"
    " Broadcasts via pg topics for other connections, plus push_outgoing_frame\n"
    " for the sender's own connection (which isn't subscribed to its own topic).\n"
).
-spec broadcast_to_page(any()) -> lustre@effect:effect(any()).
broadcast_to_page(Msg) ->
    Page = rally_runtime_ffi:get_ws_page(),
    Frame = rally_runtime_ffi:encode_push_frame(Page, Msg),
    rally_runtime_topics_ffi:broadcast(<<"page:"/utf8, Page/binary>>, Frame),
    rally_runtime_ffi:push_outgoing_frame(Frame),
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 74).
?DOC(" Broadcast a message to every connection in the app.\n").
-spec broadcast_to_app(any()) -> lustre@effect:effect(any()).
broadcast_to_app(Msg) ->
    Page = rally_runtime_ffi:get_ws_page(),
    Frame = rally_runtime_ffi:encode_push_frame(Page, Msg),
    rally_runtime_topics_ffi:broadcast(<<"app"/utf8>>, Frame),
    rally_runtime_ffi:push_outgoing_frame(Frame),
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 85).
?DOC(
    " Send a ClientContextMsg to update the client's shared context.\n"
    " On the server, encodes and queues a push frame tagged \"__ClientContext__\".\n"
    " On the client, the generated app dispatches it through client_context.update.\n"
).
-spec send_to_client_context(any()) -> lustre@effect:effect(any()).
send_to_client_context(Msg) ->
    Frame = rally_runtime_ffi:encode_push_frame(
        <<"__ClientContext__"/utf8>>,
        Msg
    ),
    rally_runtime_ffi:push_outgoing_frame(Frame),
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 102).
-spec do_navigate(binary()) -> nil.
do_navigate(_) ->
    nil.

-file("src/rally_runtime/effect.gleam", 94).
?DOC(
    " Navigate to a new URL path. Pushes a new history entry and triggers\n"
    " a route change via modem's popstate listener.\n"
    " On the server, this is a no-op.\n"
).
-spec navigate(binary()) -> lustre@effect:effect(any()).
navigate(Path) ->
    lustre@effect:from(
        fun(_) ->
            nil = do_navigate(Path),
            nil
        end
    ).

-file("src/rally_runtime/effect.gleam", 108).
?DOC(
    " Toggle dark mode. On the client, sets the cookie and toggles the class.\n"
    " On the server, this is a no-op.\n"
).
-spec set_dark_mode(boolean()) -> lustre@effect:effect(any()).
set_dark_mode(_) ->
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 114).
?DOC(
    " Set the language preference cookie.\n"
    " On the server, this is a no-op.\n"
).
-spec set_lang(binary()) -> lustre@effect:effect(any()).
set_lang(_) ->
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 121).
?DOC(
    " Read the dark mode preference from the cookie.\n"
    " Falls back to prefers-color-scheme media query.\n"
    " On the server, returns False.\n"
).
-spec read_dark_mode() -> boolean().
read_dark_mode() ->
    false.

-file("src/rally_runtime/effect.gleam", 127).
?DOC(
    " Read the language preference from the cookie.\n"
    " On the server, returns \"en\".\n"
).
-spec read_lang() -> binary().
read_lang() ->
    <<"en"/utf8>>.

-file("src/rally_runtime/effect.gleam", 201).
?DOC(" Get the session ID for the current WS connection.\n").
-spec get_ws_session() -> binary().
get_ws_session() ->
    rally_runtime_ffi:get_ws_session().

-file("src/rally_runtime/effect.gleam", 132).
?DOC(" Broadcast a message to all connections in the current browser session.\n").
-spec broadcast_to_session(any()) -> lustre@effect:effect(any()).
broadcast_to_session(Msg) ->
    Page = rally_runtime_ffi:get_ws_page(),
    Session = rally_runtime_ffi:get_ws_session(),
    Frame = rally_runtime_ffi:encode_push_frame(Page, Msg),
    rally_runtime_topics_ffi:broadcast(
        <<"session:"/utf8, Session/binary>>,
        Frame
    ),
    rally_runtime_ffi:push_outgoing_frame(Frame),
    lustre@effect:none().

-file("src/rally_runtime/effect.gleam", 154).
?DOC(" Store the WS connection handle, server context, and current page name.\n").
-spec put_ws_state(any(), any(), binary()) -> nil.
put_ws_state(_conn, _server_context, _page) ->
    rally_runtime_ffi:put_ws_state(_conn, _server_context, _page).

-file("src/rally_runtime/effect.gleam", 160).
?DOC(" Retrieve the server context stored on the current WS process.\n").
-spec get_stored_server_context() -> {ok, any()} | {error, nil}.
get_stored_server_context() ->
    rally_runtime_ffi:get_stored_server_context().

-file("src/rally_runtime/effect.gleam", 172).
?DOC(" Get the mist connection handle for this WS process.\n").
-spec get_ws_conn() -> {ok, any()} | {error, nil}.
get_ws_conn() ->
    rally_runtime_ffi:get_ws_conn().

-file("src/rally_runtime/effect.gleam", 187).
?DOC(" Drain all queued push frames. Called by the WS handler after dispatch.\n").
-spec drain_outgoing_frames() -> list(any()).
drain_outgoing_frames() ->
    rally_runtime_ffi:drain_outgoing_frames().

-file("src/rally_runtime/effect.gleam", 195).
?DOC(" Store the session ID on the current WS process.\n").
-spec put_ws_session(binary()) -> nil.
put_ws_session(_session_id) ->
    rally_runtime_ffi:put_ws_session(_session_id).

-file("src/rally_runtime/effect.gleam", 209).
?DOC(" Decode an inbound push frame (ETF protocol).\n").
-spec decode_rally_push(any()) -> {ok, bitstring()} | {error, nil}.
decode_rally_push(_msg) ->
    rally_runtime_ffi:decode_rally_push(_msg).

-file("src/rally_runtime/effect.gleam", 215).
?DOC(" Decode an inbound push frame (JSON protocol).\n").
-spec decode_rally_push_json(any()) -> {ok, binary()} | {error, nil}.
decode_rally_push_json(_msg) ->
    rally_runtime_ffi:decode_rally_push_json(_msg).

-file("src/rally_runtime/effect.gleam", 230).
?DOC(
    " Store the resolved identity on the WebSocket connection process.\n"
    " The identity type is opaque to Rally; it's stored as an Erlang term.\n"
).
-spec put_ws_identity(any()) -> nil.
put_ws_identity(_identity) ->
    rally_runtime_ffi:put_ws_identity(_identity).

-file("src/rally_runtime/effect.gleam", 237).
?DOC(
    " Retrieve the stored identity. Returns Error(Nil) when no identity\n"
    " has been stored (fresh process or pre-auth connection).\n"
).
-spec get_ws_identity() -> {ok, any()} | {error, nil}.
get_ws_identity() ->
    rally_runtime_ffi:get_ws_identity().

-file("src/rally_runtime/effect.gleam", 243).
?DOC(" Store the hostname extracted during WebSocket upgrade.\n").
-spec put_ws_hostname(binary()) -> nil.
put_ws_hostname(_hostname) ->
    rally_runtime_ffi:put_ws_hostname(_hostname).

-file("src/rally_runtime/effect.gleam", 249).
?DOC(" Retrieve the stored hostname. Returns \"\" when not set.\n").
-spec get_ws_hostname() -> binary().
get_ws_hostname() ->
    rally_runtime_ffi:get_ws_hostname().

-file("src/rally_runtime/effect.gleam", 255).
?DOC(" Store the Unix timestamp of the last successful auth check.\n").
-spec put_ws_auth_timestamp(integer()) -> nil.
put_ws_auth_timestamp(_ts) ->
    rally_runtime_ffi:put_ws_auth_timestamp(_ts).

-file("src/rally_runtime/effect.gleam", 262).
?DOC(
    " Retrieve the auth timestamp. Returns 0 when not set (0 = never authed,\n"
    " triggers immediate reauth on first RPC).\n"
).
-spec get_ws_auth_timestamp() -> integer().
get_ws_auth_timestamp() ->
    rally_runtime_ffi:get_ws_auth_timestamp().

-file("src/rally_runtime/effect.gleam", 269).
?DOC(
    " Clear identity and reset timestamp to 0. Hostname is preserved\n"
    " (connection-scoped, not auth-scoped). Used during reauth and in tests.\n"
).
-spec clear_ws_auth_state() -> nil.
clear_ws_auth_state() ->
    rally_runtime_ffi:clear_ws_auth_state().