src/rally@generator.erl

-module(rally@generator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/generator.gleam").
-export([generate/1, generate_dispatch/5, generate_empty_rpc_dispatch/2, normalize_rpc_dispatch_context_import/1, normalize_rpc_dispatch_unused_fields/1, generate_protocol_wire/7, generate_protocol_wire_js/2]).

-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(
    " Route type, parse function, path builder, and page dispatch codegen.\n"
    "\n"
    " Generates router.gleam (Route type, parse_route, route_to_path, href),\n"
    " page_dispatch.gleam (PageModel/PageMsg unions, per-route init/update/view),\n"
    " protocol_wire.gleam facade, and RPC dispatch scaffolding.\n"
).

-file("src/rally/generator.gleam", 373).
-spec generate_href() -> binary().
generate_href() ->
    <<"pub fn href(route route: Route) -> Attribute(msg) {\n  attribute.href(route_to_path(route: route))\n}"/utf8>>.

-file("src/rally/generator.gleam", 333).
-spec merge_static_segments(
    list(rally@types:url_segment()),
    binary(),
    list(binary())
) -> list(binary()).
merge_static_segments(Segments, Buf, Acc) ->
    case Segments of
        [] ->
            case Buf of
                <<""/utf8>> ->
                    lists:reverse(Acc);

                _ ->
                    lists:reverse(
                        [<<<<"\""/utf8, Buf/binary>>/binary, "\""/utf8>> | Acc]
                    )
            end;

        [{static_segment, Name} | Rest] ->
            merge_static_segments(
                Rest,
                <<<<Buf/binary, "/"/utf8>>/binary, Name/binary>>,
                Acc
            );

        [{dynamic_segment, Param_name, int_param} | Rest@1] ->
            Acc@1 = case Buf of
                <<""/utf8>> ->
                    [<<"\"/\""/utf8>> | Acc];

                _ ->
                    [<<<<"\""/utf8, Buf/binary>>/binary, "/\""/utf8>> | Acc]
            end,
            merge_static_segments(
                Rest@1,
                <<""/utf8>>,
                [<<<<"int.to_string("/utf8, Param_name/binary>>/binary,
                        ")"/utf8>> |
                    Acc@1]
            );

        [{dynamic_segment, Param_name@1, _} | Rest@2] ->
            Acc@2 = case Buf of
                <<""/utf8>> ->
                    [<<"\"/\""/utf8>> | Acc];

                _ ->
                    [<<<<"\""/utf8, Buf/binary>>/binary, "/\""/utf8>> | Acc]
            end,
            merge_static_segments(
                Rest@2,
                <<""/utf8>>,
                [<<<<"uri.percent_encode("/utf8, Param_name@1/binary>>/binary,
                        ")"/utf8>> |
                    Acc@2]
            )
    end.

-file("src/rally/generator.gleam", 322).
-spec build_path_expr(list(rally@types:url_segment())) -> binary().
build_path_expr(Segments) ->
    case Segments of
        [] ->
            <<"\"/\""/utf8>>;

        _ ->
            gleam@string:join(
                merge_static_segments(Segments, <<""/utf8>>, []),
                <<" <> "/utf8>>
            )
    end.

-file("src/rally/generator.gleam", 240).
?DOC(" Build the constructor expression using param names directly (shorthand label syntax).\n").
-spec build_constructor(binary(), list({binary(), rally@types:param_type()})) -> binary().
build_constructor(Variant_name, Params) ->
    case Params of
        [] ->
            Variant_name;

        _ ->
            Fields = begin
                _pipe = gleam@list:map(
                    Params,
                    fun(P) ->
                        {Name, _} = P,
                        <<Name/binary, ":"/utf8>>
                    end
                ),
                gleam@string:join(_pipe, <<", "/utf8>>)
            end,
            <<<<<<Variant_name/binary, "("/utf8>>/binary, Fields/binary>>/binary,
                ")"/utf8>>
    end.

-file("src/rally/generator.gleam", 309).
-spec route_to_path_arm(rally@types:scanned_route()) -> binary().
route_to_path_arm(Route) ->
    {scanned_route, Segments, Variant_name, Params, _, _} = Route,
    Destructor = build_constructor(Variant_name, Params),
    Path_expr = build_path_expr(Segments),
    <<<<<<"    "/utf8, Destructor/binary>>/binary, " -> "/utf8>>/binary,
        Path_expr/binary>>.

-file("src/rally/generator.gleam", 299).
-spec generate_route_to_path(list(rally@types:scanned_route())) -> binary().
generate_route_to_path(Routes) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:map(_pipe, fun route_to_path_arm/1),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    <<<<"pub fn route_to_path(route route: Route) -> String {\n  case route {\n"/utf8,
            Arms/binary>>/binary,
        "\n    NotFound(uri:) -> uri.to_string(uri)\n  }\n}"/utf8>>.

-file("src/rally/generator.gleam", 259).
?DOC(" Build the constructor expression substituting int params with their _val names.\n").
-spec build_constructor_with_vals(
    binary(),
    list({binary(), rally@types:param_type()}),
    list({binary(), binary()})
) -> binary().
build_constructor_with_vals(Variant_name, Params, Val_pairs) ->
    case Params of
        [] ->
            Variant_name;

        _ ->
            Fields = begin
                _pipe = gleam@list:map(
                    Params,
                    fun(P) ->
                        {Name, Param_type} = P,
                        case Param_type of
                            string_param ->
                                <<<<<<<<<<Name/binary,
                                                    ": result.unwrap(uri.percent_decode("/utf8>>/binary,
                                                Name/binary>>/binary,
                                            "), "/utf8>>/binary,
                                        Name/binary>>/binary,
                                    ")"/utf8>>;

                            int_param ->
                                Val_name = case gleam@list:find(
                                    Val_pairs,
                                    fun(Vp) ->
                                        erlang:element(1, Vp) =:= Name
                                    end
                                ) of
                                    {ok, {_, V}} ->
                                        V;

                                    {error, nil} ->
                                        Name
                                end,
                                <<<<Name/binary, ": "/utf8>>/binary,
                                    Val_name/binary>>
                        end
                    end
                ),
                gleam@string:join(_pipe, <<", "/utf8>>)
            end,
            <<<<<<Variant_name/binary, "("/utf8>>/binary, Fields/binary>>/binary,
                ")"/utf8>>
    end.

-file("src/rally/generator.gleam", 227).
?DOC(" Build the list pattern for a route's segments, prefixed with [_lang, \"admin\"].\n").
-spec build_pattern(list(rally@types:url_segment())) -> binary().
build_pattern(Segments) ->
    Seg_parts = gleam@list:map(Segments, fun(Seg) -> case Seg of
                {static_segment, Name} ->
                    <<<<"\""/utf8, Name/binary>>/binary, "\""/utf8>>;

                {dynamic_segment, Param_name, _} ->
                    Param_name
            end end),
    All_parts = Seg_parts,
    <<<<"["/utf8, (gleam@string:join(All_parts, <<", "/utf8>>))/binary>>/binary,
        "]"/utf8>>.

-file("src/rally/generator.gleam", 141).
-spec parse_route_arm(rally@types:scanned_route()) -> binary().
parse_route_arm(Route) ->
    {scanned_route, Segments, Variant_name, Params, _, _} = Route,
    Pattern = build_pattern(Segments),
    Int_params = gleam@list:filter(
        Params,
        fun(P) ->
            {_, Pt} = P,
            Pt =:= int_param
        end
    ),
    case Int_params of
        [] ->
            String_params = gleam@list:filter(
                Params,
                fun(P@1) ->
                    {_, Pt@1} = P@1,
                    Pt@1 =:= string_param
                end
            ),
            Constructor = case String_params of
                [] ->
                    build_constructor(Variant_name, Params);

                _ ->
                    build_constructor_with_vals(Variant_name, Params, [])
            end,
            <<<<<<"    "/utf8, Pattern/binary>>/binary, " -> "/utf8>>/binary,
                Constructor/binary>>;

        [Single] ->
            {Name, _} = Single,
            Val_name = <<Name/binary, "_val"/utf8>>,
            Constructor@1 = build_constructor_with_vals(
                Variant_name,
                Params,
                [{Name, Val_name}]
            ),
            <<<<<<<<<<<<<<<<"    "/utf8, Pattern/binary>>/binary,
                                        " ->\n      case int.parse("/utf8>>/binary,
                                    Name/binary>>/binary,
                                ") {\n        Ok("/utf8>>/binary,
                            Val_name/binary>>/binary,
                        ") -> "/utf8>>/binary,
                    Constructor@1/binary>>/binary,
                "\n        Error(_) -> NotFound(uri:)\n      }"/utf8>>;

        Multiple ->
            Parse_exprs = begin
                _pipe = gleam@list:map(
                    Multiple,
                    fun(P@2) ->
                        {Name@1, _} = P@2,
                        <<<<"int.parse("/utf8, Name@1/binary>>/binary,
                            ")"/utf8>>
                    end
                ),
                gleam@string:join(_pipe, <<", "/utf8>>)
            end,
            Ok_patterns = begin
                _pipe@1 = gleam@list:map(
                    Multiple,
                    fun(P@3) ->
                        {Name@2, _} = P@3,
                        <<<<"Ok("/utf8, Name@2/binary>>/binary, "_val)"/utf8>>
                    end
                ),
                gleam@string:join(_pipe@1, <<", "/utf8>>)
            end,
            Val_pairs = gleam@list:map(
                Multiple,
                fun(P@4) ->
                    {Name@3, _} = P@4,
                    {Name@3, <<Name@3/binary, "_val"/utf8>>}
                end
            ),
            Constructor@2 = build_constructor_with_vals(
                Variant_name,
                Params,
                Val_pairs
            ),
            <<<<<<<<<<<<<<<<<<<<"    "/utf8, Pattern/binary>>/binary,
                                                " ->\n      case "/utf8>>/binary,
                                            Parse_exprs/binary>>/binary,
                                        " {\n        "/utf8>>/binary,
                                    Ok_patterns/binary>>/binary,
                                " -> "/utf8>>/binary,
                            Constructor@2/binary>>/binary,
                        "\n        "/utf8>>/binary,
                    (gleam@string:join(
                        gleam@list:map(Multiple, fun(_) -> <<"_"/utf8>> end),
                        <<", "/utf8>>
                    ))/binary>>/binary,
                " -> NotFound(uri:)\n      }"/utf8>>
    end.

-file("src/rally/generator.gleam", 131).
-spec generate_parse_route(list(rally@types:scanned_route())) -> binary().
generate_parse_route(Routes) ->
    Arms = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:map(_pipe, fun parse_route_arm/1),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    <<<<"pub fn parse_route(uri: Uri) -> Route {\n  case uri.path_segments(uri.path) {\n"/utf8,
            Arms/binary>>/binary,
        "\n    _ -> NotFound(uri:)\n  }\n}"/utf8>>.

-file("src/rally/generator.gleam", 120).
-spec param_type_to_gleam(rally@types:param_type()) -> binary().
param_type_to_gleam(Param_type) ->
    case Param_type of
        int_param ->
            <<"Int"/utf8>>;

        string_param ->
            <<"String"/utf8>>
    end.

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

        Params ->
            Fields = begin
                _pipe = gleam@list:map(
                    Params,
                    fun(P) ->
                        {Name, Param_type} = P,
                        <<<<Name/binary, ": "/utf8>>/binary,
                            (param_type_to_gleam(Param_type))/binary>>
                    end
                ),
                gleam@string:join(_pipe, <<", "/utf8>>)
            end,
            <<<<<<<<"  "/utf8, (erlang:element(3, Route))/binary>>/binary,
                        "("/utf8>>/binary,
                    Fields/binary>>/binary,
                ")"/utf8>>
    end.

-file("src/rally/generator.gleam", 96).
-spec generate_route_type(list(rally@types:scanned_route())) -> binary().
generate_route_type(Routes) ->
    Variants = begin
        _pipe = Routes,
        _pipe@1 = gleam@list:map(_pipe, fun route_variant_line/1),
        _pipe@2 = lists:append(_pipe@1, [<<"  NotFound(uri: Uri)"/utf8>>]),
        gleam@string:join(_pipe@2, <<"\n"/utf8>>)
    end,
    <<<<"pub type Route {\n"/utf8, Variants/binary>>/binary, "\n}"/utf8>>.

-file("src/rally/generator.gleam", 88).
-spec has_string_params(list(rally@types:scanned_route())) -> boolean().
has_string_params(Routes) ->
    gleam@list:any(
        Routes,
        fun(R) ->
            gleam@list:any(
                erlang:element(4, R),
                fun(P) -> erlang:element(2, P) =:= string_param end
            )
        end
    ).

-file("src/rally/generator.gleam", 84).
-spec has_int_params(list(rally@types:scanned_route())) -> boolean().
has_int_params(Routes) ->
    gleam@list:any(
        Routes,
        fun(R) ->
            gleam@list:any(
                erlang:element(4, R),
                fun(P) -> erlang:element(2, P) =:= int_param end
            )
        end
    ).

-file("src/rally/generator.gleam", 67).
-spec file_header(list(rally@types:scanned_route())) -> binary().
file_header(Routes) ->
    Int_import = case has_int_params(Routes) of
        true ->
            <<"import gleam/int\n"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Result_import = case has_string_params(Routes) of
        true ->
            <<"import gleam/result\n"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    Uri_import = <<"import gleam/uri.{type Uri}\n"/utf8>>,
    <<<<<<<<"// Generated by Rally — do not edit.\n\n"/utf8, Int_import/binary>>/binary,
                Result_import/binary>>/binary,
            Uri_import/binary>>/binary,
        "import lustre/attribute.{type Attribute}"/utf8>>.

-file("src/rally/generator.gleam", 54).
-spec compare_segments(rally@types:url_segment(), rally@types:url_segment()) -> gleam@order:order().
compare_segments(A, B) ->
    case {A, B} of
        {{static_segment, _}, {dynamic_segment, _, _}} ->
            lt;

        {{dynamic_segment, _, _}, {static_segment, _}} ->
            gt;

        {{static_segment, An}, {static_segment, Bn}} ->
            gleam@string:compare(An, Bn);

        {{dynamic_segment, An@1, _}, {dynamic_segment, Bn@1, _}} ->
            gleam@string:compare(An@1, Bn@1)
    end.

-file("src/rally/generator.gleam", 41).
-spec compare_routes(
    list(rally@types:url_segment()),
    list(rally@types:url_segment())
) -> gleam@order:order().
compare_routes(A, B) ->
    case {A, B} of
        {[], []} ->
            eq;

        {[], _} ->
            lt;

        {_, []} ->
            gt;

        {[Ah | At], [Bh | Bt]} ->
            case compare_segments(Ah, Bh) of
                eq ->
                    compare_routes(At, Bt);

                Other ->
                    Other
            end
    end.

-file("src/rally/generator.gleam", 37).
?DOC(
    " Sort routes so that static patterns come before dynamic patterns at each\n"
    " level of nesting. Within static groups, sort alphabetically.\n"
).
-spec sort_routes(list(rally@types:scanned_route())) -> list(rally@types:scanned_route()).
sort_routes(Routes) ->
    gleam@list:sort(
        Routes,
        fun(A, B) ->
            compare_routes(erlang:element(2, A), erlang:element(2, B))
        end
    ).

-file("src/rally/generator.gleam", 21).
?DOC(" Generate a complete Gleam source file from a list of scanned routes.\n").
-spec generate(list(rally@types:scanned_route())) -> binary().
generate(Routes) ->
    Sorted = sort_routes(Routes),
    Header = file_header(Routes),
    Route_type = generate_route_type(Sorted),
    Parse_fn = generate_parse_route(Sorted),
    Path_fn = generate_route_to_path(Sorted),
    Href_fn = generate_href(),
    <<(gleam@string:join(
            [Header, Route_type, Parse_fn, Path_fn, Href_fn],
            <<"\n\n"/utf8>>
        ))/binary,
        "\n"/utf8>>.

-file("src/rally/generator.gleam", 679).
-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.gleam", 616).
-spec dispatch_view_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
dispatch_view_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),
                        View_args = case Has_client_context of
                            true ->
                                <<"(client_context, model)"/utf8>>;

                            false ->
                                <<"(model)"/utf8>>
                        end,
                        {ok,
                            <<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                (erlang:element(
                                                                    3,
                                                                    Route
                                                                ))/binary>>/binary,
                                                            "PageModel(model) ->\n"/utf8>>/binary,
                                                        "      element.map("/utf8>>/binary,
                                                    Alias/binary>>/binary,
                                                ".view"/utf8>>/binary,
                                            View_args/binary>>/binary,
                                        ", "/utf8>>/binary,
                                    (erlang:element(3, Route))/binary>>/binary,
                                "PageMsg)"/utf8>>};

                    _ ->
                        {error, nil}
                end
            end
        ),
        gleam@string:join(_pipe@1, <<"\n"/utf8>>)
    end,
    Signature = case Has_client_context of
        true ->
            <<"pub fn view_page(page_model page_model: PageModel, client_context client_context: client_context.ClientContext) -> Element(PageMsg) {"/utf8>>;

        false ->
            <<"pub fn view_page(page_model: PageModel) -> Element(PageMsg) {"/utf8>>
    end,
    <<<<<<Signature/binary, "\n  case page_model {\n"/utf8>>/binary,
            Arms/binary>>/binary,
        "\n    NoPageModel -> html.div([], [html.text(\"Page not found\")])\n  }\n}"/utf8>>.

