src/lightspeed@diff.erl

-module(lightspeed@diff).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/diff.gleam").
-export([target/1, operation/1, carries_html/1, slot/2, keyed_node/2, keyed_patch_plan/3, encode_stream/1, encode/1, decode_stream/1, decode/1, decode_error_to_string/1]).
-export_type([dynamic_slot/0, keyed_node/0, patch/0, decode_error/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(" Patch model and codec for rendered updates.\n").

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

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

-type patch() :: {replace, binary(), binary()} |
    {append, binary(), binary()} |
    {prepend, binary(), binary()} |
    {remove, binary()} |
    {replace_segments, binary(), binary(), binary(), list(dynamic_slot())} |
    {update_segments, binary(), binary(), list(dynamic_slot())} |
    {upsert_keyed, binary(), binary(), binary()} |
    {remove_keyed, binary(), binary()} |
    {reorder_keyed, binary(), list(binary())}.

-type decode_error() :: empty_payload |
    {unknown_payload_tag, binary()} |
    {bad_field_count, binary(), integer(), integer()} |
    {invalid_integer, binary()} |
    {unsupported_version, integer()} |
    {missing_dictionary_entry, integer()} |
    {malformed_operation, binary()} |
    invalid_escape_sequence.

-file("src/lightspeed/diff.gleam", 54).
?DOC(" Return the target selector or id for a patch.\n").
-spec target(patch()) -> binary().
target(Patch) ->
    case Patch of
        {replace, Target, _} ->
            Target;

        {append, Target@1, _} ->
            Target@1;

        {prepend, Target@2, _} ->
            Target@2;

        {remove, Target@3} ->
            Target@3;

        {replace_segments, Target@4, _, _, _} ->
            Target@4;

        {update_segments, Target@5, _, _} ->
            Target@5;

        {upsert_keyed, Target@6, _, _} ->
            Target@6;

        {remove_keyed, Target@7, _} ->
            Target@7;

        {reorder_keyed, Target@8, _} ->
            Target@8
    end.

-file("src/lightspeed/diff.gleam", 69).
?DOC(" Return the patch operation name.\n").
-spec operation(patch()) -> binary().
operation(Patch) ->
    case Patch of
        {replace, _, _} ->
            <<"replace"/utf8>>;

        {append, _, _} ->
            <<"append"/utf8>>;

        {prepend, _, _} ->
            <<"prepend"/utf8>>;

        {remove, _} ->
            <<"remove"/utf8>>;

        {replace_segments, _, _, _, _} ->
            <<"replace_segments"/utf8>>;

        {update_segments, _, _, _} ->
            <<"update_segments"/utf8>>;

        {upsert_keyed, _, _, _} ->
            <<"upsert_keyed"/utf8>>;

        {remove_keyed, _, _} ->
            <<"remove_keyed"/utf8>>;

        {reorder_keyed, _, _} ->
            <<"reorder_keyed"/utf8>>
    end.

-file("src/lightspeed/diff.gleam", 84).
?DOC(" True when a patch carries HTML content.\n").
-spec carries_html(patch()) -> boolean().
carries_html(Patch) ->
    case Patch of
        {remove, _} ->
            false;

        {update_segments, _, _, _} ->
            false;

        {remove_keyed, _, _} ->
            false;

        {reorder_keyed, _, _} ->
            false;

        _ ->
            true
    end.

-file("src/lightspeed/diff.gleam", 95).
?DOC(" Build one dynamic slot value.\n").
-spec slot(binary(), binary()) -> dynamic_slot().
slot(Name, Value) ->
    {dynamic_slot, Name, Value}.

-file("src/lightspeed/diff.gleam", 100).
?DOC(" Build one keyed node.\n").
-spec keyed_node(binary(), binary()) -> keyed_node().
keyed_node(Key, Html) ->
    {keyed_node, Key, Html}.

-file("src/lightspeed/diff.gleam", 238).
-spec keyed_lookup(list(keyed_node()), binary()) -> {ok, binary()} |
    {error, nil}.
keyed_lookup(Nodes, Key) ->
    case Nodes of
        [] ->
            {error, nil};

        [{keyed_node, Node_key, Html} | Rest] ->
            case Node_key =:= Key of
                true ->
                    {ok, Html};

                false ->
                    keyed_lookup(Rest, Key)
            end
    end.

-file("src/lightspeed/diff.gleam", 220).
-spec keyed_removals(binary(), list(keyed_node()), list(keyed_node())) -> list(patch()).
keyed_removals(Target, Previous, Current) ->
    case Previous of
        [] ->
            [];

        [{keyed_node, Key, _} | Rest] ->
            case keyed_lookup(Current, Key) of
                {ok, _} ->
                    keyed_removals(Target, Rest, Current);

                {error, nil} ->
                    [{remove_keyed, Target, Key} |
                        keyed_removals(Target, Rest, Current)]
            end
    end.

-file("src/lightspeed/diff.gleam", 194).
-spec keyed_upserts(binary(), list(keyed_node()), list(keyed_node())) -> list(patch()).
keyed_upserts(Target, Previous, Current) ->
    case Current of
        [] ->
            [];

        [{keyed_node, Key, Html} | Rest] ->
            case keyed_lookup(Previous, Key) of
                {ok, Previous_html} ->
                    case Previous_html =:= Html of
                        true ->
                            keyed_upserts(Target, Previous, Rest);

                        false ->
                            [{upsert_keyed, Target, Key, Html} |
                                keyed_upserts(Target, Previous, Rest)]
                    end;

                {error, nil} ->
                    [{upsert_keyed, Target, Key, Html} |
                        keyed_upserts(Target, Previous, Rest)]
            end
    end.

-file("src/lightspeed/diff.gleam", 864).
-spec concat(list(FLQ), list(FLQ)) -> list(FLQ).
concat(Left, Right) ->
    case Left of
        [] ->
            Right;

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

-file("src/lightspeed/diff.gleam", 105).
?DOC(" Build a keyed update plan from previous and current collections.\n").
-spec keyed_patch_plan(binary(), list(keyed_node()), list(keyed_node())) -> list(patch()).
keyed_patch_plan(Target, Previous, Current) ->
    concat(
        keyed_upserts(Target, Previous, Current),
        keyed_removals(Target, Previous, Current)
    ).

-file("src/lightspeed/diff.gleam", 903).
-spec escape_chars(list(binary()), binary()) -> binary().
escape_chars(Chars, Acc) ->
    case Chars of
        [] ->
            Acc;

        [Char | Rest] ->
            case Char of
                <<"\\"/utf8>> ->
                    escape_chars(Rest, <<Acc/binary, "\\\\"/utf8>>);

                <<"|"/utf8>> ->
                    escape_chars(Rest, <<Acc/binary, "\\|"/utf8>>);

                _ ->
                    escape_chars(Rest, <<Acc/binary, Char/binary>>)
            end
    end.

-file("src/lightspeed/diff.gleam", 899).
-spec escape_field(binary()) -> binary().
escape_field(Value) ->
    escape_chars(gleam@string:to_graphemes(Value), <<""/utf8>>).

-file("src/lightspeed/diff.gleam", 892).
-spec join_fields_loop(list(binary()), binary()) -> binary().
join_fields_loop(Fields, Acc) ->
    case Fields of
        [] ->
            Acc;

        [Field | Rest] ->
            join_fields_loop(
                Rest,
                <<<<Acc/binary, "|"/utf8>>/binary,
                    (escape_field(Field))/binary>>
            )
    end.

-file("src/lightspeed/diff.gleam", 885).
-spec join_fields(list(binary())) -> binary().
join_fields(Fields) ->
    case Fields of
        [] ->
            <<""/utf8>>;

        [Field | Rest] ->
            join_fields_loop(Rest, escape_field(Field))
    end.

-file("src/lightspeed/diff.gleam", 443).
-spec index_of(list(binary()), binary(), integer()) -> integer().
index_of(Values, Target, Index) ->
    case Values of
        [] ->
            0;

        [Value | Rest] ->
            case Value =:= Target of
                true ->
                    Index;

                false ->
                    index_of(Rest, Target, Index + 1)
            end
    end.

-file("src/lightspeed/diff.gleam", 439).
-spec encode_index(list(binary()), binary()) -> binary().
encode_index(Dictionary, Value) ->
    erlang:integer_to_binary(index_of(Dictionary, Value, 0)).

-file("src/lightspeed/diff.gleam", 408).
-spec encode_key_tokens(list(binary()), list(binary()), list(binary())) -> list(binary()).
encode_key_tokens(Keys, Dictionary, Tokens_rev) ->
    case Keys of
        [] ->
            lists:reverse(Tokens_rev);

        [Key | Rest] ->
            encode_key_tokens(
                Rest,
                Dictionary,
                [encode_index(Dictionary, Key) | Tokens_rev]
            )
    end.

-file("src/lightspeed/diff.gleam", 878).
-spec join_tokens_loop(list(binary()), binary()) -> binary().
join_tokens_loop(Tokens, Acc) ->
    case Tokens of
        [] ->
            Acc;

        [Token | Rest] ->
            join_tokens_loop(
                Rest,
                <<<<Acc/binary, ","/utf8>>/binary, Token/binary>>
            )
    end.

-file("src/lightspeed/diff.gleam", 871).
-spec join_tokens(list(binary())) -> binary().
join_tokens(Tokens) ->
    case Tokens of
        [] ->
            <<""/utf8>>;

        [Token | Rest] ->
            join_tokens_loop(Rest, Token)
    end.

-file("src/lightspeed/diff.gleam", 423).
-spec encode_dynamic_slot_tokens(
    list(dynamic_slot()),
    list(binary()),
    list(binary())
) -> list(binary()).
encode_dynamic_slot_tokens(Dynamic_slots, Dictionary, Tokens_rev) ->
    case Dynamic_slots of
        [] ->
            lists:reverse(Tokens_rev);

        [{dynamic_slot, Name, Value} | Rest] ->
            encode_dynamic_slot_tokens(
                Rest,
                Dictionary,
                [encode_index(Dictionary, Value),
                    encode_index(Dictionary, Name) |
                    Tokens_rev]
            )
    end.

-file("src/lightspeed/diff.gleam", 333).
-spec encode_operation(patch(), list(binary())) -> binary().
encode_operation(Patch, Dictionary) ->
    case Patch of
        {replace, Target, Html} ->
            join_tokens(
                [<<"r"/utf8>>,
                    encode_index(Dictionary, Target),
                    encode_index(Dictionary, Html)]
            );

        {append, Target@1, Html@1} ->
            join_tokens(
                [<<"a"/utf8>>,
                    encode_index(Dictionary, Target@1),
                    encode_index(Dictionary, Html@1)]
            );

        {prepend, Target@2, Html@2} ->
            join_tokens(
                [<<"p"/utf8>>,
                    encode_index(Dictionary, Target@2),
                    encode_index(Dictionary, Html@2)]
            );

        {remove, Target@3} ->
            join_tokens([<<"x"/utf8>>, encode_index(Dictionary, Target@3)]);

        {replace_segments, Target@4, Fingerprint, Static_html, Dynamic_slots} ->
            join_tokens(
                concat(
                    [<<"s"/utf8>>,
                        encode_index(Dictionary, Target@4),
                        encode_index(Dictionary, Fingerprint),
                        encode_index(Dictionary, Static_html),
                        erlang:integer_to_binary(erlang:length(Dynamic_slots))],
                    encode_dynamic_slot_tokens(Dynamic_slots, Dictionary, [])
                )
            );

        {update_segments, Target@5, Fingerprint@1, Dynamic_slots@1} ->
            join_tokens(
                concat(
                    [<<"u"/utf8>>,
                        encode_index(Dictionary, Target@5),
                        encode_index(Dictionary, Fingerprint@1),
                        erlang:integer_to_binary(erlang:length(Dynamic_slots@1))],
                    encode_dynamic_slot_tokens(Dynamic_slots@1, Dictionary, [])
                )
            );

        {upsert_keyed, Target@6, Key, Html@3} ->
            join_tokens(
                [<<"k"/utf8>>,
                    encode_index(Dictionary, Target@6),
                    encode_index(Dictionary, Key),
                    encode_index(Dictionary, Html@3)]
            );

        {remove_keyed, Target@7, Key@1} ->
            join_tokens(
                [<<"q"/utf8>>,
                    encode_index(Dictionary, Target@7),
                    encode_index(Dictionary, Key@1)]
            );

        {reorder_keyed, Target@8, Keys} ->
            join_tokens(
                concat(
                    [<<"o"/utf8>>,
                        encode_index(Dictionary, Target@8),
                        erlang:integer_to_binary(erlang:length(Keys))],
                    encode_key_tokens(Keys, Dictionary, [])
                )
            )
    end.

-file("src/lightspeed/diff.gleam", 318).
-spec encode_operations(list(patch()), list(binary()), list(binary())) -> list(binary()).
encode_operations(Patches, Dictionary, Operation_fields_rev) ->
    case Patches of
        [] ->
            lists:reverse(Operation_fields_rev);

        [Patch | Rest] ->
            encode_operations(
                Rest,
                Dictionary,
                [encode_operation(Patch, Dictionary) | Operation_fields_rev]
            )
    end.

-file("src/lightspeed/diff.gleam", 279).
-spec dynamic_slot_strings(list(dynamic_slot())) -> list(binary()).
dynamic_slot_strings(Dynamic_slots) ->
    case Dynamic_slots of
        [] ->
            [];

        [{dynamic_slot, Name, Value} | Rest] ->
            [Name, Value | dynamic_slot_strings(Rest)]
    end.

-file("src/lightspeed/diff.gleam", 260).
-spec patch_strings(patch()) -> list(binary()).
patch_strings(Patch) ->
    case Patch of
        {replace, Target, Html} ->
            [Target, Html];

        {append, Target@1, Html@1} ->
            [Target@1, Html@1];

        {prepend, Target@2, Html@2} ->
            [Target@2, Html@2];

        {remove, Target@3} ->
            [Target@3];

        {replace_segments, Target@4, Fingerprint, Static_html, Dynamic_slots} ->
            concat(
                [Target@4, Fingerprint, Static_html],
                dynamic_slot_strings(Dynamic_slots)
            );

        {update_segments, Target@5, Fingerprint@1, Dynamic_slots@1} ->
            concat(
                [Target@5, Fingerprint@1],
                dynamic_slot_strings(Dynamic_slots@1)
            );

        {upsert_keyed, Target@6, Key, Html@3} ->
            [Target@6, Key, Html@3];

        {remove_keyed, Target@7, Key@1} ->
            [Target@7, Key@1];

        {reorder_keyed, Target@8, Keys} ->
            concat([Target@8], Keys)
    end.

-file("src/lightspeed/diff.gleam", 307).
-spec contains_string(list(binary()), binary()) -> boolean().
contains_string(Values, Target) ->
    case Values of
        [] ->
            false;

        [Value | Rest] ->
            case Value =:= Target of
                true ->
                    true;

                false ->
                    contains_string(Rest, Target)
            end
    end.

-file("src/lightspeed/diff.gleam", 300).
-spec add_unique(list(binary()), binary()) -> list(binary()).
add_unique(Dictionary, Value) ->
    case contains_string(Dictionary, Value) of
        true ->
            Dictionary;

        false ->
            concat(Dictionary, [Value])
    end.

-file("src/lightspeed/diff.gleam", 290).
-spec add_all_unique(list(binary()), list(binary())) -> list(binary()).
add_all_unique(Dictionary, Values) ->
    case Values of
        [] ->
            Dictionary;

        [Value | Rest] ->
            add_all_unique(add_unique(Dictionary, Value), Rest)
    end.

-file("src/lightspeed/diff.gleam", 249).
-spec build_dictionary(list(patch()), list(binary())) -> list(binary()).
build_dictionary(Patches, Dictionary) ->
    case Patches of
        [] ->
            Dictionary;

        [Patch | Rest] ->
            build_dictionary(
                Rest,
                add_all_unique(Dictionary, patch_strings(Patch))
            )
    end.

-file("src/lightspeed/diff.gleam", 122).
?DOC(" Encode a patch stream with dictionary compression.\n").
-spec encode_stream(list(patch())) -> binary().
encode_stream(Patches) ->
    Dictionary = build_dictionary(Patches, []),
    Operation_fields = encode_operations(Patches, Dictionary, []),
    Fields = concat(
        concat(
            concat(
                [<<"ps"/utf8>>,
                    erlang:integer_to_binary(1),
                    erlang:integer_to_binary(erlang:length(Dictionary))],
                Dictionary
            ),
            [erlang:integer_to_binary(erlang:length(Operation_fields))]
        ),
        Operation_fields
    ),
    join_fields(Fields).

-file("src/lightspeed/diff.gleam", 117).
?DOC(" Encode one patch into a versioned compressed stream.\n").
-spec encode(patch()) -> binary().
encode(Patch) ->
    encode_stream([Patch]).

-file("src/lightspeed/diff.gleam", 839).
-spec dictionary_value(list(binary()), integer()) -> {ok, binary()} |
    {error, decode_error()}.
dictionary_value(Dictionary, Index) ->
    case Index < 0 of
        true ->
            {error, {missing_dictionary_entry, Index}};

        false ->
            case Dictionary of
                [] ->
                    {error, {missing_dictionary_entry, Index}};

                [Entry | Rest] ->
                    case Index =:= 0 of
                        true ->
                            {ok, Entry};

                        false ->
                            dictionary_value(Rest, Index - 1)
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 857).
-spec parse_int(binary()) -> {ok, integer()} | {error, decode_error()}.
parse_int(Value) ->
    case gleam_stdlib:parse_int(Value) of
        {ok, Parsed} ->
            {ok, Parsed};

        {error, _} ->
            {error, {invalid_integer, Value}}
    end.

-file("src/lightspeed/diff.gleam", 829).
-spec decode_dictionary_value(binary(), list(binary())) -> {ok, binary()} |
    {error, decode_error()}.
decode_dictionary_value(Index_text, Dictionary) ->
    case parse_int(Index_text) of
        {error, Error} ->
            {error, Error};

        {ok, Index} ->
            dictionary_value(Dictionary, Index)
    end.

-file("src/lightspeed/diff.gleam", 803).
-spec decode_dynamic_slot_pairs(
    list(binary()),
    list(binary()),
    list(dynamic_slot())
) -> {ok, list(dynamic_slot())} | {error, decode_error()}.
decode_dynamic_slot_pairs(Slot_tokens, Dictionary, Dynamic_slots_rev) ->
    case Slot_tokens of
        [] ->
            {ok, lists:reverse(Dynamic_slots_rev)};

        [Name_index, Value_index | Rest] ->
            case decode_dictionary_value(Name_index, Dictionary) of
                {error, Error} ->
                    {error, Error};

                {ok, Name} ->
                    case decode_dictionary_value(Value_index, Dictionary) of
                        {error, Error@1} ->
                            {error, Error@1};

                        {ok, Value} ->
                            decode_dynamic_slot_pairs(
                                Rest,
                                Dictionary,
                                [{dynamic_slot, Name, Value} |
                                    Dynamic_slots_rev]
                            )
                    end
            end;

        _ ->
            {error, {malformed_operation, <<"dynamic_slot_pair"/utf8>>}}
    end.

-file("src/lightspeed/diff.gleam", 783).
-spec decode_dynamic_slots(binary(), list(binary()), list(binary())) -> {ok,
        list(dynamic_slot())} |
    {error, decode_error()}.
decode_dynamic_slots(Slot_count_text, Slot_tokens, Dictionary) ->
    case parse_int(Slot_count_text) of
        {error, Error} ->
            {error, Error};

        {ok, Slot_count} ->
            case erlang:length(Slot_tokens) =:= (Slot_count * 2) of
                false ->
                    {error,
                        {bad_field_count,
                            <<"dynamic_slots"/utf8>>,
                            Slot_count * 2,
                            erlang:length(Slot_tokens)}};

                true ->
                    decode_dynamic_slot_pairs(Slot_tokens, Dictionary, [])
            end
    end.

-file("src/lightspeed/diff.gleam", 757).
-spec decode_update_segment_patch(
    binary(),
    binary(),
    binary(),
    list(binary()),
    list(binary())
) -> {ok, patch()} | {error, decode_error()}.
decode_update_segment_patch(
    Target_index,
    Fingerprint_index,
    Slot_count_text,
    Slot_tokens,
    Dictionary
) ->
    case decode_dictionary_value(Target_index, Dictionary) of
        {error, Error} ->
            {error, Error};

        {ok, Target} ->
            case decode_dictionary_value(Fingerprint_index, Dictionary) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Fingerprint} ->
                    case decode_dynamic_slots(
                        Slot_count_text,
                        Slot_tokens,
                        Dictionary
                    ) of
                        {error, Error@2} ->
                            {error, Error@2};

                        {ok, Dynamic_slots} ->
                            {ok,
                                {update_segments,
                                    Target,
                                    Fingerprint,
                                    Dynamic_slots}}
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 723).
-spec decode_segment_patch(
    binary(),
    binary(),
    binary(),
    binary(),
    list(binary()),
    list(binary())
) -> {ok, patch()} | {error, decode_error()}.
decode_segment_patch(
    Target_index,
    Fingerprint_index,
    Static_index,
    Slot_count_text,
    Slot_tokens,
    Dictionary
) ->
    case decode_dictionary_value(Target_index, Dictionary) of
        {error, Error} ->
            {error, Error};

        {ok, Target} ->
            case decode_dictionary_value(Fingerprint_index, Dictionary) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Fingerprint} ->
                    case decode_dictionary_value(Static_index, Dictionary) of
                        {error, Error@2} ->
                            {error, Error@2};

                        {ok, Static_html} ->
                            case decode_dynamic_slots(
                                Slot_count_text,
                                Slot_tokens,
                                Dictionary
                            ) of
                                {error, Error@3} ->
                                    {error, Error@3};

                                {ok, Dynamic_slots} ->
                                    {ok,
                                        {replace_segments,
                                            Target,
                                            Fingerprint,
                                            Static_html,
                                            Dynamic_slots}}
                            end
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 679).
-spec decode_key_indexes(list(binary()), list(binary()), list(binary())) -> {ok,
        list(binary())} |
    {error, decode_error()}.
