src/lightspeed@testing@liveview.erl

-module(lightspeed@testing@liveview).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/testing/liveview.gleam").
-export([lifecycle_label/1, mount_disconnected/5, mount_connected/5, render_assigns/2, render_event/3, html/1, lifecycle/1, patches/1, patch_operations/1, pushed_instructions/1, telemetry/1, has_patch/2, redirected_to/2, pushed_event/3, stable_signature/1, failure_signature/4, render_story_fixture/1, story_name/1, story_html/1, story_fingerprint/1, story_signature/1]).
-export_type([session/3, story_fixture/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(" LiveView-style deterministic testing DSL and debug signatures.\n").

-opaque session(TYW, TYX, TYY) :: {session,
        lightspeed@component@stateful:instance(TYW, TYX, TYY),
        binary(),
        binary(),
        lightspeed@component@stateful:lifecycle(),
        list(lightspeed@diff:patch()),
        list(lightspeed@agent@isa:instruction()),
        list(binary()),
        integer()}.

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

-file("src/lightspeed/testing/liveview.gleam", 399).
-spec append_ordered_to_rev(list(UEJ), list(UEJ)) -> list(UEJ).
append_ordered_to_rev(Rev, Ordered) ->
    case Ordered of
        [] ->
            Rev;

        [Value | Rest] ->
            append_ordered_to_rev([Value | Rev], Rest)
    end.

-file("src/lightspeed/testing/liveview.gleam", 146).
?DOC(" Stable lifecycle label for fixture signatures.\n").
-spec lifecycle_label(lightspeed@component@stateful:lifecycle()) -> binary().
lifecycle_label(Lifecycle) ->
    case Lifecycle of
        disconnected ->
            <<"disconnected"/utf8>>;

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

-file("src/lightspeed/testing/liveview.gleam", 345).
-spec instruction_labels(list(lightspeed@agent@isa:instruction())) -> list(binary()).
instruction_labels(Instructions) ->
    gleam@list:map(
        Instructions,
        fun(Instruction) ->
            <<"instruction:"/utf8,
                (lightspeed@agent@isa:describe(Instruction))/binary>>
        end
    ).

-file("src/lightspeed/testing/liveview.gleam", 406).
-spec prepend(list(UEN), list(UEN)) -> list(UEN).
prepend(Left, Right) ->
    case Left of
        [] ->
            Right;

        [Entry | Rest] ->
            [Entry | prepend(Rest, Right)]
    end.

-file("src/lightspeed/testing/liveview.gleam", 339).
-spec patch_labels(list(lightspeed@diff:patch())) -> list(binary()).
patch_labels(Patches) ->
    gleam@list:map(
        Patches,
        fun(Patch) ->
            <<<<<<"patch:"/utf8, (lightspeed@diff:operation(Patch))/binary>>/binary,
                    ":"/utf8>>/binary,
                (lightspeed@diff:target(Patch))/binary>>
        end
    ).

-file("src/lightspeed/testing/liveview.gleam", 324).
-spec command_instructions_loop(
    list(lightspeed@component:command(any())),
    list(lightspeed@agent@isa:instruction())
) -> list(lightspeed@agent@isa:instruction()).
command_instructions_loop(Commands, Instructions_rev) ->
    case Commands of
        [] ->
            lists:reverse(Instructions_rev);

        [Command | Rest] ->
            case Command of
                {push, Instruction} ->
                    command_instructions_loop(
                        Rest,
                        [Instruction | Instructions_rev]
                    );

                _ ->
                    command_instructions_loop(Rest, Instructions_rev)
            end
    end.

-file("src/lightspeed/testing/liveview.gleam", 318).
-spec command_instructions(list(lightspeed@component:command(any()))) -> list(lightspeed@agent@isa:instruction()).
command_instructions(Commands) ->
    command_instructions_loop(Commands, []).

-file("src/lightspeed/testing/liveview.gleam", 279).
-spec mount_with_lifecycle(
    lightspeed@component@stateful:lifecycle_component(UDH, UDI, UDJ),
    binary(),
    binary(),
    lightspeed@component@stateful:lifecycle(),
    UDI,
    binary()
) -> session(UDH, UDI, UDJ).
mount_with_lifecycle(Definition, Id, Route, Lifecycle, Assigns, Target) ->
    Context = lightspeed@component@stateful:mount_context(Id, Route, Lifecycle),
    {Instance, Commands, Patches} = lightspeed@component@stateful:start(
        Definition,
        Context,
        Assigns,
        Target
    ),
    Instructions = command_instructions(Commands),
    Telemetry_step = prepend(
        patch_labels(Patches),
        prepend(
            instruction_labels(Instructions),
            [<<<<<<"mount:"/utf8, (lifecycle_label(Lifecycle))/binary>>/binary,
                        ":"/utf8>>/binary,
                    Route/binary>>]
        )
    ),
    {session,
        Instance,
        Route,
        Target,
        Lifecycle,
        append_ordered_to_rev([], Patches),
        append_ordered_to_rev([], Instructions),
        append_ordered_to_rev([], Telemetry_step),
        1}.

-file("src/lightspeed/testing/liveview.gleam", 37).
?DOC(" Mount a stateful component in disconnected lifecycle mode.\n").
-spec mount_disconnected(
    lightspeed@component@stateful:lifecycle_component(TYZ, TZA, TZB),
    binary(),
    binary(),
    TZA,
    binary()
) -> session(TYZ, TZA, TZB).
mount_disconnected(Definition, Id, Route, Assigns, Target) ->
    mount_with_lifecycle(Definition, Id, Route, disconnected, Assigns, Target).

-file("src/lightspeed/testing/liveview.gleam", 55).
?DOC(" Mount a stateful component in connected lifecycle mode.\n").
-spec mount_connected(
    lightspeed@component@stateful:lifecycle_component(TZI, TZJ, TZK),
    binary(),
    binary(),
    TZJ,
    binary()
) -> session(TZI, TZJ, TZK).
mount_connected(Definition, Id, Route, Assigns, Target) ->
    mount_with_lifecycle(Definition, Id, Route, connected, Assigns, Target).

-file("src/lightspeed/testing/liveview.gleam", 73).
?DOC(" Apply parent assigns and capture emitted patches/commands in one test step.\n").
-spec render_assigns(session(TZR, TZS, TZT), TZS) -> session(TZR, TZS, TZT).
render_assigns(Session, Assigns) ->
    {Instance, Commands, Patches} = lightspeed@component@stateful:update(
        erlang:element(2, Session),
        Assigns
    ),
    Instructions = command_instructions(Commands),
    Telemetry_step = prepend(
        patch_labels(Patches),
        prepend(instruction_labels(Instructions), [<<"assigns:update"/utf8>>])
    ),
    {session,
        Instance,
        erlang:element(3, Session),
        erlang:element(4, Session),
        erlang:element(5, Session),
        append_ordered_to_rev(erlang:element(6, Session), Patches),
        append_ordered_to_rev(erlang:element(7, Session), Instructions),
        append_ordered_to_rev(erlang:element(8, Session), Telemetry_step),
        erlang:element(9, Session) + 1}.

-file("src/lightspeed/testing/liveview.gleam", 311).
-spec event_label(
    binary(),
    {ok, any()} | {error, lightspeed@event:decode_error()}
) -> binary().
event_label(Name, Routed) ->
    case Routed of
        {ok, _} ->
            <<<<"event:"/utf8, Name/binary>>/binary, ":ok"/utf8>>;

        {error, Error} ->
            <<<<<<"event:"/utf8, Name/binary>>/binary, ":"/utf8>>/binary,
                (lightspeed@event:error_to_string(Error))/binary>>
    end.

-file("src/lightspeed/testing/liveview.gleam", 100).
?DOC(" Route and render one event against the mounted component.\n").
-spec render_event(session(UAA, UAB, UAC), binary(), binary()) -> {session(UAA, UAB, UAC),
    {ok, UAC} | {error, lightspeed@event:decode_error()}}.
render_event(Session, Name, Payload) ->
    Inbound = lightspeed@event:inbound(Name, Payload),
    {Instance, Routed, Commands, Patches} = lightspeed@component@stateful:handle_event(
        erlang:element(2, Session),
        Inbound
    ),
    Instructions = command_instructions(Commands),
    Telemetry_step = prepend(
        patch_labels(Patches),
        prepend(instruction_labels(Instructions), [event_label(Name, Routed)])
    ),
    {{session,
            Instance,
            erlang:element(3, Session),
            erlang:element(4, Session),
            erlang:element(5, Session),
            append_ordered_to_rev(erlang:element(6, Session), Patches),
            append_ordered_to_rev(erlang:element(7, Session), Instructions),
            append_ordered_to_rev(erlang:element(8, Session), Telemetry_step),
            erlang:element(9, Session) + 1},
        Routed}.

-file("src/lightspeed/testing/liveview.gleam", 135).
?DOC(" Current rendered HTML.\n").
-spec html(session(any(), any(), any())) -> binary().
html(Session) ->
    _pipe = erlang:element(2, Session),
    lightspeed@component@stateful:html(_pipe).

-file("src/lightspeed/testing/liveview.gleam", 141).
?DOC(" Mount lifecycle mode.\n").
-spec lifecycle(session(any(), any(), any())) -> lightspeed@component@stateful:lifecycle().
lifecycle(Session) ->
    erlang:element(5, Session).

-file("src/lightspeed/testing/liveview.gleam", 154).
?DOC(" All emitted patches in execution order.\n").
-spec patches(session(any(), any(), any())) -> list(lightspeed@diff:patch()).
patches(Session) ->
    lists:reverse(erlang:element(6, Session)).

-file("src/lightspeed/testing/liveview.gleam", 159).
?DOC(" Patch operation labels in execution order.\n").
-spec patch_operations(session(any(), any(), any())) -> list(binary()).
patch_operations(Session) ->
    _pipe = Session,
    _pipe@1 = patches(_pipe),
    gleam@list:map(_pipe@1, fun lightspeed@diff:operation/1).

-file("src/lightspeed/testing/liveview.gleam", 166).
?DOC(" All pushed ISA instructions in execution order.\n").
-spec pushed_instructions(session(any(), any(), any())) -> list(lightspeed@agent@isa:instruction()).
pushed_instructions(Session) ->
    lists:reverse(erlang:element(7, Session)).

-file("src/lightspeed/testing/liveview.gleam", 173).
?DOC(" Stable telemetry/debug labels in execution order.\n").
-spec telemetry(session(any(), any(), any())) -> list(binary()).
telemetry(Session) ->
    lists:reverse(erlang:element(8, Session)).

-file("src/lightspeed/testing/liveview.gleam", 351).
-spec contains_patch(list(lightspeed@diff:patch()), lightspeed@diff:patch()) -> boolean().
contains_patch(Patches, Expected) ->
    case Patches of
        [] ->
            false;

        [Patch | Rest] ->
            case Patch =:= Expected of
                true ->
                    true;

                false ->
                    contains_patch(Rest, Expected)
            end
    end.

-file("src/lightspeed/testing/liveview.gleam", 178).
?DOC(" Assertion helper for expected patch output.\n").
-spec has_patch(session(any(), any(), any()), lightspeed@diff:patch()) -> boolean().
has_patch(Session, Patch) ->
    contains_patch(patches(Session), Patch).

-file("src/lightspeed/testing/liveview.gleam", 362).
-spec contains_navigation(list(lightspeed@agent@isa:instruction()), binary()) -> boolean().
contains_navigation(Instructions, To) ->
    case Instructions of
        [] ->
            false;

        [Instruction | Rest] ->
            case Instruction of
                {navigate, Path} ->
                    case Path =:= To of
                        true ->
                            true;

                        false ->
                            contains_navigation(Rest, To)
                    end;

                _ ->
                    contains_navigation(Rest, To)
            end
    end.

-file("src/lightspeed/testing/liveview.gleam", 186).
?DOC(" Assertion helper for redirect/navigation behavior.\n").
-spec redirected_to(session(any(), any(), any()), binary()) -> boolean().
redirected_to(Session, To) ->
    contains_navigation(pushed_instructions(Session), To).

-file("src/lightspeed/testing/liveview.gleam", 380).
-spec contains_push_event(
    list(lightspeed@agent@isa:instruction()),
    binary(),
    binary()
) -> boolean().
contains_push_event(Instructions, Name, Payload) ->
    case Instructions of
        [] ->
            false;

        [Instruction | Rest] ->
            case Instruction of
                {push_event, Event_name, Event_payload} ->
                    case (Event_name =:= Name) andalso (Event_payload =:= Payload) of
                        true ->
                            true;

                        false ->
                            contains_push_event(Rest, Name, Payload)
                    end;

                _ ->
                    contains_push_event(Rest, Name, Payload)
            end
    end.

-file("src/lightspeed/testing/liveview.gleam", 194).
?DOC(" Assertion helper for pushed server-event behavior.\n").
-spec pushed_event(session(any(), any(), any()), binary(), binary()) -> boolean().
pushed_event(Session, Name, Payload) ->
    contains_push_event(pushed_instructions(Session), Name, Payload).

-file("src/lightspeed/testing/liveview.gleam", 413).
-spec join_with(binary(), list(binary())) -> binary().
join_with(Separator, Values) ->
    case Values of
        [] ->
            <<""/utf8>>;

        [Value] ->
            Value;

        [Value@1 | Rest] ->
            <<<<Value@1/binary, Separator/binary>>/binary,
                (join_with(Separator, Rest))/binary>>
    end.

-file("src/lightspeed/testing/liveview.gleam", 203).
?DOC(" Stable deterministic session signature for CI fixtures and debug output.\n").
-spec stable_signature(session(any(), any(), any())) -> binary().
stable_signature(Session) ->
    <<<<<<<<<<<<<<<<<<<<<<<<<<"lifecycle="/utf8,
                                                        (lifecycle_label(
                                                            erlang:element(
                                                                5,
                                                                Session
                                                            )
                                                        ))/binary>>/binary,
                                                    "|route="/utf8>>/binary,
                                                (erlang:element(3, Session))/binary>>/binary,
                                            "|target="/utf8>>/binary,
                                        (erlang:element(4, Session))/binary>>/binary,
                                    "|steps="/utf8>>/binary,
                                (erlang:integer_to_binary(
                                    erlang:element(9, Session)
                                ))/binary>>/binary,
                            "|patch_ops="/utf8>>/binary,
                        (join_with(<<","/utf8>>, patch_operations(Session)))/binary>>/binary,
                    "|instructions="/utf8>>/binary,
                (join_with(
                    <<","/utf8>>,
                    gleam@list:map(
                        pushed_instructions(Session),
                        fun lightspeed@agent@isa:describe/1
                    )
                ))/binary>>/binary,
            "|telemetry="/utf8>>/binary,
        (join_with(<<","/utf8>>, telemetry(Session)))/binary>>.

-file("src/lightspeed/testing/liveview.gleam", 221).
?DOC(" Stable failure signature helper for assertions and fixture diagnostics.\n").
-spec failure_signature(
    session(any(), any(), any()),
    binary(),
    binary(),
    binary()
) -> binary().
failure_signature(Session, Assertion, Expected, Actual) ->
    <<<<<<<<<<<<<<"assertion="/utf8, Assertion/binary>>/binary,
                            "|expected="/utf8>>/binary,
                        Expected/binary>>/binary,
                    "|actual="/utf8>>/binary,
                Actual/binary>>/binary,
            "|"/utf8>>/binary,
        (stable_signature(Session))/binary>>.

-file("src/lightspeed/testing/liveview.gleam", 238).
?DOC(" Render one template story fixture with deterministic metadata.\n").
-spec render_story_fixture(lightspeed@component@story:story(any(), any())) -> story_fixture().
render_story_fixture(Fixture_story) ->
    Rendered = lightspeed@component@story:render(Fixture_story),
    Html = lightspeed@component:to_html(Rendered),
    Fingerprint = lightspeed@component:fingerprint(Rendered),
    Name = lightspeed@component@story:name(Fixture_story),
    {story_fixture,
        Name,
        Html,
        Fingerprint,
        <<<<<<<<<<"story="/utf8, Name/binary>>/binary, "|fingerprint="/utf8>>/binary,
                    Fingerprint/binary>>/binary,
                "|html="/utf8>>/binary,
            Html/binary>>}.

-file("src/lightspeed/testing/liveview.gleam", 260).
?DOC(" Fixture story name.\n").
-spec story_name(story_fixture()) -> binary().
story_name(Fixture) ->
    erlang:element(2, Fixture).

-file("src/lightspeed/testing/liveview.gleam", 265).
?DOC(" Fixture story HTML.\n").
-spec story_html(story_fixture()) -> binary().
story_html(Fixture) ->
    erlang:element(3, Fixture).

-file("src/lightspeed/testing/liveview.gleam", 270).
?DOC(" Fixture story fingerprint.\n").
-spec story_fingerprint(story_fixture()) -> binary().
story_fingerprint(Fixture) ->
    erlang:element(4, Fixture).

-file("src/lightspeed/testing/liveview.gleam", 275).
?DOC(" Fixture story stable signature.\n").
-spec story_signature(story_fixture()) -> binary().
story_signature(Fixture) ->
    erlang:element(5, Fixture).