-file("src/rally/generator.gleam", 529).
-spec dispatch_update_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
dispatch_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(model), "/utf8>>/binary,
                                                                                Vn/binary>>/binary,
                                                                            "PageMsg(msg) -> {\n"/utf8>>/binary,
                                                                        "      let #(new_model, effects, context_msg) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".update(client_context, model, msg)\n"/utf8>>/binary,
                                                            "      #("/utf8>>/binary,
                                                        Vn/binary>>/binary,
                                                    "PageModel(new_model), effect.map(effects, "/utf8>>/binary,
                                                Vn/binary>>/binary,
                                            "PageMsg), context_msg)\n"/utf8>>/binary,
                                        "    }"/utf8>>};

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

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

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

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

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

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

-file("src/rally/generator.gleam", 673).
-spec dispatch_route_param_args(rally@types:scanned_route()) -> binary().
dispatch_route_param_args(Route) ->
    _pipe = erlang:element(4, Route),
    _pipe@1 = gleam@list:map(
        _pipe,
        fun(P) -> <<", "/utf8, (erlang:element(1, P))/binary>> end
    ),
    gleam@string:join(_pipe@1, <<""/utf8>>).

-file("src/rally/generator.gleam", 477).
-spec dispatch_init_page(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract()),
    boolean()
) -> binary().
dispatch_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) andalso erlang:element(
                        5,
                        Contract
                    ) ->
                        Alias = page_module_alias(Route),
                        Param_args = dispatch_route_param_args(Route),
                        Call_args = case {Has_client_context, Param_args} of
                            {true, _} ->
                                <<<<"(client_context"/utf8, Param_args/binary>>/binary,
                                    ")"/utf8>>;

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

                            {false, _} ->
                                <<<<"("/utf8,
                                        (gleam@string:drop_start(Param_args, 2))/binary>>/binary,
                                    ")"/utf8>>
                        end,
                        {ok,
                            <<<<<<<<<<<<<<<<<<<<<<<<<<"    "/utf8,
                                                                                (dispatch_route_pattern(
                                                                                    Route
                                                                                ))/binary>>/binary,
                                                                            " -> {\n"/utf8>>/binary,
                                                                        "      let #(model, effects) = "/utf8>>/binary,
                                                                    Alias/binary>>/binary,
                                                                ".init"/utf8>>/binary,
                                                            Call_args/binary>>/binary,
                                                        "\n"/utf8>>/binary,
                                                    "      #("/utf8>>/binary,
                                                (erlang:element(3, Route))/binary>>/binary,
                                            "PageModel(model), effect.map(effects, "/utf8>>/binary,
                                        (erlang:element(3, Route))/binary>>/binary,
                                    "PageMsg))\n"/utf8>>/binary,
                                "    }"/utf8>>};

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

        false ->
            <<"pub fn init_page(route: router.Route) -> #(PageModel, Effect(PageMsg)) {"/utf8>>
    end,
    <<<<<<Signature/binary, "\n  case route {\n"/utf8>>/binary, Arms/binary>>/binary,
        "\n    _ -> #(NoPageModel, effect.none())\n  }\n}"/utf8>>.