decode_key_indexes(Key_indexes, Dictionary, Keys_rev) ->
    case Key_indexes of
        [] ->
            {ok, lists:reverse(Keys_rev)};

        [Key_index | Rest] ->
            case decode_dictionary_value(Key_index, Dictionary) of
                {error, Error} ->
                    {error, Error};

                {ok, Key} ->
                    decode_key_indexes(Rest, Dictionary, [Key | Keys_rev])
            end
    end.

-file("src/lightspeed/diff.gleam", 650).
-spec decode_reorder_patch(binary(), binary(), list(binary()), list(binary())) -> {ok,
        patch()} |
    {error, decode_error()}.
decode_reorder_patch(Target_index, Key_count_text, Key_indexes, Dictionary) ->
    case decode_dictionary_value(Target_index, Dictionary) of
        {error, Error} ->
            {error, Error};

        {ok, Target} ->
            case parse_int(Key_count_text) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Key_count} ->
                    case erlang:length(Key_indexes) =:= Key_count of
                        false ->
                            {error,
                                {bad_field_count,
                                    <<"reorder_keyed_keys"/utf8>>,
                                    Key_count,
                                    erlang:length(Key_indexes)}};

                        true ->
                            case decode_key_indexes(Key_indexes, Dictionary, []) of
                                {error, Error@2} ->
                                    {error, Error@2};

                                {ok, Keys} ->
                                    {ok, {reorder_keyed, Target, Keys}}
                            end
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 712).
-spec map_patch_name_error({ok, patch()} | {error, decode_error()}, binary()) -> {ok,
        patch()} |
    {error, decode_error()}.
