src/lightspeed@router.erl

-module(lightspeed@router).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/router.gleam").
-export([new/1, add/4, resolve/2, mount_instructions/2, decode/2, view_id/1, params/1, match_label/1]).
-export_type([route_param/0, route/1, router/1, route_match/1, matched_route/1]).

-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(" Router integration helpers for mount/render flows.\n").

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

-type route(LKJ) :: {route,
        binary(),
        binary(),
        fun((lightspeed@event:inbound_event(), list(route_param())) -> {ok, LKJ} |
            {error, lightspeed@event:decode_error()})}.

-type router(LKK) :: {router, binary(), list(route(LKK))}.

-type route_match(LKL) :: {found, matched_route(LKL)} |
    {not_found, binary(), binary()}.

-type matched_route(LKM) :: {matched_route,
        binary(),
        binary(),
        binary(),
        list(route_param()),
        fun((lightspeed@event:inbound_event(), list(route_param())) -> {ok, LKM} |
            {error, lightspeed@event:decode_error()})}.

-file("src/lightspeed/router.gleam", 48).
?DOC(" Create a new router with a fallback not-found view.\n").
-spec new(binary()) -> router(any()).
new(Not_found_view) ->
    {router, Not_found_view, []}.

-file("src/lightspeed/router.gleam", 53).
?DOC(" Register a route pattern.\n").
-spec add(
    router(LKP),
    binary(),
    binary(),
    fun((lightspeed@event:inbound_event(), list(route_param())) -> {ok, LKP} |
        {error, lightspeed@event:decode_error()})
) -> router(LKP).
add(Router, Pattern, View_id, Decode_event) ->
    {router,
        erlang:element(2, Router),
        [{route, Pattern, View_id, Decode_event} | erlang:element(3, Router)]}.

-file("src/lightspeed/router.gleam", 229).
-spec join_graphemes(list(binary())) -> binary().
join_graphemes(Graphemes) ->
    case Graphemes of
        [] ->
            <<""/utf8>>;

        [Grapheme | Rest] ->
            <<Grapheme/binary, (join_graphemes(Rest))/binary>>
    end.

-file("src/lightspeed/router.gleam", 217).
-spec param_name(binary()) -> gleam@option:option(binary()).
param_name(Segment) ->
    case gleam@string:to_graphemes(Segment) of
        [<<":"/utf8>> | Rest] ->
            case Rest of
                [] ->
                    none;

                _ ->
                    {some, join_graphemes(Rest)}
            end;

        _ ->
            none
    end.

-file("src/lightspeed/router.gleam", 173).
-spec match_segments(list(binary()), list(binary()), list(route_param())) -> gleam@option:option(list(route_param())).
match_segments(Pattern_segments, Path_segments, Captures_rev) ->
    case {Pattern_segments, Path_segments} of
        {[], []} ->
            {some, lists:reverse(Captures_rev)};

        {[], _} ->
            none;

        {_, []} ->
            none;

        {[Pattern_segment | Pattern_rest], [Path_segment | Path_rest]} ->
            case param_name(Pattern_segment) of
                {some, Name} ->
                    match_segments(
                        Pattern_rest,
                        Path_rest,
                        [{route_param, Name, Path_segment} | Captures_rev]
                    );

                none ->
                    case Pattern_segment =:= Path_segment of
                        true ->
                            match_segments(
                                Pattern_rest,
                                Path_rest,
                                Captures_rev
                            );

                        false ->
                            none
                    end
            end
    end.

-file("src/lightspeed/router.gleam", 206).
-spec filter_empty(list(binary()), list(binary())) -> list(binary()).
filter_empty(Values, Kept_rev) ->
    case Values of
        [] ->
            lists:reverse(Kept_rev);

        [Value | Rest] ->
            case Value =:= <<""/utf8>> of
                true ->
                    filter_empty(Rest, Kept_rev);

                false ->
                    filter_empty(Rest, [Value | Kept_rev])
            end
    end.

-file("src/lightspeed/router.gleam", 200).
-spec segments(binary()) -> list(binary()).
segments(Path) ->
    _pipe = Path,
    _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
    filter_empty(_pipe@1, []).

-file("src/lightspeed/router.gleam", 167).
-spec match_pattern(binary(), binary()) -> gleam@option:option(list(route_param())).
match_pattern(Pattern, Path) ->
    Pattern_segments = segments(Pattern),
    Path_segments = segments(Path),
    match_segments(Pattern_segments, Path_segments, []).