-file("src/rally/generator.gleam", 458).
-spec dispatch_page_msg_type(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
dispatch_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  NoPageMsg\n}"/utf8>>.

-file("src/rally/generator.gleam", 439).
-spec dispatch_page_model_type(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
dispatch_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.gleam", 432).
-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.gleam", 425).
-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.gleam", 683).
-spec dispatch_page_imports(
    list(rally@types:scanned_route()),
    gleam@dict:dict(binary(), rally@types:page_contract())
) -> binary().
dispatch_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) ->
                    {ok,
                        <<<<<<"import "/utf8,
                                    (erlang:element(5, Route))/binary>>/binary,
                                " as "/utf8>>/binary,
                            (page_module_alias(Route))/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.gleam", 382).
?DOC(" Generate a complete page_dispatch.gleam source file from scanned routes.\n").
-spec generate_dispatch(
    list(rally@types:scanned_route()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    boolean(),
    binary(),
    binary()
) -> binary().
generate_dispatch(
    Routes,
    Contracts,
    Has_client_context,
    Router_module,
    Client_context_module
) ->
    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,
    Page_imports = dispatch_page_imports(Routes, Contract_map),
    Ctx_import = case Has_client_context of
        true ->
            <<"\n"/utf8,
                (import_as(Client_context_module, <<"client_context"/utf8>>))/binary>>;

        false ->
            <<""/utf8>>
    end,
    Option_import = case Has_client_context of
        true ->
            <<"import gleam/option.{type Option, None}\n"/utf8>>;

        false ->
            <<""/utf8>>
    end,
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.\n\n"/utf8,
                                                                    (import_as(
                                                                        Router_module,
                                                                        <<"router"/utf8>>
                                                                    ))/binary>>/binary,
                                                                "\n"/utf8>>/binary,
                                                            Option_import/binary>>/binary,
                                                        "import lustre/effect.{type Effect}
import lustre/element.{type Element}
import lustre/element/html
"/utf8>>/binary,
                                                    Page_imports/binary>>/binary,
                                                Ctx_import/binary>>/binary,
                                            "\n\n"/utf8>>/binary,
                                        (dispatch_page_model_type(
                                            Routes,
                                            Contract_map
                                        ))/binary>>/binary,
                                    "\n\n"/utf8>>/binary,
                                (dispatch_page_msg_type(Routes, Contract_map))/binary>>/binary,
                            "\n\n"/utf8>>/binary,
                        (dispatch_init_page(
                            Routes,
                            Contract_map,
                            Has_client_context
                        ))/binary>>/binary,
                    "\n\n"/utf8>>/binary,
                (dispatch_update_page(Routes, Contract_map, Has_client_context))/binary>>/binary,
            "\n\n"/utf8>>/binary,
        (dispatch_view_page(Routes, Contract_map, Has_client_context))/binary>>.

-file("src/rally/generator.gleam", 700).
-spec generate_empty_rpc_dispatch(
    binary(),
    list(libero@codegen_dispatch:extra_param())
) -> binary().
generate_empty_rpc_dispatch(Atoms_module, Extra_params) ->
    Extra_imports = begin
        _pipe = gleam@list:filter_map(
            Extra_params,
            fun(P) -> case erlang:element(4, P) of
                    <<""/utf8>> ->
                        {error, nil};

                    Line ->
                        {ok, Line}
                end end
        ),
        _pipe@1 = gleam@string:join(_pipe, <<"\n"/utf8>>),
        (fun(S) -> case S of
                <<""/utf8>> ->
                    <<""/utf8>>;

                _ ->
                    <<S/binary, "\n"/utf8>>
            end end)(_pipe@1)
    end,
    Extra_handle_params = begin
        _pipe@2 = gleam@list:map(
            Extra_params,
            fun(P@1) ->
                <<<<<<<<<<<<"\n  "/utf8, (erlang:element(2, P@1))/binary>>/binary,
                                    " _"/utf8>>/binary,
                                (erlang:element(2, P@1))/binary>>/binary,
                            ": "/utf8>>/binary,
                        (erlang:element(3, P@1))/binary>>/binary,
                    ","/utf8>>
            end
        ),
        gleam@string:join(_pipe@2, <<""/utf8>>)
    end,
    <<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT.

import libero/error.{MalformedRequest, UnknownFunction}
import libero/wire
import server_context.{type ServerContext}
"/utf8,
                            Extra_imports/binary>>/binary,
                        "
@external(erlang, \""/utf8>>/binary,
                    Atoms_module/binary>>/binary,
                "\", \"ensure\")
fn ensure_atoms() -> Nil

pub type ClientMsg {
  NoClientMessages
}

pub fn handle(
  server_context server_context: ServerContext,
  data data: BitArray,"/utf8>>/binary,
            Extra_handle_params/binary>>/binary,
        "
) -> #(BitArray, ServerContext) {
  ensure_atoms()
  case wire.decode_request(data) {
    Ok(#(\"rpc\", request_id, _msg)) ->
      #(wire.encode_response(request_id:, value: Error(UnknownFunction(\"rpc\"))), server_context)
    Ok(#(name, request_id, _)) ->
      #(wire.encode_response(request_id:, value: Error(UnknownFunction(name))), server_context)
    Error(_) ->
      #(wire.encode_response(request_id: 0, value: Error(MalformedRequest)), server_context)
  }
}
"/utf8>>.

-file("src/rally/generator.gleam", 755).
-spec normalize_rpc_dispatch_context_import(binary()) -> binary().
normalize_rpc_dispatch_context_import(Source) ->
    gleam@string:replace(
        Source,
        <<"import server/server_context.{type ServerContext}"/utf8>>,
        <<"import server_context.{type ServerContext}"/utf8>>
    ).

-file("src/rally/generator.gleam", 803).
-spec underscore_field_shorthand(binary()) -> binary().
underscore_field_shorthand(Field) ->
    Trimmed = gleam@string:trim(Field),
    case gleam_stdlib:string_ends_with(Trimmed, <<":"/utf8>>) of
        true ->
            <<Field/binary, " _"/utf8>>;

        false ->
            Field
    end.

-file("src/rally/generator.gleam", 770).
-spec underscore_unused_dispatch_fields(binary()) -> binary().
underscore_unused_dispatch_fields(Line) ->
    case gleam@string:split_once(Line, <<" -> {"/utf8>>) of
        {ok, {Before_arrow, After_arrow}} ->
            case gleam@string:split_once(Before_arrow, <<"("/utf8>>) of
                {ok, {Before_open, After_open}} ->
                    case gleam@string:split_once(After_open, <<")"/utf8>>) of
                        {ok, {Fields, <<""/utf8>>}} ->
                            case gleam_stdlib:contains_string(
                                Fields,
                                <<":"/utf8>>
                            ) of
                                true ->
                                    Underscored_fields = begin
                                        _pipe = Fields,
                                        _pipe@1 = gleam@string:split(
                                            _pipe,
                                            <<","/utf8>>
                                        ),
                                        _pipe@2 = gleam@list:map(
                                            _pipe@1,
                                            fun underscore_field_shorthand/1
                                        ),
                                        gleam@string:join(_pipe@2, <<","/utf8>>)
                                    end,
                                    <<<<<<<<Before_open/binary, "("/utf8>>/binary,
                                                Underscored_fields/binary>>/binary,
                                            ") -> {"/utf8>>/binary,
                                        After_arrow/binary>>;

                                false ->
                                    Line
                            end;

                        _ ->
                            Line
                    end;

                _ ->
                    Line
            end;

        _ ->
            Line
    end.

-file("src/rally/generator.gleam", 763).
-spec normalize_rpc_dispatch_unused_fields(binary()) -> binary().
normalize_rpc_dispatch_unused_fields(Source) ->
    _pipe = Source,
    _pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
    _pipe@2 = gleam@list:map(_pipe@1, fun underscore_unused_dispatch_fields/1),
    gleam@string:join(_pipe@2, <<"\n"/utf8>>).

-file("src/rally/generator.gleam", 973).
-spec etf_protocol_wire_source(
    binary(),
    binary(),
    gleam@option:option(rally@types:auth_config())
) -> binary().
etf_protocol_wire_source(Atoms_module, Rpc_dispatch_module, Auth_config) ->
    Has_auth = gleam@option:is_some(Auth_config),
    Auth_import = case Auth_config of
        {some, {auth_config, Auth_module}} ->
            <<(import_as(Auth_module, <<"auth"/utf8>>))/binary, "\n"/utf8>>;

        none ->
            <<""/utf8>>
    end,
    Dispatch_fn = case Has_auth of
        true ->
            <<"pub fn dispatch_rpc(
  envelope envelope: RpcEnvelope,
  server_context server_context: ServerContext,
  identity identity: auth.Identity,
) -> #(RpcResult, ServerContext) {
  let #(data, server_context) =
    rpc_dispatch.handle(server_context:, data: envelope.raw, identity:)
  #(RpcResult(data:), server_context)
}
"/utf8>>;

        false ->
            <<"pub fn dispatch_rpc(
  envelope envelope: RpcEnvelope,
  server_context server_context: ServerContext,
) -> #(RpcResult, ServerContext) {
  let #(data, server_context) =
    rpc_dispatch.handle(server_context:, data: envelope.raw)
  #(RpcResult(data:), server_context)
}
"/utf8>>
    end,
    <<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.