map_patch_name_error(Result, Patch_name) ->
    case Result of
        {ok, Patch} ->
            {ok, Patch};

        {error, {malformed_operation, _}} ->
            {error, {malformed_operation, Patch_name}};

        {error, Error} ->
            {error, Error}
    end.

-file("src/lightspeed/diff.gleam", 694).
-spec decode_binary_patch(
    binary(),
    binary(),
    binary(),
    list(binary()),
    fun((binary(), binary()) -> patch())
) -> {ok, patch()} | {error, decode_error()}.
decode_binary_patch(
    Patch_name,
    Target_index,
    Html_index,
    Dictionary,
    Constructor
) ->
    _pipe = case decode_dictionary_value(Target_index, Dictionary) of
        {error, Error} ->
            {error, Error};

        {ok, Target} ->
            case decode_dictionary_value(Html_index, Dictionary) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Html} ->
                    {ok, Constructor(Target, Html)}
            end
    end,
    map_patch_name_error(_pipe, Patch_name).

-file("src/lightspeed/diff.gleam", 552).
-spec decode_operation(binary(), list(binary())) -> {ok, patch()} |
    {error, decode_error()}.
decode_operation(Operation_field, Dictionary) ->
    Tokens = gleam@string:split(Operation_field, <<","/utf8>>),
    case Tokens of
        [<<"r"/utf8>>, Target_index, Html_index] ->
            decode_binary_patch(
                <<"replace"/utf8>>,
                Target_index,
                Html_index,
                Dictionary,
                fun(Field@0, Field@1) -> {replace, Field@0, Field@1} end
            );

        [<<"a"/utf8>>, Target_index@1, Html_index@1] ->
            decode_binary_patch(
                <<"append"/utf8>>,
                Target_index@1,
                Html_index@1,
                Dictionary,
                fun(Field@0, Field@1) -> {append, Field@0, Field@1} end
            );

        [<<"p"/utf8>>, Target_index@2, Html_index@2] ->
            decode_binary_patch(
                <<"prepend"/utf8>>,
                Target_index@2,
                Html_index@2,
                Dictionary,
                fun(Field@0, Field@1) -> {prepend, Field@0, Field@1} end
            );

        [<<"x"/utf8>>, Target_index@3] ->
            case decode_dictionary_value(Target_index@3, Dictionary) of
                {error, Error} ->
                    {error, Error};

                {ok, Target} ->
                    {ok, {remove, Target}}
            end;

        [<<"k"/utf8>>, Target_index@4, Key_index, Html_index@3] ->
            case decode_dictionary_value(Target_index@4, Dictionary) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, Target@1} ->
                    case decode_dictionary_value(Key_index, Dictionary) of
                        {error, Error@2} ->
                            {error, Error@2};

                        {ok, Key} ->
                            case decode_dictionary_value(
                                Html_index@3,
                                Dictionary
                            ) of
                                {error, Error@3} ->
                                    {error, Error@3};

                                {ok, Html} ->
                                    {ok, {upsert_keyed, Target@1, Key, Html}}
                            end
                    end
            end;

        [<<"q"/utf8>>, Target_index@5, Key_index@1] ->
            case decode_dictionary_value(Target_index@5, Dictionary) of
                {error, Error@4} ->
                    {error, Error@4};

                {ok, Target@2} ->
                    case decode_dictionary_value(Key_index@1, Dictionary) of
                        {error, Error@5} ->
                            {error, Error@5};

                        {ok, Key@1} ->
                            {ok, {remove_keyed, Target@2, Key@1}}
                    end
            end;

        [<<"o"/utf8>>, Target_index@6, Key_count | Key_indexes] ->
            decode_reorder_patch(
                Target_index@6,
                Key_count,
                Key_indexes,
                Dictionary
            );

        [<<"s"/utf8>>,
            Target_index@7,
            Fingerprint_index,
            Static_index,
            Slot_count |
            Slot_tokens] ->
            decode_segment_patch(
                Target_index@7,
                Fingerprint_index,
                Static_index,
                Slot_count,
                Slot_tokens,
                Dictionary
            );

        [<<"u"/utf8>>,
            Target_index@8,
            Fingerprint_index@1,
            Slot_count@1 |
            Slot_tokens@1] ->
            decode_update_segment_patch(
                Target_index@8,
                Fingerprint_index@1,
                Slot_count@1,
                Slot_tokens@1,
                Dictionary
            );

        _ ->
            {error, {malformed_operation, Operation_field}}
    end.

