src/lightspeed@component@stateful.erl

-module(lightspeed@component@stateful).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/component/stateful.gleam").
-export([mount_context/3, route/2, route_event/2, start/4, update/2, handle_event/2, model/1, rendered/1, html/1, context/1]).
-export_type([lifecycle/0, mount_context/0, event_route/1, lifecycle_component/3, instance/3]).

-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(" Stateful component lifecycle and event routing compatibility helpers.\n").

-type lifecycle() :: disconnected | connected.

-type mount_context() :: {mount_context, binary(), binary(), lifecycle()}.

-type event_route(HYU) :: {event_route,
        binary(),
        fun((lightspeed@event:inbound_event()) -> {ok, HYU} |
            {error, lightspeed@event:decode_error()})}.

-type lifecycle_component(HYV, HYW, HYX) :: {lifecycle_component,
        fun((mount_context(), HYW) -> {HYV,
            list(lightspeed@component:command(HYX))}),
        fun((HYV, HYW) -> {HYV, list(lightspeed@component:command(HYX))}),
        fun((HYV, HYX) -> {HYV, list(lightspeed@component:command(HYX))}),
        fun((HYV) -> lightspeed@component:rendered()),
        list(event_route(HYX))}.

-opaque instance(HYY, HYZ, HZA) :: {instance,
        lifecycle_component(HYY, HYZ, HZA),
        HYY,
        HYZ,
        mount_context(),
        binary(),
        lightspeed@component:rendered()}.

-file("src/lightspeed/component/stateful.gleam", 50).
?DOC(" Build a mount context.\n").
-spec mount_context(binary(), binary(), lifecycle()) -> mount_context().
mount_context(Id, Route, Lifecycle) ->
    {mount_context, Id, Route, Lifecycle}.

-file("src/lightspeed/component/stateful.gleam", 59).
?DOC(" Build one event route.\n").
-spec route(
    binary(),
    fun((lightspeed@event:inbound_event()) -> {ok, HZB} |
        {error, lightspeed@event:decode_error()})
) -> event_route(HZB).
route(Name, Decode) ->
    {event_route, Name, Decode}.

-file("src/lightspeed/component/stateful.gleam", 195).
-spec route_names(list(event_route(any()))) -> binary().
route_names(Routes) ->
    case Routes of
        [] ->
            <<"no_routes_registered"/utf8>>;

        [{event_route, Name, _}] ->
            Name;

        [{event_route, Name@1, _} | Rest] ->
            <<<<Name@1/binary, "|"/utf8>>/binary, (route_names(Rest))/binary>>
    end.

-file("src/lightspeed/component/stateful.gleam", 175).
-spec route_event_with_expected(
    lightspeed@event:inbound_event(),
    list(event_route(IBU)),
    binary()
) -> {ok, IBU} | {error, lightspeed@event:decode_error()}.
route_event_with_expected(Inbound, Routes, Expected) ->
    case Routes of
        [] ->
            {error,
                {unexpected_event, Expected, lightspeed@event:name(Inbound)}};

        [{event_route, Name, Decode} | Rest] ->
            case lightspeed@event:name(Inbound) =:= Name of
                true ->
                    Decode(Inbound);

                false ->
                    route_event_with_expected(Inbound, Rest, Expected)
            end
    end.

-file("src/lightspeed/component/stateful.gleam", 67).
?DOC(" Resolve an inbound event against named routes.\n").
-spec route_event(lightspeed@event:inbound_event(), list(event_route(HZF))) -> {ok,
        HZF} |
    {error, lightspeed@event:decode_error()}.
route_event(Inbound, Routes) ->
    route_event_with_expected(Inbound, Routes, route_names(Routes)).

-file("src/lightspeed/component/stateful.gleam", 77).
?DOC(
    " Start a stateful component instance from mount assigns.\n"
    "\n"
    " Returns initial commands and one deterministic replace patch.\n"
).
-spec start(lifecycle_component(HZK, HZL, HZM), mount_context(), HZL, binary()) -> {instance(HZK, HZL, HZM),
    list(lightspeed@component:command(HZM)),
    list(lightspeed@diff:patch())}.