import gleam/bytes_tree
import gleam/dynamic.{type Dynamic}
import libero/error.{type DecodeError}
import libero/wire as libero_wire
import mist.{type WebsocketConnection, type WebsocketMessage}
import server_context.{type ServerContext}
import "/utf8,
                                    Rpc_dispatch_module/binary>>/binary,
                                " as rpc_dispatch
"/utf8>>/binary,
                            Auth_import/binary>>/binary,
                        "

@external(erlang, \""/utf8>>/binary,
                    Atoms_module/binary>>/binary,
                "\", \"ensure\")
fn ensure_atoms() -> Nil

pub fn page_init_ok() -> Nil { Nil }

pub fn encode(value: a) -> BitArray { libero_wire.encode(value) }
pub fn decode_request(data: BitArray) -> Result(#(String, Int, Dynamic), DecodeError) { libero_wire.decode_request(data) }
pub fn encode_request(module module: String, request_id request_id: Int, msg msg: a) -> BitArray { libero_wire.encode_request(module:, request_id:, msg:) }
pub fn encode_response(request_id request_id: Int, value value: a) -> BitArray { libero_wire.encode_response(request_id:, value:) }
pub fn tag_response(request_id request_id: Int, data data: BitArray) -> BitArray { libero_wire.tag_response(request_id:, data:) }
pub fn encode_push(module module: String, value value: a) -> BitArray { libero_wire.encode_push(module:, value:) }
pub fn variant_tag(value: Dynamic) -> Result(String, Nil) { libero_wire.variant_tag(value) }
pub fn coerce(value: a) -> b { libero_wire.coerce(value) }

pub fn encode_flags(value: a) -> String { libero_wire.encode_flags(value) }
pub fn decode_flags_typed(flags: String, decoder_name: String) -> Result(a, DecodeError) { libero_wire.decode_flags_typed(flags:, decoder_name:) }

pub opaque type RpcEnvelope {
  RpcEnvelope(request_id: Int, identity: String, raw: BitArray)
}

pub opaque type RpcResult {
  RpcResult(data: BitArray)
}

pub fn rpc_request_id(envelope: RpcEnvelope) -> Int { envelope.request_id }
pub fn rpc_identity(envelope: RpcEnvelope) -> String { envelope.identity }
pub fn rpc_raw_payload(envelope: RpcEnvelope) -> BitArray { envelope.raw }

pub fn decode_rpc_envelope(data: BitArray) -> Result(RpcEnvelope, Nil) {
  case libero_wire.decode_request(data) {
    Ok(#(_module, request_id, raw)) ->
      case libero_wire.variant_tag(raw) {
        Ok(tag) -> Ok(RpcEnvelope(request_id:, identity: tag, raw: data))
        Error(Nil) -> Error(Nil)
      }
    Error(_) -> Error(Nil)
  }
}

pub fn decode_ws_rpc_envelope(msg: WebsocketMessage(a)) -> Result(RpcEnvelope, Nil) {
  case msg {
    mist.Binary(data) ->
      case decode_rpc_envelope(data) {
        Ok(envelope) if envelope.request_id == 0 -> Error(Nil)
        result -> result
      }
    _ -> Error(Nil)
  }
}

"/utf8>>/binary,
            Dispatch_fn/binary>>/binary,
        "
pub fn send_rpc_result(conn: WebsocketConnection, result: RpcResult) -> Nil {
  let _send_result = mist.send_binary_frame(conn, result.data)
  Nil
}

pub fn rpc_result_body(result: RpcResult) -> bytes_tree.BytesTree {
  bytes_tree.from_bit_array(result.data)
}

pub fn rpc_content_type() -> String {
  \"application/octet-stream\"
}

pub fn auth_error_result(request_id: Int, message: String) -> RpcResult {
  RpcResult(data: encode_response(request_id:, value: Error(message)))
}

pub fn error_result(request_id: Int, message: String) -> RpcResult {
  RpcResult(data: encode_response(request_id:, value: Error(message)))
}

pub fn malformed_rpc_result() -> RpcResult {
  RpcResult(data: encode_response(request_id: 0, value: Error(\"Bad request\")))
}

@external(erlang, \"rally_runtime_wire_ffi\", \"tuple_element\")
pub fn tuple_element(_tuple: Dynamic, _index: Int) -> Dynamic { dynamic.nil() }
"/utf8>>.

-file("src/rally/generator.gleam", 1100).
-spec json_protocol_wire_source(
    binary(),
    binary(),
    list(libero@scanner:handler_endpoint()),
    gleam@option:option(rally@types:auth_config()),
    binary()
) -> binary().
json_protocol_wire_source(
    Contract_hash,
    _,
    Endpoints,
    Auth_config,
    Wire_import_module
) ->
    Has_auth = gleam@option:is_some(Auth_config),
    Auth_import = case Auth_config of
        {some, {auth_config, Auth_module}} ->
            <<(import_as(Auth_module, <<"auth"/utf8>>))/binary, "\n"/utf8>>;

        none ->
            <<""/utf8>>
    end,
    Json_codecs_module = gleam@string:replace(
        Wire_import_module,
        <<"protocol_wire"/utf8>>,
        <<"json_codecs"/utf8>>
    ),
    Handler_imports = case rally@generator@json_rpc_dispatch:handler_imports(
        Endpoints
    ) of
        [] ->
            <<""/utf8>>;

        Imports ->
            <<(gleam@string:join(Imports, <<"\n"/utf8>>))/binary, "\n"/utf8>>
    end,
    Json_dispatch = rally@generator@json_rpc_dispatch:generate_json_dispatch_function_with_prefix(
        Endpoints,
        Has_auth,
        <<""/utf8>>
    ),
    Dispatch_fn = case Has_auth of
        true ->
            <<"pub fn dispatch_rpc(
  envelope envelope: RpcEnvelope,
  server_context server_context: ServerContext,
  identity identity: auth.Identity,
) -> #(RpcResult, ServerContext) {
  let #(frame, server_context) =
    json_dispatch(
      message: envelope.message,
      request_id: envelope.request_id,
      server_context:,
      identity:,
    )
  #(RpcResult(text: frame), server_context)
}
"/utf8>>;

        false ->
            <<"pub fn dispatch_rpc(
  envelope envelope: RpcEnvelope,
  server_context server_context: ServerContext,
) -> #(RpcResult, ServerContext) {
  let #(frame, server_context) =
    json_dispatch(
      message: envelope.message,
      request_id: envelope.request_id,
      server_context:,
    )
  #(RpcResult(text: frame), server_context)
}
"/utf8>>
    end,
    <<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.

import gleam/bit_array
import gleam/bytes_tree
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/io
import gleam/json
import gleam/option.{type Option, None, Some}
import libero/frame.{type ServerFrame}
import libero/json/error.{type JsonError, JsonError}
import libero/json/wire.{type RequestEnvelope} as json_wire
import libero/trace
import mist.{type WebsocketConnection, type WebsocketMessage}
import server_context.{type ServerContext}
import "/utf8,
                                                Json_codecs_module/binary>>/binary,
                                            " as json_codecs
"/utf8>>/binary,
                                        Handler_imports/binary>>/binary,
                                    Auth_import/binary>>/binary,
                                "

const contract_hash = \""/utf8>>/binary,
                            Contract_hash/binary>>/binary,
                        "\"

pub fn page_init_ok() -> json.Json { json.null() }

pub fn encode_request(module module: String, request_id request_id: Int, msg msg: json.Json) -> String {
  json_wire.encode_request(module:, request_id:, msg:, contract_hash:)
}

pub fn decode_server_frame(data: String) -> Result(ServerFrame(Dynamic), List(JsonError)) {
  json_wire.decode_server_frame(data)
}

pub fn encode_response(request_id request_id: Int, value value: json.Json) -> String {
  json_wire.encode_response(request_id:, value:)
}

pub fn encode_error(request_id request_id: Option(Int), errors errors: List(JsonError)) -> String {
  json_wire.encode_error(request_id:, errors:)
}

pub fn encode_push(module module: String, value value: json.Json) -> String {
  json_wire.encode_push(module:, value:)
}

pub fn encode_flags(value: json.Json) -> String {
  json_wire.encode_flags(value)
}

pub fn decode_flags_typed(flags: String, decoder: fn(Dynamic) -> Result(a, List(JsonError))) -> Result(a, List(JsonError)) {
  json_wire.decode_flags_typed(flags, decoder)
}

pub fn decode_request(data: String) -> Result(RequestEnvelope, List(JsonError)) {
  json_wire.decode_request(data, contract_hash)
}

pub opaque type RpcEnvelope {
  RpcEnvelope(request_id: Int, identity: String, message: Dynamic, raw_text: String)
}

pub opaque type RpcResult {
  RpcResult(text: String)
}

pub fn rpc_request_id(envelope: RpcEnvelope) -> Int { envelope.request_id }
pub fn rpc_identity(envelope: RpcEnvelope) -> String { envelope.identity }
pub fn rpc_raw_payload(envelope: RpcEnvelope) -> BitArray { bit_array.from_string(envelope.raw_text) }

pub fn decode_rpc_envelope(data: BitArray) -> Result(RpcEnvelope, Nil) {
  case bit_array.to_string(data) {
    Error(_) -> Error(Nil)
    Ok(text) -> decode_rpc_envelope_text(text)
  }
}

pub fn decode_ws_rpc_envelope(msg: WebsocketMessage(a)) -> Result(RpcEnvelope, Nil) {
  case msg {
    mist.Text(data) ->
      case decode_rpc_envelope_text(data) {
        Ok(envelope) if envelope.request_id == 0 -> Error(Nil)
        result -> result
      }
    _ -> Error(Nil)
  }
}

fn decode_rpc_envelope_text(data: String) -> Result(RpcEnvelope, Nil) {
  case json_wire.decode_request(data, contract_hash) {
    Error(_) -> Error(Nil)
    Ok(envelope) ->
      case extract_message_type(envelope.message) {
        Error(_) -> Error(Nil)
        Ok(type_str) ->
          Ok(RpcEnvelope(
            request_id: envelope.request_id,
            identity: type_str,
            message: envelope.message,
            raw_text: data,
          ))
      }
  }
}

fn extract_message_type(message: Dynamic) -> Result(String, Nil) {
  case decode.run(
    message,
    decode.field(\"type\", decode.string, fn(x) {
      decode.success(x)
    }),
  ) {
    Ok(type_str) -> Ok(type_str)
    Error(_) -> Error(Nil)
  }
}

"/utf8>>/binary,
                    Json_dispatch/binary>>/binary,
                "\n"/utf8>>/binary,
            Dispatch_fn/binary>>/binary,
        "
pub fn send_rpc_result(conn: WebsocketConnection, result: RpcResult) -> Nil {
  let _send_result = mist.send_text_frame(conn, result.text)
  Nil
}

pub fn rpc_result_body(result: RpcResult) -> bytes_tree.BytesTree {
  bytes_tree.from_string(result.text)
}

pub fn rpc_content_type() -> String {
  \"application/json\"
}

pub fn auth_error_result(request_id: Int, message: String) -> RpcResult {
  let result: Result(Nil, String) = Error(message)
  let encoded =
    json_codecs.json_encode_gleam_result__result(
      result,
      fn(_x) { json.null() },
      fn(x) { json.string(x) },
    )
  RpcResult(text: encode_response(request_id:, value: encoded))
}

pub fn error_result(request_id: Int, message: String) -> RpcResult {
  RpcResult(text: json_wire.encode_error(
    request_id: Some(request_id),
    errors: [JsonError(\"rpc\", message)],
  ))
}

pub fn malformed_rpc_result() -> RpcResult {
  RpcResult(text: json_wire.encode_error(
    request_id: None,
    errors: [JsonError(\"frame\", \"invalid RPC frame\")],
  ))
}

pub fn variant_tag(_value: Dynamic) -> Result(String, Nil) {
  panic as \"JSON protocol: variant_tag not implemented\"
}

pub fn encode(_value: a) -> BitArray {
  panic as \"JSON protocol: encode not implemented\"
}
"/utf8>>.

-file("src/rally/generator.gleam", 816).
?DOC(" Generate the server-side protocol wire facade based on the protocol string.\n").
-spec generate_protocol_wire(
    binary(),
    binary(),
    binary(),
    binary(),
    list(libero@scanner:handler_endpoint()),
    gleam@option:option(rally@types:auth_config()),
    binary()
) -> binary().
generate_protocol_wire(
    Protocol,
    Atoms_module,
    Contract_hash,
    Rpc_dispatch_module,
    Endpoints,
    Auth_config,
    Wire_import_module
) ->
    case Protocol of
        <<"json"/utf8>> ->
            json_protocol_wire_source(
                Contract_hash,
                Rpc_dispatch_module,
                Endpoints,
                Auth_config,
                Wire_import_module
            );

        _ ->
            etf_protocol_wire_source(
                Atoms_module,
                Rpc_dispatch_module,
                Auth_config
            )
    end.

-file("src/rally/generator.gleam", 843).
?DOC(
    " Generate the client-side JS protocol facade module.\n"
    " Re-exports FFI functions from the correct libero wire module\n"
    " based on protocol (ETF re-exports from rpc_ffi.mjs, JSON wraps\n"
    " encode_request with the contract hash).\n"
).
-spec generate_protocol_wire_js(binary(), binary()) -> binary().
generate_protocol_wire_js(Protocol, Contract_hash) ->
    case Protocol of
        <<"json"/utf8>> ->
            <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.\n"/utf8,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            "import { encode_request as libero_encode_request, decode_server_frame as libero_decode_server_frame, encode_flags, decode_flags_typed, identity } from \"../../libero/libero/json/wire_ffi.mjs\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        "import { Response, Push, Error as FrameError } from \"../../libero/libero/frame.mjs\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    "import { Ok, Error as ResultError, Empty, NonEmpty, BitArray } from \"../../gleam_stdlib/gleam.mjs\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                "import { Some, None } from \"../../gleam_stdlib/gleam/option.mjs\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            "import { typeRegistry } from \"./type_registry.mjs\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                        "\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    "const contract_hash = \""/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                Contract_hash/binary>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                            "\";\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                        "\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                    "// ---------- Libero typed JSON -> Gleam JS value ----------\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                                "//\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                            "// Response and push values arrive as Libero typed-value objects:\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                        "//   { type: \"<module>.<Type>\", variant: \"<Variant>\", fields: ... }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                    "//\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                                "// Container types (Result, Option) use array fields:\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                            "//   Result Ok:    { ..., fields: [inner_value] }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                        "//   Result Error: { ..., fields: [inner_value] }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                    "//   Option Some:  { ..., fields: [inner_value] }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                                "//   Option None:  { ..., fields: {} }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                            "//   User types:   { ..., fields: { label: value, ... } }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                        "\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                    "function typedJsonToGleamValue(value) {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                                "  if (value === undefined || value === null) return undefined;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                            "  if (typeof value !== \"object\") return value;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                        "  if (Array.isArray(value)) return value.map(v => typedJsonToGleamValue(v));\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                    "  // Libero typed-value shape: { type, variant, fields }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                                "  if (value.type && value.variant && value.fields !== undefined) {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                            "    const t = value.type;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                        "    const v = value.variant;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                    "    const f = value.fields;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                                "    if (t === \"gleam.Nil\") return undefined;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                            "    if (t === \"gleam.String\") return f.value;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                        "    if (t === \"gleam.Int\") return f.value;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                    "    if (t === \"gleam.Float\") return f.value;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                                "    if (t === \"gleam.Bool\") return f.value;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                            "    if (t === \"gleam.Result\" || t === \"gleam/result.Result\") {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                        "      const inner = typedJsonToGleamValue(Array.isArray(f) ? f[0] : f.value);\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                    "      return v === \"Ok\" ? new Ok(inner) : new ResultError(inner);\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                                "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                            "    if (t === \"gleam.Option\" || t === \"gleam/option.Option\") {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                        "      if (v === \"None\" || (Array.isArray(f) && f.length === 0) || Object.keys(f).length === 0) return new None();\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                    "      const inner = typedJsonToGleamValue(Array.isArray(f) ? f[0] : f.value);\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                                "      return new Some(inner);\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                            "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                        "    if (t === \"gleam.List\") {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                    "      if (v === \"Empty\") return new Empty();\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                                "      const items = (f.items || f).map(item => typedJsonToGleamValue(item));\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                            "      let list = new Empty();\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                        "      for (let i = items.length - 1; i >= 0; i--) list = new NonEmpty(items[i], list);\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                    "      return list;\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                                "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                            "    if (t === \"gleam.Tuple\") {\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                        "      return (f.elements || f).map(e => typedJsonToGleamValue(e));\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                    "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                                                                "    // User types: dispatch by full type identity\n"/utf8>>/binary,
                                                                                                                                                                                                                                                            "    // Key by full \"type\" + \"#\" + \"variant\" so a mismatched parent type\n"/utf8>>/binary,
                                                                                                                                                                                                                                                        "    // (\"some/module.OldType\" with variant \"Discount\") never resolves to\n"/utf8>>/binary,
                                                                                                                                                                                                                                                    "    // the registry entry for \"some/module.Discount\".\n"/utf8>>/binary,
                                                                                                                                                                                                                                                "    const key = t + \"#\" + v;\n"/utf8>>/binary,
                                                                                                                                                                                                                                            "    const ctor = typeRegistry[key];\n"/utf8>>/binary,
                                                                                                                                                                                                                                        "    if (!ctor) {\n"/utf8>>/binary,
                                                                                                                                                                                                                                    "      throw new Error(\"Unknown type in JSON decode: type=\" + t + \" variant=\" + v + \" (lookup key: \" + key + \")\");\n"/utf8>>/binary,
                                                                                                                                                                                                                                "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                            "    if (Array.isArray(f)) {\n"/utf8>>/binary,
                                                                                                                                                                                                                        "      return ctor(f.map(val => typedJsonToGleamValue(val)));\n"/utf8>>/binary,
                                                                                                                                                                                                                    "    }\n"/utf8>>/binary,
                                                                                                                                                                                                                "    const fields = {};\n"/utf8>>/binary,
                                                                                                                                                                                                            "    for (const [fieldKey, val] of Object.entries(f)) {\n"/utf8>>/binary,
                                                                                                                                                                                                        "      fields[fieldKey] = typedJsonToGleamValue(val);\n"/utf8>>/binary,
                                                                                                                                                                                                    "    }\n"/utf8>>/binary,
                                                                                                                                                                                                "    return ctor(fields);\n"/utf8>>/binary,
                                                                                                                                                                                            "  }\n"/utf8>>/binary,
                                                                                                                                                                                        "  return value;\n"/utf8>>/binary,
                                                                                                                                                                                    "}\n"/utf8>>/binary,
                                                                                                                                                                                "\n"/utf8>>/binary,
                                                                                                                                                                            "// ---------- Public API ----------\n"/utf8>>/binary,
                                                                                                                                                                        "//\n"/utf8>>/binary,
                                                                                                                                                                    "// encode_request receives messages pre-encoded by Gleam typed JSON\n"/utf8>>/binary,
                                                                                                                                                                "// codecs (types.json_encode_client_msg). The value is already in\n"/utf8>>/binary,
                                                                                                                                                            "// the Libero typed-value shape — pass through without conversion.\n"/utf8>>/binary,
                                                                                                                                                        "\n"/utf8>>/binary,
                                                                                                                                                    "export function encode_request(module, requestId, msg) {\n"/utf8>>/binary,
                                                                                                                                                "  return libero_encode_request(module, requestId, msg, contract_hash);\n"/utf8>>/binary,
                                                                                                                                            "}\n"/utf8>>/binary,
                                                                                                                                        "\n"/utf8>>/binary,
                                                                                                                                    "export function decode_server_frame(data) {\n"/utf8>>/binary,
                                                                                                                                "  try {\n"/utf8>>/binary,
                                                                                                                            "    const result = libero_decode_server_frame(data);\n"/utf8>>/binary,
                                                                                                                        "    if (result instanceof ResultError) return result;\n"/utf8>>/binary,
                                                                                                                    "    const frame = result[0];\n"/utf8>>/binary,
                                                                                                                "    if (frame instanceof Response) {\n"/utf8>>/binary,
                                                                                                            "      try {\n"/utf8>>/binary,
                                                                                                        "        return new Ok({ kind: \"response\", requestId: frame.request_id, value: typedJsonToGleamValue(frame.value) });\n"/utf8>>/binary,
                                                                                                    "      } catch (e) {\n"/utf8>>/binary,
                                                                                                "        return new Ok({ kind: \"error\", requestId: frame.request_id, errors: [[\"decode\", e.message || \"JSON decode error\"]] });\n"/utf8>>/binary,
                                                                                            "      }\n"/utf8>>/binary,
                                                                                        "    }\n"/utf8>>/binary,
                                                                                    "    if (frame instanceof Push) {\n"/utf8>>/binary,
                                                                                "      try {\n"/utf8>>/binary,
                                                                            "        return new Ok({ kind: \"push\", module: frame.module, value: typedJsonToGleamValue(frame.value) });\n"/utf8>>/binary,
                                                                        "      } catch (e) {\n"/utf8>>/binary,
                                                                    "        return new ResultError(e.message || \"JSON decode error\");\n"/utf8>>/binary,
                                                                "      }\n"/utf8>>/binary,
                                                            "    }\n"/utf8>>/binary,
                                                        "    if (frame instanceof FrameError) {\n"/utf8>>/binary,
                                                    "      const rid = frame.request_id;\n"/utf8>>/binary,
                                                "      return new Ok({ kind: \"error\", requestId: rid instanceof Some ? rid[0] : null, errors: frame.errors });\n"/utf8>>/binary,
                                            "    }\n"/utf8>>/binary,
                                        "    return result;\n"/utf8>>/binary,
                                    "  } catch (e) {\n"/utf8>>/binary,
                                "    return new ResultError(e.message || \"JSON decode error\");\n"/utf8>>/binary,
                            "  }\n"/utf8>>/binary,
                        "}\n"/utf8>>/binary,
                    "\n"/utf8>>/binary,
                "export { encode_flags, decode_flags_typed, identity, typedJsonToGleamValue };\n"/utf8>>;

        _ ->
            <<<<"// Generated by Rally — do not edit.\n"/utf8,
                    "export { encode_request, decode_server_frame, identity } from \"../../libero/libero/rpc_ffi.mjs\";\n"/utf8>>/binary,
                "export { encode_flags, decode_flags_typed } from \"../../libero/libero/wire.mjs\";\n"/utf8>>
    end.