-file("src/lightspeed/diff.gleam", 537).
-spec decode_operations(list(binary()), list(binary()), list(patch())) -> {ok,
        list(patch())} |
    {error, decode_error()}.
decode_operations(Operation_fields, Dictionary, Patches_rev) ->
    case Operation_fields of
        [] ->
            {ok, lists:reverse(Patches_rev)};

        [Operation_field | Rest] ->
            case decode_operation(Operation_field, Dictionary) of
                {error, Error} ->
                    {error, Error};

                {ok, Patch} ->
                    decode_operations(Rest, Dictionary, [Patch | Patches_rev])
            end
    end.

-file("src/lightspeed/diff.gleam", 518).
-spec split_n(list(binary()), integer(), list(binary())) -> {ok,
        {list(binary()), list(binary())}} |
    {error, decode_error()}.
split_n(Values, Count, Acc_rev) ->
    case Count < 0 of
        true ->
            {error, {malformed_operation, <<"negative_count"/utf8>>}};

        false ->
            case Count of
                0 ->
                    {ok, {lists:reverse(Acc_rev), Values}};

                _ ->
                    case Values of
                        [] ->
                            {error,
                                {malformed_operation,
                                    <<"insufficient_fields"/utf8>>}};

                        [Value | Rest] ->
                            split_n(Rest, Count - 1, [Value | Acc_rev])
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 481).
-spec decode_dictionary_and_operations(binary(), list(binary())) -> {ok,
        list(patch())} |
    {error, decode_error()}.