-file("src/lightspeed/router.gleam", 119).
-spec find_match(list(route(LLM)), binary()) -> gleam@option:option(matched_route(LLM)).
find_match(Routes, Normalized_path) ->
    case Routes of
        [] ->
            none;

        [Route | Rest] ->
            case match_pattern(erlang:element(2, Route), Normalized_path) of
                {some, Captures} ->
                    {some,
                        {matched_route,
                            Normalized_path,
                            erlang:element(2, Route),
                            erlang:element(3, Route),
                            Captures,
                            erlang:element(4, Route)}};

                none ->
                    find_match(Rest, Normalized_path)
            end
    end.

-file("src/lightspeed/router.gleam", 243).
-spec drop_last(binary()) -> binary().
drop_last(Path) ->
    case lists:reverse(gleam@string:to_graphemes(Path)) of
        [] ->
            <<""/utf8>>;

        [_ | Rest] ->
            _pipe = Rest,
            _pipe@1 = lists:reverse(_pipe),
            join_graphemes(_pipe@1)
    end.

-file("src/lightspeed/router.gleam", 236).
-spec ends_with_slash(binary()) -> boolean().
ends_with_slash(Path) ->
    case lists:reverse(gleam@string:to_graphemes(Path)) of
        [<<"/"/utf8>> | _] ->
            true;

        _ ->
            false
    end.

-file("src/lightspeed/router.gleam", 156).
-spec trim_trailing_slashes(binary()) -> binary().
trim_trailing_slashes(Path) ->
    case Path of
        <<"/"/utf8>> ->
            <<"/"/utf8>>;

        _ ->
            case ends_with_slash(Path) of
                true ->
                    trim_trailing_slashes(drop_last(Path));

                false ->
                    Path
            end
    end.

-file("src/lightspeed/router.gleam", 142).
-spec normalize_path(binary()) -> binary().
normalize_path(Path) ->
    No_query = case gleam@string:split(Path, <<"?"/utf8>>) of
        [First | _] ->
            First;

        [] ->
            Path
    end,
    Trimmed = trim_trailing_slashes(No_query),
    case Trimmed of
        <<""/utf8>> ->
            <<"/"/utf8>>;

        _ ->
            Trimmed
    end.

-file("src/lightspeed/router.gleam", 67).
?DOC(" Resolve one path into a route match.\n").
-spec resolve(router(LKV), binary()) -> route_match(LKV).
resolve(Router, Path) ->
    Normalized = normalize_path(Path),
    case find_match(lists:reverse(erlang:element(3, Router)), Normalized) of
        {some, Matched} ->
            {found, Matched};

        none ->
            {not_found, Normalized, erlang:element(2, Router)}
    end.

-file("src/lightspeed/router.gleam", 77).
?DOC(" Build a mount/render ISA sequence for a resolved route.\n").
-spec mount_instructions(route_match(any()), binary()) -> list(lightspeed@agent@isa:instruction()).
mount_instructions(Route_match, Csrf_token) ->
    case Route_match of
        {found, Matched} ->
            [{mount, erlang:element(2, Matched), Csrf_token},
                {render, erlang:element(4, Matched)}];

        {not_found, Path, View_id} ->
            [{mount, Path, Csrf_token}, {render, View_id}]
    end.

-file("src/lightspeed/router.gleam", 94).
?DOC(" Decode one inbound event against a matched route decoder.\n").
-spec decode(matched_route(LLB), lightspeed@event:inbound_event()) -> {ok, LLB} |
    {error, lightspeed@event:decode_error()}.
decode(Matched, Inbound) ->
    (erlang:element(6, Matched))(Inbound, erlang:element(5, Matched)).

-file("src/lightspeed/router.gleam", 102).
?DOC(" Matched route view id.\n").
-spec view_id(matched_route(any())) -> binary().
view_id(Matched) ->
    erlang:element(4, Matched).

-file("src/lightspeed/router.gleam", 107).
?DOC(" Matched route captured params.\n").
-spec params(matched_route(any())) -> list(route_param()).
params(Matched) ->
    erlang:element(5, Matched).

-file("src/lightspeed/router.gleam", 112).
?DOC(" Render stable route match labels for logs.\n").
-spec match_label(route_match(any())) -> binary().
match_label(Route_match) ->
    case Route_match of
        {found, Matched} ->
            <<"found:"/utf8, (erlang:element(3, Matched))/binary>>;

        {not_found, _, View_id} ->
            <<"not_found:"/utf8, View_id/binary>>
    end.