src/gliff@internal@unified.erl

-module(gliff@internal@unified).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gliff/internal/unified.gleam").
-export([edits_to_hunks/2, to_unified/3, to_unified_with/4, from_unified/1]).
-export_type([indexed_edit/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(false).

-type indexed_edit() :: {indexed_edit, gliff@types:edit(), integer(), integer()}.

-file("src/gliff/internal/unified.gleam", 62).
?DOC(false).
-spec edit_size(gliff@types:edit()) -> {integer(), integer()}.
edit_size(Edit) ->
    case Edit of
        {equal, Lines} ->
            N = erlang:length(Lines),
            {N, N};

        {delete, Lines@1} ->
            {erlang:length(Lines@1), 0};

        {insert, Lines@2} ->
            {0, erlang:length(Lines@2)}
    end.

-file("src/gliff/internal/unified.gleam", 43).
?DOC(false).
-spec index_edits(
    list(gliff@types:edit()),
    integer(),
    integer(),
    list(indexed_edit())
) -> list(indexed_edit()).
index_edits(Edits, Old_pos, New_pos, Acc) ->
    case Edits of
        [] ->
            lists:reverse(Acc);

        [Edit | Rest] ->
            Entry = {indexed_edit, Edit, Old_pos, New_pos},
            {Old_advance, New_advance} = edit_size(Edit),
            index_edits(
                Rest,
                Old_pos + Old_advance,
                New_pos + New_advance,
                [Entry | Acc]
            )
    end.

-file("src/gliff/internal/unified.gleam", 188).
?DOC(false).
-spec has_changes(list(indexed_edit())) -> boolean().
has_changes(Indexed) ->
    case Indexed of
        [] ->
            false;

        [Entry | Rest] ->
            case erlang:element(2, Entry) of
                {equal, _} ->
                    has_changes(Rest);

                _ ->
                    true
            end
    end.

-file("src/gliff/internal/unified.gleam", 213).
?DOC(false).
-spec count_lines_in_edits(list(gliff@types:edit()), integer(), integer()) -> {integer(),
    integer()}.
count_lines_in_edits(Edits, Old_acc, New_acc) ->
    case Edits of
        [] ->
            {Old_acc, New_acc};

        [Edit | Rest] ->
            {O, N} = edit_size(Edit),
            count_lines_in_edits(Rest, Old_acc + O, New_acc + N)
    end.

-file("src/gliff/internal/unified.gleam", 199).
?DOC(false).
-spec build_single_hunk(list(indexed_edit())) -> gliff@types:hunk().
build_single_hunk(Entries) ->
    case Entries of
        [] ->
            {hunk, 1, 0, 1, 0, []};

        [First | _] ->
            Edits = gleam@list:map(Entries, fun(E) -> erlang:element(2, E) end),
            Old_start = erlang:element(3, First) + 1,
            New_start = erlang:element(4, First) + 1,
            {Old_count, New_count} = count_lines_in_edits(Edits, 0, 0),
            {hunk, Old_start, Old_count, New_start, New_count, Edits}
    end.

-file("src/gliff/internal/unified.gleam", 244).
?DOC(false).
-spec format_edit(gliff@types:edit()) -> binary().
format_edit(Edit) ->
    case Edit of
        {equal, Lines} ->
            gleam@string:join(
                gleam@list:map(
                    Lines,
                    fun(L) -> <<<<" "/utf8, L/binary>>/binary, "\n"/utf8>> end
                ),
                <<""/utf8>>
            );

        {delete, Lines@1} ->
            gleam@string:join(
                gleam@list:map(
                    Lines@1,
                    fun(L@1) ->
                        <<<<"-"/utf8, L@1/binary>>/binary, "\n"/utf8>>
                    end
                ),
                <<""/utf8>>
            );

        {insert, Lines@2} ->
            gleam@string:join(
                gleam@list:map(
                    Lines@2,
                    fun(L@2) ->
                        <<<<"+"/utf8, L@2/binary>>/binary, "\n"/utf8>>
                    end
                ),
                <<""/utf8>>
            )
    end.

-file("src/gliff/internal/unified.gleam", 268).
?DOC(false).
-spec digit_to_string(integer()) -> binary().
digit_to_string(D) ->
    case D of
        0 ->
            <<"0"/utf8>>;

        1 ->
            <<"1"/utf8>>;

        2 ->
            <<"2"/utf8>>;

        3 ->
            <<"3"/utf8>>;

        4 ->
            <<"4"/utf8>>;

        5 ->
            <<"5"/utf8>>;

        6 ->
            <<"6"/utf8>>;

        7 ->
            <<"7"/utf8>>;

        8 ->
            <<"8"/utf8>>;

        _ ->
            <<"9"/utf8>>
    end.

-file("src/gliff/internal/unified.gleam", 261).
?DOC(false).
-spec do_int_to_string(integer()) -> binary().
do_int_to_string(N) ->
    case N < 10 of
        true ->
            digit_to_string(N);

        false ->
            <<(do_int_to_string(N div 10))/binary,
                (digit_to_string(N rem 10))/binary>>
    end.

-file("src/gliff/internal/unified.gleam", 254).
?DOC(false).
-spec int_to_string(integer()) -> binary().
int_to_string(N) ->
    case N < 0 of
        true ->
            <<"-"/utf8, (do_int_to_string(- N))/binary>>;

        false ->
            do_int_to_string(N)
    end.

-file("src/gliff/internal/unified.gleam", 229).
?DOC(false).
-spec format_hunk(gliff@types:hunk()) -> binary().
format_hunk(Hunk) ->
    Header = <<<<<<<<<<<<<<<<"@@ -"/utf8,
                                    (int_to_string(erlang:element(2, Hunk)))/binary>>/binary,
                                ","/utf8>>/binary,
                            (int_to_string(erlang:element(3, Hunk)))/binary>>/binary,
                        " +"/utf8>>/binary,
                    (int_to_string(erlang:element(4, Hunk)))/binary>>/binary,
                ","/utf8>>/binary,
            (int_to_string(erlang:element(5, Hunk)))/binary>>/binary,
        " @@\n"/utf8>>,
    Body = gleam@string:join(
        gleam@list:map(erlang:element(6, Hunk), fun format_edit/1),
        <<""/utf8>>
    ),
    <<Header/binary, Body/binary>>.

-file("src/gliff/internal/unified.gleam", 283).
?DOC(false).
-spec take_last(list(DXR), integer()) -> list(DXR).
take_last(Items, N) ->
    Len = erlang:length(Items),
    gleam@list:drop(Items, case Len > N of
            true ->
                Len - N;

            false ->
                0
        end).

-file("src/gliff/internal/unified.gleam", 85).
?DOC(false).
-spec find_change_groups_loop(
    list(indexed_edit()),
    integer(),
    list(indexed_edit()),
    list(list(indexed_edit()))
) -> list(list(indexed_edit())).
find_change_groups_loop(Indexed, Context, Current_group, Groups) ->
    case Indexed of
        [] ->
            case Current_group of
                [] ->
                    lists:reverse(Groups);

                _ ->
                    lists:reverse([lists:reverse(Current_group) | Groups])
            end;

        [Entry | Rest] ->
            case erlang:element(2, Entry) of
                {equal, Lines} ->
                    N = erlang:length(Lines),
                    case Current_group of
                        [] ->
                            Ctx_lines = take_last(Lines, Context),
                            Ctx_count = erlang:length(Ctx_lines),
                            case Ctx_count > 0 of
                                true ->
                                    Ctx_entry = {indexed_edit,
                                        {equal, Ctx_lines},
                                        (erlang:element(3, Entry) + N) - Ctx_count,
                                        (erlang:element(4, Entry) + N) - Ctx_count},
                                    find_change_groups_loop(
                                        Rest,
                                        Context,
                                        [Ctx_entry],
                                        Groups
                                    );

                                false ->
                                    find_change_groups_loop(
                                        Rest,
                                        Context,
                                        Current_group,
                                        Groups
                                    )
                            end;

                        _ ->
                            Has_more_changes = has_changes(Rest),
                            case {Has_more_changes, N > (Context * 2)} of
                                {_, true} ->
                                    Tail_ctx = gleam@list:take(Lines, Context),
                                    Tail_entry = {indexed_edit,
                                        {equal, Tail_ctx},
                                        erlang:element(3, Entry),
                                        erlang:element(4, Entry)},
                                    Finished_group = lists:reverse(
                                        [Tail_entry | Current_group]
                                    ),
                                    Head_ctx = take_last(Lines, Context),
                                    Head_count = erlang:length(Head_ctx),
                                    Head_entry = {indexed_edit,
                                        {equal, Head_ctx},
                                        (erlang:element(3, Entry) + N) - Head_count,
                                        (erlang:element(4, Entry) + N) - Head_count},
                                    find_change_groups_loop(
                                        Rest,
                                        Context,
                                        [Head_entry],
                                        [Finished_group | Groups]
                                    );

                                {false, _} when N > Context ->
                                    Tail_ctx@1 = gleam@list:take(Lines, Context),
                                    Tail_entry@1 = {indexed_edit,
                                        {equal, Tail_ctx@1},
                                        erlang:element(3, Entry),
                                        erlang:element(4, Entry)},
                                    Finished_group@1 = lists:reverse(
                                        [Tail_entry@1 | Current_group]
                                    ),
                                    find_change_groups_loop(
                                        Rest,
                                        Context,
                                        [],
                                        [Finished_group@1 | Groups]
                                    );

                                {_, _} ->
                                    find_change_groups_loop(
                                        Rest,
                                        Context,
                                        [Entry | Current_group],
                                        Groups
                                    )
                            end
                    end;

                _ ->
                    case Current_group of
                        [] ->
                            find_change_groups_loop(
                                Rest,
                                Context,
                                [Entry],
                                Groups
                            );

                        _ ->
                            find_change_groups_loop(
                                Rest,
                                Context,
                                [Entry | Current_group],
                                Groups
                            )
                    end
            end
    end.

-file("src/gliff/internal/unified.gleam", 78).
?DOC(false).
-spec find_change_groups(list(indexed_edit()), integer()) -> list(list(indexed_edit())).
find_change_groups(Indexed, Context) ->
    find_change_groups_loop(Indexed, Context, [], []).

-file("src/gliff/internal/unified.gleam", 73).
?DOC(false).
-spec build_hunks(list(indexed_edit()), integer()) -> list(gliff@types:hunk()).
build_hunks(Indexed, Context) ->
    Change_groups = find_change_groups(Indexed, Context),
    gleam@list:map(Change_groups, fun(Group) -> build_single_hunk(Group) end).

-file("src/gliff/internal/unified.gleam", 34).
?DOC(false).
-spec edits_to_hunks(list(gliff@types:edit()), integer()) -> list(gliff@types:hunk()).
edits_to_hunks(Edits, Context) ->
    Indexed = index_edits(Edits, 0, 0, []),
    build_hunks(Indexed, Context).

-file("src/gliff/internal/unified.gleam", 5).
?DOC(false).
-spec to_unified(list(gliff@types:edit()), binary(), binary()) -> binary().
to_unified(Edits, Old_name, New_name) ->
    Hunks = edits_to_hunks(Edits, 3),
    Header = <<<<<<<<"--- "/utf8, Old_name/binary>>/binary, "\n+++ "/utf8>>/binary,
            New_name/binary>>/binary,
        "\n"/utf8>>,
    Body = gleam@string:join(
        gleam@list:map(Hunks, fun format_hunk/1),
        <<""/utf8>>
    ),
    <<Header/binary, Body/binary>>.

-file("src/gliff/internal/unified.gleam", 16).
?DOC(false).
-spec to_unified_with(list(gliff@types:edit()), binary(), binary(), integer()) -> binary().
to_unified_with(Edits, Old_name, New_name, Context) ->
    Hunks = edits_to_hunks(Edits, Context),
    Header = <<<<<<<<"--- "/utf8, Old_name/binary>>/binary, "\n+++ "/utf8>>/binary,
            New_name/binary>>/binary,
        "\n"/utf8>>,
    Body = gleam@string:join(
        gleam@list:map(Hunks, fun format_hunk/1),
        <<""/utf8>>
    ),
    <<Header/binary, Body/binary>>.

-file("src/gliff/internal/unified.gleam", 299).
?DOC(false).
-spec skip_file_headers(list(binary())) -> list(binary()).
skip_file_headers(Lines) ->
    case Lines of
        [<<"---"/utf8, _/binary>>, <<"+++ "/utf8, _/binary>> | Rest] ->
            Rest;

        [_ | Rest@1] ->
            skip_file_headers(Rest@1);

        [] ->
            []
    end.

-file("src/gliff/internal/unified.gleam", 399).
?DOC(false).
-spec digit_value(binary()) -> {ok, integer()} | {error, nil}.
digit_value(D) ->
    case D of
        <<"0"/utf8>> ->
            {ok, 0};

        <<"1"/utf8>> ->
            {ok, 1};

        <<"2"/utf8>> ->
            {ok, 2};

        <<"3"/utf8>> ->
            {ok, 3};

        <<"4"/utf8>> ->
            {ok, 4};

        <<"5"/utf8>> ->
            {ok, 5};

        <<"6"/utf8>> ->
            {ok, 6};

        <<"7"/utf8>> ->
            {ok, 7};

        <<"8"/utf8>> ->
            {ok, 8};

        <<"9"/utf8>> ->
            {ok, 9};

        _ ->
            {error, nil}
    end.

-file("src/gliff/internal/unified.gleam", 370).
?DOC(false).
-spec parse_int_loop(list(binary()), integer(), boolean()) -> {ok, integer()} |
    {error, binary()}.
parse_int_loop(Chars, Acc, Started) ->
    case Chars of
        [] ->
            case Started of
                true ->
                    {ok, Acc};

                false ->
                    {error, <<"Empty integer"/utf8>>}
            end;

        [<<"-"/utf8>> | Rest] ->
            case Started of
                false ->
                    case parse_int_loop(Rest, 0, false) of
                        {ok, N} ->
                            {ok, - N};

                        {error, E} ->
                            {error, E}
                    end;

                true ->
                    {error, <<"Unexpected '-'"/utf8>>}
            end;

        [D | Rest@1] ->
            case digit_value(D) of
                {ok, V} ->
                    parse_int_loop(Rest@1, (Acc * 10) + V, true);

                {error, _} ->
                    {error, <<"Not a digit: "/utf8, D/binary>>}
            end
    end.

-file("src/gliff/internal/unified.gleam", 366).
?DOC(false).
-spec parse_int(binary()) -> {ok, integer()} | {error, binary()}.
parse_int(S) ->
    parse_int_loop(gleam@string:to_graphemes(S), 0, false).

-file("src/gliff/internal/unified.gleam", 350).
?DOC(false).
-spec parse_range(binary()) -> {ok, {integer(), integer()}} | {error, binary()}.
parse_range(Range) ->
    case gleam@string:split(Range, <<","/utf8>>) of
        [Start_str, Count_str] ->
            case {parse_int(Start_str), parse_int(Count_str)} of
                {{ok, S}, {ok, C}} ->
                    {ok, {S, C}};

                {_, _} ->
                    {error, <<"Invalid range numbers"/utf8>>}
            end;

        [Start_str@1] ->
            case parse_int(Start_str@1) of
                {ok, S@1} ->
                    {ok, {S@1, 1}};

                {error, _} ->
                    {error, <<"Invalid range number"/utf8>>}
            end;

        _ ->
            {error, <<"Invalid range"/utf8>>}
    end.

-file("src/gliff/internal/unified.gleam", 335).
?DOC(false).
-spec parse_ranges(binary()) -> {ok,
        {integer(), integer(), integer(), integer()}} |
    {error, binary()}.
parse_ranges(Range_str) ->
    case gleam@string:split(Range_str, <<" "/utf8>>) of
        [Old_range, New_range | _] ->
            case {parse_range(gleam@string:drop_start(Old_range, 1)),
                parse_range(gleam@string:drop_start(New_range, 1))} of
                {{ok, {Os, Oc}}, {ok, {Ns, Nc}}} ->
                    {ok, {Os, Oc, Ns, Nc}};

                {_, _} ->
                    {error, <<"Invalid range: "/utf8, Range_str/binary>>}
            end;

        _ ->
            {error, <<"Invalid range format: "/utf8, Range_str/binary>>}
    end.

-file("src/gliff/internal/unified.gleam", 328).
?DOC(false).
-spec parse_hunk_header(binary()) -> {ok,
        {integer(), integer(), integer(), integer()}} |
    {error, binary()}.
parse_hunk_header(Header) ->
    case gleam@string:split(Header, <<"@@"/utf8>>) of
        [_, Range_str | _] ->
            parse_ranges(gleam@string:trim(Range_str));

        _ ->
            {error, <<"Invalid hunk header: "/utf8, Header/binary>>}
    end.

-file("src/gliff/internal/unified.gleam", 442).
?DOC(false).
-spec group_raw_body_loop(list(gliff@types:edit()), list(gliff@types:edit())) -> list(gliff@types:edit()).
group_raw_body_loop(Edits, Acc) ->
    case Edits of
        [] ->
            Acc;

        [{equal, L1}, {equal, L2} | Rest] ->
            group_raw_body_loop([{equal, lists:append(L1, L2)} | Rest], Acc);

        [{insert, L1@1}, {insert, L2@1} | Rest@1] ->
            group_raw_body_loop(
                [{insert, lists:append(L1@1, L2@1)} | Rest@1],
                Acc
            );

        [{delete, L1@2}, {delete, L2@2} | Rest@2] ->
            group_raw_body_loop(
                [{delete, lists:append(L1@2, L2@2)} | Rest@2],
                Acc
            );

        [Edit | Rest@3] ->
            group_raw_body_loop(Rest@3, [Edit | Acc])
    end.

-file("src/gliff/internal/unified.gleam", 437).
?DOC(false).
-spec group_raw_body(list(gliff@types:edit())) -> list(gliff@types:edit()).
group_raw_body(Edits) ->
    _pipe = group_raw_body_loop(Edits, []),
    lists:reverse(_pipe).

-file("src/gliff/internal/unified.gleam", 415).
?DOC(false).
-spec parse_hunk_body(list(binary()), list(gliff@types:edit())) -> {list(gliff@types:edit()),
    list(binary())}.
parse_hunk_body(Lines, Acc) ->
    case Lines of
        [] ->
            {group_raw_body(lists:reverse(Acc)), []};

        [<<"@@"/utf8, _/binary>> = Rest_line | Rest] ->
            {group_raw_body(lists:reverse(Acc)), [Rest_line | Rest]};

        [Line | Rest@1] ->
            Edit = case gleam@string:first(Line) of
                {ok, <<"+"/utf8>>} ->
                    {insert, [gleam@string:drop_start(Line, 1)]};

                {ok, <<"-"/utf8>>} ->
                    {delete, [gleam@string:drop_start(Line, 1)]};

                {ok, <<" "/utf8>>} ->
                    {equal, [gleam@string:drop_start(Line, 1)]};

                _ ->
                    {equal, [Line]}
            end,
            parse_hunk_body(Rest@1, [Edit | Acc])
    end.

-file("src/gliff/internal/unified.gleam", 307).
?DOC(false).
-spec parse_hunks(list(binary()), list(gliff@types:hunk())) -> {ok,
        list(gliff@types:hunk())} |
    {error, binary()}.
parse_hunks(Lines, Acc) ->
    case Lines of
        [] ->
            {ok, lists:reverse(Acc)};

        [<<""/utf8>> | Rest] ->
            parse_hunks(Rest, Acc);

        [<<"@@"/utf8, _/binary>> = Header | Rest@1] ->
            case parse_hunk_header(Header) of
                {ok, {Old_start, Old_count, New_start, New_count}} ->
                    {Edits, Remaining} = parse_hunk_body(Rest@1, []),
                    Hunk = {hunk,
                        Old_start,
                        Old_count,
                        New_start,
                        New_count,
                        Edits},
                    parse_hunks(Remaining, [Hunk | Acc]);

                {error, E} ->
                    {error, E}
            end;

        [Line | _] ->
            {error, <<"Unexpected line: "/utf8, Line/binary>>}
    end.

-file("src/gliff/internal/unified.gleam", 293).
?DOC(false).
-spec parse_unified(binary()) -> {ok, list(gliff@types:hunk())} |
    {error, binary()}.
parse_unified(Input) ->
    Lines = gleam@string:split(Input, <<"\n"/utf8>>),
    Content_lines = skip_file_headers(Lines),
    parse_hunks(Content_lines, []).

-file("src/gliff/internal/unified.gleam", 28).
?DOC(false).
-spec from_unified(binary()) -> {ok, list(gliff@types:hunk())} |
    {error, binary()}.
from_unified(Unified) ->
    parse_unified(Unified).