decode_dictionary_and_operations(Dictionary_count_text, Rest_fields) ->
    case parse_int(Dictionary_count_text) of
        {error, Error} ->
            {error, Error};

        {ok, Dictionary_count} ->
            case split_n(Rest_fields, Dictionary_count, []) of
                {error, Error@1} ->
                    {error, Error@1};

                {ok, {Dictionary, Operation_count_and_fields}} ->
                    case Operation_count_and_fields of
                        [Operation_count_text | Operation_fields] ->
                            case parse_int(Operation_count_text) of
                                {error, Error@2} ->
                                    {error, Error@2};

                                {ok, Operation_count} ->
                                    case erlang:length(Operation_fields) =:= Operation_count of
                                        false ->
                                            {error,
                                                {bad_field_count,
                                                    <<"operation_fields"/utf8>>,
                                                    Operation_count,
                                                    erlang:length(
                                                        Operation_fields
                                                    )}};

                                        true ->
                                            decode_operations(
                                                Operation_fields,
                                                Dictionary,
                                                []
                                            )
                                    end
                            end;

                        _ ->
                            {error,
                                {bad_field_count,
                                    <<"operation_count"/utf8>>,
                                    1,
                                    0}}
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 454).
-spec decode_fields(list(binary())) -> {ok, list(patch())} |
    {error, decode_error()}.
