src/rally@generator@client.erl

-module(rally@generator@client).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/generator/client.gleam").
-export([generate_package_with_client_context_contract/8, generate_package/6]).
-export_type([generated_file/0, client_context_sync_fields/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(
    " Client package generation.\n"
    "\n"
    " Assembles the standalone Gleam project that compiles to the browser\n"
    " SPA: gleam.toml, app.gleam (Lustre entry point with per-page TEA loop),\n"
    " transport.gleam (WebSocket FFI bridge), and router.gleam (client-side\n"
    " route parsing). The generated package lives in .generated_clients/<namespace>.\n"
).

-type generated_file() :: {generated_file, binary(), binary()}.

-type client_context_sync_fields() :: {client_context_sync_fields,
        boolean(),
        boolean(),
        boolean()}.

-file("src/rally/generator/client.gleam", 103).
-spec empty_client_context_contract() -> rally@types:client_context_contract().
empty_client_context_contract() ->
    {client_context_contract, [], [], true, true}.

-file("src/rally/generator/client.gleam", 96).
-spec config_mjs(binary()) -> binary().
config_mjs(Protocol) ->
    <<<<<<"// Generated by Rally — do not edit.\n"/utf8,
                "export const protocol = \""/utf8>>/binary,
            Protocol/binary>>/binary,
        "\";\n"/utf8>>.

-file("src/rally/generator/client.gleam", 1231).
?DOC(
    " Generate the import alias for a page module, using the route's module_path.\n"
    " e.g. \"pages/home_\" -> import alias \"pages_home_\"\n"
    "      \"pages/article/slug_\" -> import alias \"pages_article_slug_\"\n"
).
-spec page_module_alias(rally@types:scanned_route()) -> binary().
page_module_alias(Route) ->
    gleam@string:replace(erlang:element(5, Route), <<"/"/utf8>>, <<"_"/utf8>>).

-file("src/rally/generator/client.gleam", 1250).
-spec generate_page_imports(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
generate_page_imports(Routes, Contract_map) ->
    _pipe = Routes,
    _pipe@1 = gleam@list:filter_map(
        _pipe,
        fun(Route) ->
            case gleam_stdlib:map_get(Contract_map, erlang:element(3, Route)) of
                {ok, Contract} when erlang:element(7, Contract) ->
                    Alias = page_module_alias(Route),
                    {ok,
                        <<<<<<"import "/utf8,
                                    (erlang:element(5, Route))/binary>>/binary,
                                " as "/utf8>>/binary,
                            Alias/binary>>};

                _ ->
                    {error, nil}
            end
        end
    ),
    _pipe@2 = gleam@list:unique(_pipe@1),
    _pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2),
    gleam@string:join(_pipe@3, <<"\n"/utf8>>).

-file("src/rally/generator/client.gleam", 1246).
-spec module_alias(binary()) -> binary().
module_alias(Module_path) ->
    gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>).

-file("src/rally/generator/client.gleam", 1239).
-spec last_segment(binary()) -> binary().
last_segment(Module_path) ->
    case begin
        _pipe = gleam@string:split(Module_path, <<"/"/utf8>>),
        gleam@list:last(_pipe)
    end of
        {ok, Seg} ->
            Seg;

        {error, nil} ->
            Module_path
    end.

-file("src/rally/generator/client.gleam", 112).
-spec import_as(binary(), binary()) -> binary().
import_as(Module_path, Alias) ->
    case last_segment(Module_path) =:= Alias of
        true ->
            <<"import "/utf8, Module_path/binary>>;

        false ->
            <<<<<<"import "/utf8, Module_path/binary>>/binary, " as "/utf8>>/binary,
                Alias/binary>>
    end.

-file("src/rally/generator/client.gleam", 1177).
-spec generate_render_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
generate_render_page(Routes, Contract_map, Has_client_context) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Alias = page_module_alias(Route),
                        Vn = erlang:element(3, Route),
                        View_args = case Has_client_context of
                            true ->
                                <<"(client_context, m)"/utf8>>;

                            false ->
                                <<"(m)"/utf8>>
                        end,
                        {ok,
                            <<<<<<<<<<<<<<<<<<"    "/utf8, Vn/binary>>/binary,
                                                            "PageModel(m) ->\n"/utf8>>/binary,
                                                        "      element.map("/utf8>>/binary,
                                                    Alias/binary>>/binary,
                                                ".view"/utf8>>/binary,
                                            View_args/binary>>/binary,
                                        ", fn(msg) { PageMsg("/utf8>>/binary,
                                    Vn/binary>>/binary,
                                "PageMsg(msg)) })"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    Not_found_arm = <<"    NoPageModel -> html.div([], [html.text(\"Page not found\")])"/utf8>>,
    Sig = case Has_client_context of
        true ->
            <<"fn render_page(page_model: PageModel, client_context: client_context.ClientContext) -> Element(Msg) {"/utf8>>;

        false ->
            <<"fn render_page(page_model: PageModel) -> Element(Msg) {"/utf8>>
    end,
    <<<<<<<<<<Sig/binary, "\n  case page_model {\n"/utf8>>/binary, Arms/binary>>/binary,
                "\n"/utf8>>/binary,
            Not_found_arm/binary>>/binary,
        "\n  }\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 1080).
-spec generate_update_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
generate_update_page(Routes, Contract_map, Has_client_context) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Alias = page_module_alias(Route),
                        Vn = erlang:element(3, Route),
                        case {Has_client_context, erlang:element(8, Contract)} of
                            {true, true} ->
                                {ok,
                                    <<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                        Vn/binary>>/binary,
                                                                                    "PageModel(m), "/utf8>>/binary,
                                                                                Vn/binary>>/binary,
                                                                            "PageMsg(msg) -> {\n"/utf8>>/binary,
                                                                        "      let #(new_m, e, ctx_msg) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".update(client_context, m, msg)\n"/utf8>>/binary,
                                                            "      #("/utf8>>/binary,
                                                        Vn/binary>>/binary,
                                                    "PageModel(new_m), effect.map(e, fn(msg) { PageMsg("/utf8>>/binary,
                                                Vn/binary>>/binary,
                                            "PageMsg(msg)) }), ctx_msg)\n"/utf8>>/binary,
                                        "    }"/utf8>>};

                            {true, false} ->
                                {ok,
                                    <<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                        Vn/binary>>/binary,
                                                                                    "PageModel(m), "/utf8>>/binary,
                                                                                Vn/binary>>/binary,
                                                                            "PageMsg(msg) -> {\n"/utf8>>/binary,
                                                                        "      let #(new_m, e) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".update(client_context, m, msg)\n"/utf8>>/binary,
                                                            "      #("/utf8>>/binary,
                                                        Vn/binary>>/binary,
                                                    "PageModel(new_m), effect.map(e, fn(msg) { PageMsg("/utf8>>/binary,
                                                Vn/binary>>/binary,
                                            "PageMsg(msg)) }), None)\n"/utf8>>/binary,
                                        "    }"/utf8>>};

                            {_, _} ->
                                {ok,
                                    <<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                        Vn/binary>>/binary,
                                                                                    "PageModel(m), "/utf8>>/binary,
                                                                                Vn/binary>>/binary,
                                                                            "PageMsg(msg) -> {\n"/utf8>>/binary,
                                                                        "      let #(new_m, e) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".update(m, msg)\n"/utf8>>/binary,
                                                            "      #("/utf8>>/binary,
                                                        Vn/binary>>/binary,
                                                    "PageModel(new_m), effect.map(e, fn(msg) { PageMsg("/utf8>>/binary,
                                                Vn/binary>>/binary,
                                            "PageMsg(msg)) }))\n"/utf8>>/binary,
                                        "    }"/utf8>>}
                        end;

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    case Has_client_context of
        true ->
            Catch_all = <<"    _, _ -> #(page_model, effect.none(), None)"/utf8>>,
            Sig = <<"fn update_page(page_model page_model: PageModel, page_msg page_msg: PageMsg, client_context client_context: client_context.ClientContext) -> #(PageModel, Effect(Msg), Option(client_context.ClientContextMsg)) {"/utf8>>,
            <<<<<<<<<<Sig/binary, "\n  case page_model, page_msg {\n"/utf8>>/binary,
                            Arms/binary>>/binary,
                        "\n"/utf8>>/binary,
                    Catch_all/binary>>/binary,
                "\n  }\n}"/utf8>>;

        false ->
            Catch_all@1 = <<"    _, _ -> #(page_model, effect.none())"/utf8>>,
            Sig@1 = <<"fn update_page(page_model page_model: PageModel, page_msg page_msg: PageMsg) -> #(PageModel, Effect(Msg)) {"/utf8>>,
            <<<<<<<<<<Sig@1/binary, "\n  case page_model, page_msg {\n"/utf8>>/binary,
                            Arms/binary>>/binary,
                        "\n"/utf8>>/binary,
                    Catch_all@1/binary>>/binary,
                "\n  }\n}"/utf8>>
    end.

-file("src/rally/generator/client.gleam", 834).
-spec route_pattern_with(rally@types:scanned_route(), binary()) -> binary().
route_pattern_with(Route, Prefix) ->
    case erlang:element(4, Route) of
        [] ->
            <<"router."/utf8, (erlang:element(3, Route))/binary>>;

        Params ->
            Param_names = begin
                _pipe = Params,
                _pipe@1 = gleam@list:map(
                    _pipe,
                    fun(P) ->
                        <<Prefix/binary, (erlang:element(1, P))/binary>>
                    end
                ),
                gleam@string:join(_pipe@1, <<", "/utf8>>)
            end,
            <<<<<<<<"router."/utf8, (erlang:element(3, Route))/binary>>/binary,
                        "("/utf8>>/binary,
                    Param_names/binary>>/binary,
                ")"/utf8>>
    end.

-file("src/rally/generator/client.gleam", 826).
?DOC(
    " Generate a route pattern match expression, e.g. \"router.Home\" or\n"
    " \"router.ArticleSlug(slug)\" depending on whether the route has params.\n"
).
-spec route_pattern(rally@types:scanned_route()) -> binary().
route_pattern(Route) ->
    route_pattern_with(Route, <<""/utf8>>).

-file("src/rally/generator/client.gleam", 1029).
-spec generate_reinit_server(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
generate_reinit_server(Routes, Contract_map) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Pattern = route_pattern(Route),
                        Body = case erlang:element(4, Route) of
                            [] ->
                                <<<<"      transport.send_page_init(\""/utf8,
                                        (erlang:element(3, Route))/binary>>/binary,
                                    "\", Nil)"/utf8>>;

                            [Single] ->
                                <<<<<<<<"      transport.send_page_init(\""/utf8,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "\", "/utf8>>/binary,
                                        (erlang:element(1, Single))/binary>>/binary,
                                    ")"/utf8>>;

                            Params ->
                                Tuple = <<<<"#("/utf8,
                                        (gleam@string:join(
                                            gleam@list:map(
                                                Params,
                                                fun(P) ->
                                                    erlang:element(1, P)
                                                end
                                            ),
                                            <<", "/utf8>>
                                        ))/binary>>/binary,
                                    ")"/utf8>>,
                                <<<<<<<<"      transport.send_page_init(\""/utf8,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "\", "/utf8>>/binary,
                                        Tuple/binary>>/binary,
                                    ")"/utf8>>
                        end,
                        {ok,
                            <<<<<<<<"    "/utf8, Pattern/binary>>/binary,
                                        " -> {\n"/utf8>>/binary,
                                    Body/binary>>/binary,
                                "\n      effect.none()\n    }"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    <<<<"fn reinit_server(route: router.Route) -> Effect(Msg) {\n  case route {\n"/utf8,
            Arms/binary>>/binary,
        "\n    _ -> effect.none()\n  }\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 1235).
-spec page_model_decoder_name(binary()) -> binary().
page_model_decoder_name(Module_path) ->
    <<<<"decode_"/utf8,
            (gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>))/binary>>/binary,
        "_model"/utf8>>.

-file("src/rally/generator/client.gleam", 830).
-spec route_pattern_ignored(rally@types:scanned_route()) -> binary().
route_pattern_ignored(Route) ->
    route_pattern_with(Route, <<"_"/utf8>>).

-file("src/rally/generator/client.gleam", 929).
-spec generate_hydrate_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
generate_hydrate_page(Routes, Contract_map, Has_client_context) ->
    Hydrate_uses_client_context = Has_client_context andalso gleam@list:any(
        Routes,
        fun(Route) ->
            case gleam_stdlib:map_get(Contract_map, erlang:element(3, Route)) of
                {ok, Contract} ->
                    erlang:element(7, Contract) andalso erlang:element(
                        6,
                        Contract
                    );

                {error, nil} ->
                    false
            end
        end
    ),
    Hydrate_client_context_name = case Hydrate_uses_client_context of
        true ->
            <<"client_context"/utf8>>;

        false ->
            <<"_client_context"/utf8>>
    end,
    Error_fallback = case Has_client_context of
        true ->
            <<"        Error(_) -> init_page(route: route, client_context: client_context)\n"/utf8>>;

        false ->
            <<"        Error(_) -> init_page(route: route)\n"/utf8>>
    end,
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route@1) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route@1)
                ) of
                    {ok, Contract@1} when erlang:element(7, Contract@1) andalso erlang:element(
                        6,
                        Contract@1
                    ) ->
                        Alias = page_module_alias(Route@1),
                        Pattern = route_pattern_ignored(Route@1),
                        Decoder_name = page_model_decoder_name(
                            erlang:element(5, Route@1)
                        ),
                        Init_loaded_call = case Has_client_context of
                            true ->
                                <<<<<<Alias/binary, ".init_loaded("/utf8>>/binary,
                                        Hydrate_client_context_name/binary>>/binary,
                                    ", model)"/utf8>>;

                            false ->
                                <<Alias/binary, ".init_loaded(model)"/utf8>>
                        end,
                        Variant = erlang:element(3, Route@1),
                        {ok,
                            <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                                    Pattern/binary>>/binary,
                                                                                                " -> {\n"/utf8>>/binary,
                                                                                            "      case codec.decode_flags_typed(flags, \""/utf8>>/binary,
                                                                                        Decoder_name/binary>>/binary,
                                                                                    "\") {\n"/utf8>>/binary,
                                                                                "        Ok(model) -> {\n"/utf8>>/binary,
                                                                            "          let #(m, e) = "/utf8>>/binary,
                                                                        Init_loaded_call/binary>>/binary,
                                                                    "\n"/utf8>>/binary,
                                                                "          #("/utf8>>/binary,
                                                            Variant/binary>>/binary,
                                                        "PageModel(m), effect.map(e, fn(msg) { PageMsg("/utf8>>/binary,
                                                    Variant/binary>>/binary,
                                                "PageMsg(msg)) }))\n"/utf8>>/binary,
                                            "        }\n"/utf8>>/binary,
                                        Error_fallback/binary>>/binary,
                                    "      }\n"/utf8>>/binary,
                                "    }"/utf8>>};

                    {ok, Contract@2} when erlang:element(7, Contract@2) ->
                        Pattern@1 = route_pattern_ignored(Route@1),
                        Decoder_name@1 = page_model_decoder_name(
                            erlang:element(5, Route@1)
                        ),
                        Variant@1 = erlang:element(3, Route@1),
                        {ok,
                            <<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                        Pattern@1/binary>>/binary,
                                                                    " -> {\n"/utf8>>/binary,
                                                                "      case codec.decode_flags_typed(flags, \""/utf8>>/binary,
                                                            Decoder_name@1/binary>>/binary,
                                                        "\") {\n"/utf8>>/binary,
                                                    "        Ok(model) -> #("/utf8>>/binary,
                                                Variant@1/binary>>/binary,
                                            "PageModel(model), effect.none())\n"/utf8>>/binary,
                                        Error_fallback/binary>>/binary,
                                    "      }\n"/utf8>>/binary,
                                "    }"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    Sig = case Has_client_context of
        true ->
            <<<<"fn hydrate_page(route: router.Route, flags: String, "/utf8,
                    Hydrate_client_context_name/binary>>/binary,
                ": client_context.ClientContext) -> #(PageModel, Effect(Msg)) {"/utf8>>;

        false ->
            <<"fn hydrate_page(route: router.Route, flags: String) -> #(PageModel, Effect(Msg)) {"/utf8>>
    end,
    <<<<<<Sig/binary, "\n  case route {\n"/utf8>>/binary, Arms/binary>>/binary,
        "\n    _ -> #(NoPageModel, effect.none())\n  }\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 848).
?DOC(" Generate the extra arguments for init_page calls from route params.\n").
-spec route_param_args(rally@types:scanned_route()) -> binary().
route_param_args(Route) ->
    case erlang:element(4, Route) of
        [] ->
            <<""/utf8>>;

        Params ->
            _pipe = Params,
            _pipe@1 = gleam@list:map(
                _pipe,
                fun(P) -> <<", "/utf8, (erlang:element(1, P))/binary>> end
            ),
            gleam@string:join(_pipe@1, <<""/utf8>>)
    end.

-file("src/rally/generator/client.gleam", 858).
-spec generate_init_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
generate_init_page(Routes, Contract_map, Has_client_context) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Alias = page_module_alias(Route),
                        Pattern = route_pattern(Route),
                        Param_args = route_param_args(Route),
                        Call_args = case {Has_client_context, Param_args} of
                            {true, _} ->
                                <<<<<<"("/utf8, "client_context"/utf8>>/binary,
                                        Param_args/binary>>/binary,
                                    ")"/utf8>>;

                            {false, <<""/utf8>>} ->
                                <<"()"/utf8>>;

                            {false, _} ->
                                <<<<"("/utf8,
                                        (gleam@string:drop_start(Param_args, 2))/binary>>/binary,
                                    ")"/utf8>>
                        end,
                        Server_init_call = case erlang:element(4, Route) of
                            [] ->
                                <<<<"      transport.send_page_init(\""/utf8,
                                        (erlang:element(3, Route))/binary>>/binary,
                                    "\", Nil)\n"/utf8>>;

                            Params ->
                                Param_names = gleam@list:map(
                                    Params,
                                    fun(P) -> erlang:element(1, P) end
                                ),
                                Params_tuple = case Param_names of
                                    [Single] ->
                                        Single;

                                    _ ->
                                        <<<<"#("/utf8,
                                                (gleam@string:join(
                                                    Param_names,
                                                    <<", "/utf8>>
                                                ))/binary>>/binary,
                                            ")"/utf8>>
                                end,
                                <<<<<<<<"      transport.send_page_init(\""/utf8,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "\", "/utf8>>/binary,
                                        Params_tuple/binary>>/binary,
                                    ")\n"/utf8>>
                        end,
                        {ok,
                            <<<<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                    Pattern/binary>>/binary,
                                                                                " -> {\n"/utf8>>/binary,
                                                                            Server_init_call/binary>>/binary,
                                                                        "      let #(m, e) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".init"/utf8>>/binary,
                                                            Call_args/binary>>/binary,
                                                        "\n"/utf8>>/binary,
                                                    "      #("/utf8>>/binary,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "PageModel(m), effect.map(e, fn(msg) { PageMsg("/utf8>>/binary,
                                        (erlang:element(3, Route))/binary>>/binary,
                                    "PageMsg(msg)) }))\n"/utf8>>/binary,
                                "    }"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    Not_found_arm = <<"    _ -> #(NoPageModel, effect.none())"/utf8>>,
    Sig = case Has_client_context of
        true ->
            <<"fn init_page(route route: router.Route, client_context client_context: client_context.ClientContext) -> #(PageModel, Effect(Msg)) {"/utf8>>;

        false ->
            <<"fn init_page(route route: router.Route) -> #(PageModel, Effect(Msg)) {"/utf8>>
    end,
    <<<<<<<<<<Sig/binary, "\n  case route {\n"/utf8>>/binary, Arms/binary>>/binary,
                "\n"/utf8>>/binary,
            Not_found_arm/binary>>/binary,
        "\n  }\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 805).
-spec generate_page_msg_type(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
generate_page_msg_type(Routes, Contract_map) ->
    Variants = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Alias = page_module_alias(Route),
                        {ok,
                            <<<<<<<<"  "/utf8,
                                            (erlang:element(3, Route))/binary>>/binary,
                                        "PageMsg("/utf8>>/binary,
                                    Alias/binary>>/binary,
                                ".Msg)"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    <<<<"pub type PageMsg {\n"/utf8, Variants/binary>>/binary, "\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 786).
-spec generate_page_model_type(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
generate_page_model_type(Routes, Contract_map) ->
    Variants = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:filter_map(
            _pipe,
            fun(Route) ->
                case gleam_stdlib:map_get(
                    Contract_map,
                    erlang:element(3, Route)
                ) of
                    {ok, Contract} when erlang:element(7, Contract) ->
                        Alias = page_module_alias(Route),
                        {ok,
                            <<<<<<<<"  "/utf8,
                                            (erlang:element(3, Route))/binary>>/binary,
                                        "PageModel("/utf8>>/binary,
                                    Alias/binary>>/binary,
                                ".Model)"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    <<<<"pub type PageModel {\n"/utf8, Variants/binary>>/binary,
        "\n  NoPageModel\n}"/utf8>>.

-file("src/rally/generator/client.gleam", 755).
-spec find_to_client_msg_wrapper(
    rally@types:scanned_route(),
    rally@types:page_contract()
) -> {ok, binary()} | {error, nil}.
find_to_client_msg_wrapper(Route, Contract) ->
    _pipe = erlang:element(3, Contract),
    gleam@list:find_map(
        _pipe,
        fun(Variant) -> case erlang:element(3, Variant) of
                [Field] ->
                    case erlang:element(3, Field) of
                        {user_type, Module_path, <<"ToClient"/utf8>>, []} when Module_path =:= erlang:element(
                            5,
                            Route
                        ) ->
                            {ok, erlang:element(2, Variant)};

                        _ ->
                            {error, nil}
                    end;

                _ ->
                    {error, nil}
            end end
    ).

-file("src/rally/generator/client.gleam", 734).
-spec generate_push_registrations(
    list({rally@types:scanned_route(), rally@types:page_contract()})
) -> binary().
generate_push_registrations(Contracts) ->
    _pipe = Contracts,
    _pipe@1 = gleam@list:filter_map(
        _pipe,
        fun(Pair) ->
            {Route, Contract} = Pair,
            case find_to_client_msg_wrapper(Route, Contract) of
                {ok, Wrapper} ->
                    Alias = page_module_alias(Route),
                    {ok,
                        <<<<<<<<<<<<<<<<"
    let _ =
      transport.register_push_handler(\""/utf8,
                                                        (erlang:element(
                                                            3,
                                                            Route
                                                        ))/binary>>/binary,
                                                    "\", fn(raw) {
        dispatch(PageMsg("/utf8>>/binary,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "PageMsg("/utf8>>/binary,
                                        Alias/binary>>/binary,
                                    "."/utf8>>/binary,
                                Wrapper/binary>>/binary,
                            "(transport.coerce(raw)))))
      })"/utf8>>};

                {error, _} ->
                    {error, nil}
            end
        end
    ),
    gleam@string:join(_pipe@1, <<""/utf8>>).

-file("src/rally/generator/client.gleam", 362).
-spec client_context_init_overlay(client_context_sync_fields()) -> binary().
client_context_init_overlay(Fields) ->
    Assignments = [],
    Assignments@1 = case erlang:element(2, Fields) of
        true ->
            [<<"current_path: current_path"/utf8>> | Assignments];

        false ->
            Assignments
    end,
    Assignments@2 = case erlang:element(3, Fields) of
        true ->
            [<<"dark_mode: rally_effect.read_dark_mode()"/utf8>> |
                Assignments@1];

        false ->
            Assignments@1
    end,
    Assignments@3 = case erlang:element(4, Fields) of
        true ->
            [<<"lang: rally_effect.read_lang()"/utf8>> | Assignments@2];

        false ->
            Assignments@2
    end,
    Assignments@4 = lists:reverse(Assignments@3),
    case Assignments@4 of
        [] ->
            <<""/utf8>>;

        _ ->
            <<<<"  let client_context = client_context.ClientContext(
    ..client_context,
    "/utf8,
                    (gleam@string:join(Assignments@4, <<",
    "/utf8>>))/binary>>/binary,
                ",
  )
"/utf8>>
    end.

-file("src/rally/generator/client.gleam", 337).
-spec client_context_sync_fields(
    gleam@option:option(rally@types:client_context_contract())
) -> client_context_sync_fields().
client_context_sync_fields(Contract) ->
    Fields = case Contract of
        {some, Contract@1} ->
            _pipe = erlang:element(2, Contract@1),
            _pipe@1 = gleam@list:find_map(
                _pipe,
                fun(Variant) -> case erlang:element(2, Variant) of
                        <<"ClientContext"/utf8>> ->
                            {ok, erlang:element(3, Variant)};

                        _ ->
                            {error, nil}
                    end end
            ),
            _pipe@2 = gleam@result:unwrap(_pipe@1, []),
            _pipe@3 = gleam@list:map(
                _pipe@2,
                fun(Field) -> erlang:element(2, Field) end
            ),
            gleam@set:from_list(_pipe@3);

        none ->
            gleam@set:new()
    end,
    {client_context_sync_fields,
        gleam@set:contains(Fields, <<"current_path"/utf8>>),
        gleam@set:contains(Fields, <<"dark_mode"/utf8>>),
        gleam@set:contains(Fields, <<"lang"/utf8>>)}.

-file("src/rally/generator/client.gleam", 392).
-spec app_gleam(
    list(rally@types:scanned_route()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    gleam@option:option(rally@types:client_context_contract()),
    binary()
) -> binary().
app_gleam(Routes, Contracts, Client_context_contract, Client_context_module) ->
    Has_client_context = gleam@option:is_some(Client_context_contract),
    Sync_fields = client_context_sync_fields(Client_context_contract),
    Init_context_overlay = client_context_init_overlay(Sync_fields),
    Needs_rally_effect = erlang:element(3, Sync_fields) orelse erlang:element(
        4,
        Sync_fields
    ),
    Contract_map = begin
        _pipe = Contracts,
        _pipe@1 = gleam@list:map(
            _pipe,
            fun(Pair) ->
                {erlang:element(3, (erlang:element(1, Pair))),
                    erlang:element(2, Pair)}
            end
        ),
        maps:from_list(_pipe@1)
    end,
    Push_registrations = generate_push_registrations(Contracts),
    Page_model_type = generate_page_model_type(Routes, Contract_map),
    Page_msg_type = generate_page_msg_type(Routes, Contract_map),
    Init_page_fn = generate_init_page(Routes, Contract_map, Has_client_context),
    Hydrate_page_fn = generate_hydrate_page(
        Routes,
        Contract_map,
        Has_client_context
    ),
    Reinit_server_fn = generate_reinit_server(Routes, Contract_map),
    Update_page_fn = generate_update_page(
        Routes,
        Contract_map,
        Has_client_context
    ),
    Render_page_fn = generate_render_page(
        Routes,
        Contract_map,
        Has_client_context
    ),
    Layout_modules = begin
        _pipe@2 = Routes,
        _pipe@3 = gleam@list:filter_map(
            _pipe@2,
            fun(Route) -> case erlang:element(6, Route) of
                    {some, Layout} ->
                        {ok, Layout};

                    _ ->
                        {error, nil}
                end end
        ),
        gleam@list:unique(_pipe@3)
    end,
    Ctx_import = case Has_client_context of
        true ->
            <<"\n"/utf8,
                (import_as(Client_context_module, <<"client_context"/utf8>>))/binary>>;

        false ->
            <<""/utf8>>
    end,
    Rally_effect_import = case Needs_rally_effect of
        true ->
            <<"import rally_runtime/effect as rally_effect\n\n"/utf8>>;

        false ->
            <<"\n"/utf8>>
    end,
    Layout_imports = case Layout_modules of
        [] ->
            <<""/utf8>>;

        Mods ->
            _pipe@4 = gleam@list:map(
                Mods,
                fun(Mod) ->
                    <<<<<<"\nimport "/utf8, Mod/binary>>/binary, " as "/utf8>>/binary,
                        (module_alias(Mod))/binary>>
                end
            ),
            gleam@string:join(_pipe@4, <<""/utf8>>)
    end,
    Ctx_field = case Has_client_context of
        true ->
            <<"\n    client_context: client_context.ClientContext,\n    current_path: String,"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Ctx_msg_variant = case Has_client_context of
        true ->
            <<"\n  ClientContextUpdate(client_context.ClientContextMsg)"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Modem_init = <<"modem.init(fn(uri) { UrlChanged(router.parse_route(uri)) })"/utf8>>,
    Ctx_decoder_name = <<<<"decode_"/utf8,
            (gleam@string:replace(
                Client_context_module,
                <<"/"/utf8>>,
                <<"_"/utf8>>
            ))/binary>>/binary,
        "_client_context"/utf8>>,
    Ctx_init = case Has_client_context of
        true ->
            <<<<<<<<<<<<"  let flags = transport.read_flags()
  let #(ctx_model, ctx_effects) = client_context.init()
  let current_path = router.route_to_path(route)
  let client_context = case codec.decode_flags_typed(transport.read_client_context(), \""/utf8,
                                    Ctx_decoder_name/binary>>/binary,
                                "\") {
    Ok(ctx) -> ctx
    Error(_) -> ctx_model
  }
"/utf8>>/binary,
                            Init_context_overlay/binary>>/binary,
                        "  let #(page_model, page_effects) = case flags {
    \"\" -> init_page(route: route, client_context: client_context)
    _ -> hydrate_page(route, flags, client_context)
  }
  #(Model(route:, page_model:, connection: Disconnected, client_context:, current_path:),
    effect.batch([init_transport(), "/utf8>>/binary,
                    Modem_init/binary>>/binary,
                ", effect.map(ctx_effects, ClientContextUpdate), page_effects]))"/utf8>>;

        false ->
            <<<<"  let flags = transport.read_flags()
  let #(page_model, page_effects) = case flags {
    \"\" -> init_page(route: route)
    _ -> hydrate_page(route, flags)
  }
  #(Model(route:, page_model:, connection: Disconnected),
    effect.batch([init_transport(), "/utf8,
                    Modem_init/binary>>/binary,
                ", page_effects]))"/utf8>>
    end,
    Ctx_update_arm = case Has_client_context of
        true ->
            <<"
    ClientContextUpdate(client_context_msg) -> {
      let #(new_client_context, client_context_effects) = client_context.update(model.client_context, client_context_msg)
      #(Model(..model, client_context: new_client_context), effect.map(client_context_effects, ClientContextUpdate))
    }"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Url_changed_body = case Has_client_context of
        true ->
            case erlang:element(2, Sync_fields) of
                true ->
                    <<"      case route == model.route {
        True -> #(model, effect.none())
        False -> {
          let current_path = router.route_to_path(route)
          let new_client_context =
            client_context.ClientContext(..model.client_context, current_path:)
          let #(page_model, page_effects) = init_page(route: route, client_context: new_client_context)
          #(Model(..model, route:, page_model:, client_context: new_client_context, current_path:), page_effects)
        }
      }"/utf8>>;

                false ->
                    <<"      case route == model.route {
        True -> #(model, effect.none())
        False -> {
          let current_path = router.route_to_path(route)
          let #(page_model, page_effects) = init_page(route: route, client_context: model.client_context)
          #(Model(..model, route:, page_model:, current_path:), page_effects)
        }
      }"/utf8>>
            end;

        false ->
            <<"      case route == model.route {
        True -> #(model, effect.none())
        False -> {
          let #(page_model, page_effects) = init_page(route)
          #(Model(..model, route:, page_model:), page_effects)
        }
      }"/utf8>>
    end,
    Page_msg_body = case Has_client_context of
        true ->
            <<"      let #(page_model, page_effects, ctx_msg) = update_page(page_model: model.page_model, page_msg: page_msg, client_context: model.client_context)
      let #(new_client_context, ctx_effects) = case ctx_msg {
        Some(cm) -> {
          let #(cc, ce) = client_context.update(model.client_context, cm)
          #(cc, effect.map(ce, ClientContextUpdate))
        }
        None -> #(model.client_context, effect.none())
      }
      #(Model(..model, page_model:, client_context: new_client_context), effect.batch([page_effects, ctx_effects]))"/utf8>>;

        false ->
            <<"      let #(page_model, page_effects) = update_page(page_model: model.page_model, page_msg: page_msg)
      #(Model(..model, page_model:), page_effects)"/utf8>>
    end,
    Ctx_push_registration = case Has_client_context of
        true ->
            <<"\n    let _ = transport.register_push_handler(\"__ClientContext__\", fn(raw) {\n      dispatch(ClientContextUpdate(transport.coerce(raw)))\n    })"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Render_page_call = case Has_client_context of
        true ->
            <<"render_page(model.page_model, model.client_context)"/utf8>>;

        false ->
            <<"render_page(model.page_model)"/utf8>>
    end,
    Layout_arms = case Layout_modules of
        [] ->
            <<""/utf8>>;

        _ ->
            _pipe@5 = gleam@list:map(
                Routes,
                fun(Route@1) -> case erlang:element(6, Route@1) of
                        {some, Layout@1} ->
                            Variant = <<(erlang:element(3, Route@1))/binary,
                                "PageModel"/utf8>>,
                            Alias = module_alias(Layout@1),
                            case Has_client_context of
                                true ->
                                    <<<<<<<<"    "/utf8, Variant/binary>>/binary,
                                                "(_) ->\n      "/utf8>>/binary,
                                            Alias/binary>>/binary,
                                        ".layout(model.client_context, ClientContextUpdate, content)\n"/utf8>>;

                                false ->
                                    <<<<<<<<"    "/utf8, Variant/binary>>/binary,
                                                "(_) ->\n      "/utf8>>/binary,
                                            Alias/binary>>/binary,
                                        ".layout(content)\n"/utf8>>
                            end;

                        none ->
                            <<""/utf8>>
                    end end
            ),
            _pipe@6 = gleam@string:join(_pipe@5, <<""/utf8>>),
            (fun(Arms) -> case Arms of
                    <<""/utf8>> ->
                        <<""/utf8>>;

                    _ ->
                        <<Arms/binary, "    _ -> content\n"/utf8>>
                end end)(_pipe@6)
    end,
    Wrap_layout = case Layout_arms of
        <<""/utf8>> ->
            <<""/utf8>>;

        Arms@1 ->
            <<<<"\nfn wrap_layout(model: Model, content: Element(Msg)) -> Element(Msg) {\n  case model.page_model {\n"/utf8,
                    Arms@1/binary>>/binary,
                "  }\n}\n"/utf8>>
    end,
    View_body = case Layout_arms of
        <<""/utf8>> ->
            <<<<"  html.div([attr.class(\"rally-app\")], [
    "/utf8,
                    Render_page_call/binary>>/binary,
                ",
    connection_banner(model.connection),
  ])"/utf8>>;

        _ ->
            <<<<"  let content =
    html.div([attr.class(\"rally-app\")], [
      "/utf8,
                    Render_page_call/binary>>/binary,
                ",
      connection_banner(model.connection),
    ])
  wrap_layout(model, content)"/utf8>>
    end,
    Page_imports = generate_page_imports(Routes, Contract_map),
    Option_import = case Has_client_context of
        true ->
            <<"import gleam/option.{type Option, None, Some}\n"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.
"/utf8,
                                                                                                                                                                        Option_import/binary>>/binary,
                                                                                                                                                                    "import lustre
import lustre/attribute as attr
import lustre/element.{type Element}
import lustre/element/html
import lustre/effect.{type Effect}
import modem
import generated/codec
import generated/router
import generated/transport
"/utf8>>/binary,
                                                                                                                                                                Rally_effect_import/binary>>/binary,
                                                                                                                                                            "@external(javascript, \"../generated/codec_ffi.mjs\", \"ensure_decoders\")
fn ensure_decoders() -> Nil
"/utf8>>/binary,
                                                                                                                                                        Page_imports/binary>>/binary,
                                                                                                                                                    Ctx_import/binary>>/binary,
                                                                                                                                                Layout_imports/binary>>/binary,
                                                                                                                                            "

"/utf8>>/binary,
                                                                                                                                        Page_model_type/binary>>/binary,
                                                                                                                                    "

"/utf8>>/binary,
                                                                                                                                Page_msg_type/binary>>/binary,
                                                                                                                            "

pub type Model {
  Model(
    route: router.Route,
    page_model: PageModel,
    connection: Connection,"/utf8>>/binary,
                                                                                                                        Ctx_field/binary>>/binary,
                                                                                                                    "
  )
}

pub type Connection {
  Connected
  Disconnected
  Reconnecting
}

pub type Msg {
  UrlChanged(router.Route)
  PageMsg(PageMsg)
  TransportConnected
  TransportDisconnected(reason: String)"/utf8>>/binary,
                                                                                                                Ctx_msg_variant/binary>>/binary,
                                                                                                            "
}

pub fn main() {
  let app = lustre.application(init, update, view)
  lustre.start(app, \"#app\", Nil)
}

fn init(_flags: Nil) -> #(Model, Effect(Msg)) {
  let _ = ensure_decoders()
  let route = router.parse_route_from_url()
"/utf8>>/binary,
                                                                                                        Ctx_init/binary>>/binary,
                                                                                                    "
}

fn init_transport() -> Effect(Msg) {
  effect.from(fn(dispatch) {
    let _ = transport.init(\"/ws\")
    let _ = transport.register_on_connect(fn() { dispatch(TransportConnected) })
    let _ = transport.register_on_disconnect(fn(reason) { dispatch(TransportDisconnected(reason)) })
"/utf8>>/binary,
                                                                                                Push_registrations/binary>>/binary,
                                                                                            Ctx_push_registration/binary>>/binary,
                                                                                        "
    Nil
  })
}

"/utf8>>/binary,
                                                                                    Init_page_fn/binary>>/binary,
                                                                                "

"/utf8>>/binary,
                                                                            Hydrate_page_fn/binary>>/binary,
                                                                        "

"/utf8>>/binary,
                                                                    Reinit_server_fn/binary>>/binary,
                                                                "

"/utf8>>/binary,
                                                            Update_page_fn/binary>>/binary,
                                                        "

"/utf8>>/binary,
                                                    Render_page_fn/binary>>/binary,
                                                "

fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
  case msg {
    UrlChanged(route) -> {
"/utf8>>/binary,
                                            Url_changed_body/binary>>/binary,
                                        "
    }
    PageMsg(page_msg) -> {
"/utf8>>/binary,
                                    Page_msg_body/binary>>/binary,
                                "
    }
    TransportConnected ->
      #(Model(..model, connection: Connected), reinit_server(model.route))
    TransportDisconnected(_reason) ->
      #(Model(..model, connection: Disconnected), effect.none())"/utf8>>/binary,
                            Ctx_update_arm/binary>>/binary,
                        "
  }
}

"/utf8>>/binary,
                    Wrap_layout/binary>>/binary,
                "
fn view(model: Model) -> Element(Msg) {
"/utf8>>/binary,
            View_body/binary>>/binary,
        "
}

fn connection_banner(connection: Connection) -> Element(Msg) {
  case connection {
    Connected -> html.text(\"\")
    Disconnected ->
      html.div(
        [attr.class(\"rally-banner rally-banner--disconnected\")],
        [html.text(\"Disconnected from server. Reconnecting...\")],
      )
    Reconnecting ->
      html.div(
        [attr.class(\"rally-banner rally-banner--reconnecting\")],
        [html.text(\"Reconnecting...\")],
      )
  }
}
"/utf8>>.

-file("src/rally/generator/client.gleam", 119).
-spec client_router(list(rally@types:scanned_route())) -> binary().
client_router(Routes) ->
    Server_router = rally@generator:generate(Routes),
    Client_fns = <<"

/// Read the browser's current location.
@external(javascript, \"./router_ffi.mjs\", \"currentUrl\")
fn current_url() -> String

/// Parse the current browser URL into a Route.
pub fn parse_route_from_url() -> Route {
  let url = current_url()
  let uri = case uri.parse(url) {
    Ok(u) -> u
    Error(_) -> {
      let assert Ok(fallback) = uri.parse(\"http://localhost/\")
      fallback
    }
  }
  parse_route(uri)
}
"/utf8>>,
    <<<<Server_router/binary, "\n"/utf8>>/binary, Client_fns/binary>>.

-file("src/rally/generator/client.gleam", 774).
-spec router_ffi_mjs() -> binary().
router_ffi_mjs() ->
    <<"// Generated by Rally — do not edit.
//
// Browser router FFI — reads the current URL.
// Used by the generated client router.gleam.

export function currentUrl() {
  return globalThis.location?.href ?? \"http://localhost/\";
}
"/utf8>>.

-file("src/rally/generator/client.gleam", 238).
-spec transport_gleam(binary()) -> binary().
transport_gleam(_) ->
    Protocol_wire = <<"\"./protocol_wire.mjs\""/utf8>>,
    Transport_ffi = <<"\"./transport_ffi.mjs\""/utf8>>,
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.
import gleam/dynamic.{type Dynamic}

pub type DecodeError {
  DecodeError(message: String)
}

/// Initialize the WebSocket connection. Call once on app startup.
@external(javascript, "/utf8,
                                                                                    Transport_ffi/binary>>/binary,
                                                                                ", \"ensureSocket\")
pub fn init(url: String) -> Nil

/// Send a message to the server. The protocol_wire facade encodes
/// the message according to the configured protocol (ETF binary or JSON).
@external(javascript, "/utf8>>/binary,
                                                                            Transport_ffi/binary>>/binary,
                                                                        ", \"send\")
fn send_raw(
  url: String,
  page: String,
  msg: a,
  callback: fn(b) -> Nil,
) -> Nil

/// Register a handler for push messages from the server.
/// The handler is called with the decoded push value whenever
/// the server sends a ToClient for this page.
@external(javascript, "/utf8>>/binary,
                                                                    Transport_ffi/binary>>/binary,
                                                                ", \"registerPushHandler\")
pub fn register_push_handler(
  page: String,
  handler: fn(Dynamic) -> Nil,
) -> Nil

/// Register a callback that fires when the WebSocket connects
/// (both initial connect and reconnects).
@external(javascript, "/utf8>>/binary,
                                                            Transport_ffi/binary>>/binary,
                                                        ", \"registerOnConnect\")
pub fn register_on_connect(callback: fn() -> Nil) -> Nil

/// Register a callback that fires when the WebSocket disconnects.
@external(javascript, "/utf8>>/binary,
                                                    Transport_ffi/binary>>/binary,
                                                ", \"registerOnDisconnect\")
pub fn register_on_disconnect(callback: fn(String) -> Nil) -> Nil

/// Register a handler that fires when an RPC fails at the framework
/// layer (dispatch errors, malformed requests, decode failures, or
/// connection loss while a call is in flight). Domain-level errors
/// returned by handlers flow through the per-call callback as usual.
@external(javascript, "/utf8>>/binary,
                                            Transport_ffi/binary>>/binary,
                                        ", \"registerRpcErrorHandler\")
pub fn register_rpc_error_handler(callback: fn(String) -> Nil) -> Nil

/// Send a ToServer message to the server.
/// Encodes the message and sends it over the WebSocket.
pub fn send_to_server(page: String, msg: a) -> Nil {
  send_raw(\"/ws\", page, msg, fn(_) { Nil })
}

/// Send an RPC call and invoke callback with the handler's return value.
/// Framework-level errors do not invoke this callback; they flow through
/// register_rpc_error_handler. User Msg types only need to handle whatever
/// shape the handler returns.
pub fn send_rpc(msg: a, callback: fn(b) -> Nil) -> Nil {
  send_raw(\"/ws\", \"rpc\", msg, callback)
}

/// Send route params to initialize the server-side page model.
/// Uses request_id 0 as the init sentinel.
pub fn send_page_init(page: String, params: a) -> Nil {
  send_page_init_raw(\"/ws\", page, params)
}

@external(javascript, "/utf8>>/binary,
                                    Transport_ffi/binary>>/binary,
                                ", \"send_page_init\")
fn send_page_init_raw(url: String, page: String, params: a) -> Nil

/// Read SSR flags embedded in the page by the server.
/// Returns empty string if not present.
@external(javascript, "/utf8>>/binary,
                            Transport_ffi/binary>>/binary,
                        ", \"read_flags\")
pub fn read_flags() -> String

/// Read the server-provided ClientContext from SSR flags.
/// Returns empty string if not present.
@external(javascript, "/utf8>>/binary,
                    Transport_ffi/binary>>/binary,
                ", \"read_client_context\")
pub fn read_client_context() -> String

/// Type-level identity cast. The JS runtime representation is unchanged;
/// this lets generated code bridge between Dynamic/generic and concrete types
/// where the value is already the correct shape (decoded ETF, SSR flags).
@external(javascript, "/utf8>>/binary,
            Protocol_wire/binary>>/binary,
        ", \"identity\")
pub fn coerce(value: a) -> b
"/utf8>>.

-file("src/rally/generator/client.gleam", 231).
-spec client_path(binary(), binary()) -> binary().
client_path(Path, Prefix) ->
    case gleam_stdlib:string_starts_with(Path, <<"/"/utf8>>) of
        true ->
            Path;

        _ ->
            <<Prefix/binary, Path/binary>>
    end.

-file("src/rally/generator/client.gleam", 202).
-spec format_dep(binary(), tom:toml(), binary()) -> binary().
format_dep(Name, Value, Prefix) ->
    case Value of
        {string, Version} ->
            <<<<<<Name/binary, " = \""/utf8>>/binary, Version/binary>>/binary,
                "\"\n"/utf8>>;

        {inline_table, Table} ->
            case gleam_stdlib:map_get(Table, <<"path"/utf8>>) of
                {ok, {string, Path}} ->
                    <<<<<<Name/binary, " = { path = \""/utf8>>/binary,
                            (client_path(Path, Prefix))/binary>>/binary,
                        "\" }\n"/utf8>>;

                _ ->
                    Entries = begin
                        _pipe = maps:to_list(Table),
                        _pipe@1 = gleam@list:map(
                            _pipe,
                            fun(Pair) -> case erlang:element(2, Pair) of
                                    {string, S} ->
                                        <<<<<<(erlang:element(1, Pair))/binary,
                                                    " = \""/utf8>>/binary,
                                                S/binary>>/binary,
                                            "\""/utf8>>;

                                    _ ->
                                        <<(erlang:element(1, Pair))/binary,
                                            " = \"???\""/utf8>>
                                end end
                        ),
                        gleam@string:join(_pipe@1, <<", "/utf8>>)
                    end,
                    <<<<<<Name/binary, " = { "/utf8>>/binary, Entries/binary>>/binary,
                        " }\n"/utf8>>
            end;

        {table, Table} ->
            case gleam_stdlib:map_get(Table, <<"path"/utf8>>) of
                {ok, {string, Path}} ->
                    <<<<<<Name/binary, " = { path = \""/utf8>>/binary,
                            (client_path(Path, Prefix))/binary>>/binary,
                        "\" }\n"/utf8>>;

                _ ->
                    Entries = begin
                        _pipe = maps:to_list(Table),
                        _pipe@1 = gleam@list:map(
                            _pipe,
                            fun(Pair) -> case erlang:element(2, Pair) of
                                    {string, S} ->
                                        <<<<<<(erlang:element(1, Pair))/binary,
                                                    " = \""/utf8>>/binary,
                                                S/binary>>/binary,
                                            "\""/utf8>>;

                                    _ ->
                                        <<(erlang:element(1, Pair))/binary,
                                            " = \"???\""/utf8>>
                                end end
                        ),
                        gleam@string:join(_pipe@1, <<", "/utf8>>)
                    end,
                    <<<<<<Name/binary, " = { "/utf8>>/binary, Entries/binary>>/binary,
                        " }\n"/utf8>>
            end;

        _ ->
            <<""/utf8>>
    end.

-file("src/rally/generator/client.gleam", 185).
-spec is_server_runtime_dep(binary()) -> boolean().
is_server_runtime_dep(Name) ->
    gleam@list:contains(
        [<<"envoy"/utf8>>,
            <<"gleam_erlang"/utf8>>,
            <<"gleam_http"/utf8>>,
            <<"gleam_time"/utf8>>,
            <<"global_value"/utf8>>,
            <<"logging"/utf8>>,
            <<"mist"/utf8>>,
            <<"simplifile"/utf8>>,
            <<"sqlight"/utf8>>],
        Name
    ).

-file("src/rally/generator/client.gleam", 144).
-spec client_gleam_toml(
    gleam@dict:dict(binary(), tom:toml()),
    binary(),
    binary()
) -> binary().
client_gleam_toml(Server_deps, Client_root, Protocol) ->
    Header = <<"name = \"client\"\nversion = \"0.1.0\"\ntarget = \"javascript\"\n\n[dependencies]\ngleam_stdlib = \">= 0.60.0 and < 2.0.0\"\nlustre = \">= 5.6.0 and < 7.0.0\"\nmodem = \">= 2.0.0 and < 3.0.0\"\n"/utf8>>,
    Depth = erlang:length(gleam@string:split(Client_root, <<"/"/utf8>>)),
    Prefix = gleam@string:repeat(<<"../"/utf8>>, Depth),
    Json_deps = case Protocol of
        <<"json"/utf8>> ->
            <<"gleam_json = \">= 3.1.0 and < 4.0.0\"\nlibero = \">= 6.0.0 and < 7.0.0\"\n"/utf8>>;

        _ ->
            <<""/utf8>>
    end,
    Baseline = gleam@set:from_list(
        [<<"gleam_stdlib"/utf8>>, <<"lustre"/utf8>>, <<"modem"/utf8>>]
    ),
    Extra_deps = begin
        _pipe = Server_deps,
        _pipe@1 = maps:to_list(_pipe),
        _pipe@2 = gleam@list:filter(
            _pipe@1,
            fun(Pair) ->
                not gleam@set:contains(Baseline, erlang:element(1, Pair))
            end
        ),
        _pipe@3 = gleam@list:filter(
            _pipe@2,
            fun(Pair@1) ->
                (erlang:element(1, Pair@1) /= <<"rally"/utf8>>) andalso (erlang:element(
                    1,
                    Pair@1
                )
                /= <<"marmot"/utf8>>)
            end
        ),
        _pipe@4 = gleam@list:filter(_pipe@3, fun(Pair@2) -> case Protocol of
                    <<"json"/utf8>> ->
                        (erlang:element(1, Pair@2) /= <<"libero"/utf8>>) andalso (erlang:element(
                            1,
                            Pair@2
                        )
                        /= <<"gleam_json"/utf8>>);

                    _ ->
                        true
                end end),
        _pipe@5 = gleam@list:filter(
            _pipe@4,
            fun(Pair@3) ->
                not is_server_runtime_dep(erlang:element(1, Pair@3))
            end
        ),
        _pipe@6 = gleam@list:sort(
            _pipe@5,
            fun(A, B) ->
                gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
            end
        ),
        _pipe@7 = gleam@list:map(
            _pipe@6,
            fun(Pair@4) ->
                format_dep(
                    erlang:element(1, Pair@4),
                    erlang:element(2, Pair@4),
                    Prefix
                )
            end
        ),
        gleam@string:join(_pipe@7, <<""/utf8>>)
    end,
    <<<<Header/binary, Json_deps/binary>>/binary, Extra_deps/binary>>.

-file("src/rally/generator/client.gleam", 49).
-spec generate_package_with_client_context_contract(
    list(rally@types:scanned_route()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    rally@types:scan_config(),
    gleam@dict:dict(binary(), tom:toml()),
    binary(),
    gleam@option:option(rally@types:client_context_contract()),
    binary(),
    binary()
) -> list(generated_file()).
generate_package_with_client_context_contract(
    Routes,
    Contracts,
    Config,
    Server_deps,
    Transport_ffi_content,
    Client_context_contract,
    Client_context_module,
    Protocol
) ->
    [{generated_file,
            <<(erlang:element(13, Config))/binary, "/gleam.toml"/utf8>>,
            client_gleam_toml(Server_deps, erlang:element(13, Config), Protocol)},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/transport_ffi.mjs"/utf8>>,
            Transport_ffi_content},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/transport.gleam"/utf8>>,
            transport_gleam(Protocol)},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/router_ffi.mjs"/utf8>>,
            router_ffi_mjs()},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/router.gleam"/utf8>>,
            client_router(Routes)},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/app.gleam"/utf8>>,
            app_gleam(
                Routes,
                Contracts,
                Client_context_contract,
                Client_context_module
            )},
        {generated_file,
            <<(erlang:element(13, Config))/binary,
                "/src/generated/config.mjs"/utf8>>,
            config_mjs(Protocol)}].

-file("src/rally/generator/client.gleam", 26).
-spec generate_package(
    list(rally@types:scanned_route()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    rally@types:scan_config(),
    gleam@dict:dict(binary(), tom:toml()),
    binary(),
    boolean()
) -> list(generated_file()).
generate_package(
    Routes,
    Contracts,
    Config,
    Server_deps,
    Transport_ffi_content,
    Has_client_context
) ->
    generate_package_with_client_context_contract(
        Routes,
        Contracts,
        Config,
        Server_deps,
        Transport_ffi_content,
        case Has_client_context of
            true ->
                {some, empty_client_context_contract()};

            false ->
                none
        end,
        <<"client_context"/utf8>>,
        erlang:element(18, Config)
    ).