start(Definition, Context, Assigns, Target) ->
    {Model, Commands} = (erlang:element(2, Definition))(Context, Assigns),
    Rendered = (erlang:element(5, Definition))(Model),
    Patch = {replace, Target, lightspeed@component:to_html(Rendered)},
    {{instance, Definition, Model, Assigns, Context, Target, Rendered},
        Commands,
        [Patch]}.

-file("src/lightspeed/component/stateful.gleam", 203).
-spec patch_plan(
    binary(),
    lightspeed@component:rendered(),
    lightspeed@component:rendered()
) -> list(lightspeed@diff:patch()).
patch_plan(Target, Previous, Next) ->
    case Previous =:= Next of
        true ->
            [];

        false ->
            case lightspeed@component:fingerprint(Previous) =:= lightspeed@component:fingerprint(
                Next
            ) of
                true ->
                    [{update_segments,
                            Target,
                            lightspeed@component:fingerprint(Next),
                            [lightspeed@diff:slot(
                                    <<"html"/utf8>>,
                                    lightspeed@component:to_html(Next)
                                )]}];

                false ->
                    [{replace, Target, lightspeed@component:to_html(Next)}]
            end
    end.

-file("src/lightspeed/component/stateful.gleam", 106).
?DOC(" Apply parent assigns updates.\n").
-spec update(instance(HZW, HZX, HZY), HZX) -> {instance(HZW, HZX, HZY),
    list(lightspeed@component:command(HZY)),
    list(lightspeed@diff:patch())}.
update(Instance, Assigns) ->
    {Model, Commands} = (erlang:element(3, erlang:element(2, Instance)))(
        erlang:element(3, Instance),
        Assigns
    ),
    Rendered = (erlang:element(5, erlang:element(2, Instance)))(Model),
    Patches = patch_plan(
        erlang:element(6, Instance),
        erlang:element(7, Instance),
        Rendered
    ),
    {{instance,
            erlang:element(2, Instance),
            Model,
            Assigns,
            erlang:element(5, Instance),
            erlang:element(6, Instance),
            Rendered},
        Commands,
        Patches}.

-file("src/lightspeed/component/stateful.gleam", 126).
?DOC(" Route and handle one inbound event.\n").
-spec handle_event(instance(IAI, IAJ, IAK), lightspeed@event:inbound_event()) -> {instance(IAI, IAJ, IAK),
    {ok, IAK} | {error, lightspeed@event:decode_error()},
    list(lightspeed@component:command(IAK)),
    list(lightspeed@diff:patch())}.
handle_event(Instance, Inbound) ->
    case route_event(Inbound, erlang:element(6, erlang:element(2, Instance))) of
        {error, Error} ->
            {Instance, {error, Error}, [], []};

        {ok, Message} ->
            {Model, Commands} = (erlang:element(4, erlang:element(2, Instance)))(
                erlang:element(3, Instance),
                Message
            ),
            Rendered = (erlang:element(5, erlang:element(2, Instance)))(Model),
            Patches = patch_plan(
                erlang:element(6, Instance),
                erlang:element(7, Instance),
                Rendered
            ),
            {{instance,
                    erlang:element(2, Instance),
                    Model,
                    erlang:element(4, Instance),
                    erlang:element(5, Instance),
                    erlang:element(6, Instance),
                    Rendered},
                {ok, Message},
                Commands,
                Patches}
    end.

-file("src/lightspeed/component/stateful.gleam", 155).
?DOC(" Current model.\n").
-spec model(instance(IAW, any(), any())) -> IAW.
model(Instance) ->
    erlang:element(3, Instance).

-file("src/lightspeed/component/stateful.gleam", 160).
?DOC(" Last rendered value.\n").
-spec rendered(instance(any(), any(), any())) -> lightspeed@component:rendered().
rendered(Instance) ->
    erlang:element(7, Instance).

-file("src/lightspeed/component/stateful.gleam", 165).
?DOC(" Latest HTML snapshot.\n").
-spec html(instance(any(), any(), any())) -> binary().
html(Instance) ->
    _pipe = erlang:element(7, Instance),
    lightspeed@component:to_html(_pipe).

-file("src/lightspeed/component/stateful.gleam", 171).
?DOC(" Current mount context.\n").
-spec context(instance(any(), any(), any())) -> mount_context().
context(Instance) ->
    erlang:element(5, Instance).