decode_fields(Fields) ->
    case Fields of
        [Tag, Version_text, Dictionary_count_text | Rest] ->
            case Tag of
                <<"ps"/utf8>> ->
                    case parse_int(Version_text) of
                        {error, Error} ->
                            {error, Error};

                        {ok, Version} ->
                            case Version =:= 1 of
                                false ->
                                    {error, {unsupported_version, Version}};

                                true ->
                                    decode_dictionary_and_operations(
                                        Dictionary_count_text,
                                        Rest
                                    )
                            end
                    end;

                _ ->
                    {error, {unknown_payload_tag, Tag}}
            end;

        _ ->
            {error,
                {bad_field_count,
                    <<"stream_header"/utf8>>,
                    3,
                    erlang:length(Fields)}}
    end.

-file("src/lightspeed/diff.gleam", 919).
-spec split_chars(list(binary()), binary(), list(binary()), boolean()) -> {ok,
        list(binary())} |
    {error, decode_error()}.
split_chars(Chars, Current, Fields_rev, Escaped) ->
    case Chars of
        [] ->
            case Escaped of
                true ->
                    {error, invalid_escape_sequence};

                false ->
                    {ok, lists:reverse([Current | Fields_rev])}
            end;

        [Char | Rest] ->
            case Escaped of
                true ->
                    split_chars(
                        Rest,
                        <<Current/binary, Char/binary>>,
                        Fields_rev,
                        false
                    );

                false ->
                    case Char of
                        <<"\\"/utf8>> ->
                            split_chars(Rest, Current, Fields_rev, true);

                        <<"|"/utf8>> ->
                            split_chars(
                                Rest,
                                <<""/utf8>>,
                                [Current | Fields_rev],
                                false
                            );

                        _ ->
                            split_chars(
                                Rest,
                                <<Current/binary, Char/binary>>,
                                Fields_rev,
                                false
                            )
                    end
            end
    end.

-file("src/lightspeed/diff.gleam", 915).
-spec split_fields(binary()) -> {ok, list(binary())} | {error, decode_error()}.
split_fields(Payload) ->
    split_chars(gleam@string:to_graphemes(Payload), <<""/utf8>>, [], false).

-file("src/lightspeed/diff.gleam", 161).
?DOC(" Decode a patch stream.\n").
-spec decode_stream(binary()) -> {ok, list(patch())} | {error, decode_error()}.
decode_stream(Payload) ->
    case Payload of
        <<""/utf8>> ->
            {error, empty_payload};

        _ ->
            case split_fields(Payload) of
                {error, Error} ->
                    {error, Error};

                {ok, Fields} ->
                    decode_fields(Fields)
            end
    end.

-file("src/lightspeed/diff.gleam", 146).
?DOC(" Decode one patch from a versioned compressed stream.\n").
-spec decode(binary()) -> {ok, patch()} | {error, decode_error()}.
decode(Payload) ->
    case decode_stream(Payload) of
        {error, Error} ->
            {error, Error};

        {ok, Patches} ->
            case Patches of
                [Patch] ->
                    {ok, Patch};

                _ ->
                    {error,
                        {malformed_operation,
                            <<"expected_single_patch:"/utf8,
                                (erlang:integer_to_binary(
                                    erlang:length(Patches)
                                ))/binary>>}}
            end
    end.

-file("src/lightspeed/diff.gleam", 173).
?DOC(" Stable error labels for logs and adapter failures.\n").
-spec decode_error_to_string(decode_error()) -> binary().
decode_error_to_string(Error) ->
    case Error of
        empty_payload ->
            <<"empty_payload"/utf8>>;

        {unknown_payload_tag, Tag} ->
            <<"unknown_payload_tag:"/utf8, Tag/binary>>;

        {bad_field_count, Stage, Expected, Actual} ->
            <<<<<<<<<<"bad_field_count:"/utf8, Stage/binary>>/binary, ":"/utf8>>/binary,
                        (erlang:integer_to_binary(Expected))/binary>>/binary,
                    ":"/utf8>>/binary,
                (erlang:integer_to_binary(Actual))/binary>>;

        {invalid_integer, Value} ->
            <<"invalid_integer:"/utf8, Value/binary>>;

        {unsupported_version, Version} ->
            <<"unsupported_version:"/utf8,
                (erlang:integer_to_binary(Version))/binary>>;

        {missing_dictionary_entry, Index} ->
            <<"missing_dictionary_entry:"/utf8,
                (erlang:integer_to_binary(Index))/binary>>;

        {malformed_operation, Reason} ->
            <<"malformed_operation:"/utf8, Reason/binary>>;

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