src/gliff@internal@merge.erl

-module(gliff@internal@merge).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gliff/internal/merge.gleam").
-export([merge3/3]).
-export_type([change_hunk/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 change_hunk() :: {change_hunk, integer(), integer(), list(binary())}.

-file("src/gliff/internal/merge.gleam", 42).
?DOC(false).
-spec collect_change(
    list(gliff@types:raw_edit()),
    integer(),
    list(binary()),
    list(binary())
) -> {change_hunk(), list(gliff@types:raw_edit()), integer()}.
collect_change(Edits, Pos, Deleted, Inserted) ->
    case Edits of
        [{raw_delete, V} | Rest] ->
            collect_change(Rest, Pos + 1, [V | Deleted], Inserted);

        [{raw_insert, V@1} | Rest@1] ->
            collect_change(Rest@1, Pos, Deleted, [V@1 | Inserted]);

        _ ->
            Base_start = Pos - erlang:length(Deleted),
            Hunk = {change_hunk, Base_start, Pos, lists:reverse(Inserted)},
            {Hunk, Edits, Pos}
    end.

-file("src/gliff/internal/merge.gleam", 27).
?DOC(false).
-spec edits_to_change_hunks(
    list(gliff@types:raw_edit()),
    integer(),
    list(change_hunk())
) -> list(change_hunk()).
edits_to_change_hunks(Edits, Pos, Acc) ->
    case Edits of
        [] ->
            lists:reverse(Acc);

        [{raw_equal, _} | Rest] ->
            edits_to_change_hunks(Rest, Pos + 1, Acc);

        _ ->
            {Hunk, Remaining, New_pos} = collect_change(Edits, Pos, [], []),
            edits_to_change_hunks(Remaining, New_pos, [Hunk | Acc])
    end.

-file("src/gliff/internal/merge.gleam", 208).
?DOC(false).
-spec hunks_overlap(change_hunk(), change_hunk()) -> boolean().
hunks_overlap(A, B) ->
    (erlang:element(2, A) < erlang:element(3, B)) andalso (erlang:element(2, B)
    < erlang:element(3, A)).

-file("src/gliff/internal/merge.gleam", 212).
?DOC(false).
-spec slice_lines(list(binary()), integer(), integer()) -> list(binary()).
slice_lines(Lines, From, To) ->
    _pipe = Lines,
    _pipe@1 = gleam@list:drop(_pipe, From),
    gleam@list:take(_pipe@1, To - From).

-file("src/gliff/internal/merge.gleam", 218).
?DOC(false).
-spec split_lines(binary()) -> list(binary()).
split_lines(Text) ->
    case Text of
        <<""/utf8>> ->
            [];

        _ ->
            gleam@string:split(Text, <<"\n"/utf8>>)
    end.

-file("src/gliff/internal/merge.gleam", 225).
?DOC(false).
-spec max(integer(), integer()) -> integer().
max(A, B) ->
    case A > B of
        true ->
            A;

        false ->
            B
    end.

-file("src/gliff/internal/merge.gleam", 232).
?DOC(false).
-spec min(integer(), integer()) -> integer().
min(A, B) ->
    case A < B of
        true ->
            A;

        false ->
            B
    end.

-file("src/gliff/internal/merge.gleam", 66).
?DOC(false).
-spec merge_hunks(
    list(binary()),
    list(change_hunk()),
    list(change_hunk()),
    integer(),
    list(binary()),
    list(gliff@types:conflict())
) -> gliff@types:merge_result().
merge_hunks(Base_lines, Ours, Theirs, Pos, Output, Conflicts) ->
    case {Ours, Theirs} of
        {[], []} ->
            Remaining = gleam@list:drop(Base_lines, Pos),
            Final_output = lists:append(lists:reverse(Output), Remaining),
            Merged = gleam@string:join(Final_output, <<"\n"/utf8>>),
            case Conflicts of
                [] ->
                    {merge_ok, Merged};

                _ ->
                    {merge_conflict, Merged, lists:reverse(Conflicts)}
            end;

        {[O | Rest_ours], []} ->
            Context = slice_lines(Base_lines, Pos, erlang:element(2, O)),
            New_output = lists:append(
                lists:reverse(erlang:element(4, O)),
                lists:append(lists:reverse(Context), Output)
            ),
            merge_hunks(
                Base_lines,
                Rest_ours,
                [],
                erlang:element(3, O),
                New_output,
                Conflicts
            );

        {[], [T | Rest_theirs]} ->
            Context@1 = slice_lines(Base_lines, Pos, erlang:element(2, T)),
            New_output@1 = lists:append(
                lists:reverse(erlang:element(4, T)),
                lists:append(lists:reverse(Context@1), Output)
            ),
            merge_hunks(
                Base_lines,
                [],
                Rest_theirs,
                erlang:element(3, T),
                New_output@1,
                Conflicts
            );

        {[O@1 | Rest_ours@1], [T@1 | Rest_theirs@1]} ->
            case hunks_overlap(O@1, T@1) of
                false ->
                    case erlang:element(2, O@1) =< erlang:element(2, T@1) of
                        true ->
                            Context@2 = slice_lines(
                                Base_lines,
                                Pos,
                                erlang:element(2, O@1)
                            ),
                            New_output@2 = lists:append(
                                lists:reverse(erlang:element(4, O@1)),
                                lists:append(lists:reverse(Context@2), Output)
                            ),
                            merge_hunks(
                                Base_lines,
                                Rest_ours@1,
                                [T@1 | Rest_theirs@1],
                                erlang:element(3, O@1),
                                New_output@2,
                                Conflicts
                            );

                        false ->
                            Context@3 = slice_lines(
                                Base_lines,
                                Pos,
                                erlang:element(2, T@1)
                            ),
                            New_output@3 = lists:append(
                                lists:reverse(erlang:element(4, T@1)),
                                lists:append(lists:reverse(Context@3), Output)
                            ),
                            merge_hunks(
                                Base_lines,
                                [O@1 | Rest_ours@1],
                                Rest_theirs@1,
                                erlang:element(3, T@1),
                                New_output@3,
                                Conflicts
                            )
                    end;

                true ->
                    case erlang:element(4, O@1) =:= erlang:element(4, T@1) of
                        true ->
                            Context@4 = slice_lines(
                                Base_lines,
                                Pos,
                                erlang:element(2, O@1)
                            ),
                            Merged_end = max(
                                erlang:element(3, O@1),
                                erlang:element(3, T@1)
                            ),
                            New_output@4 = lists:append(
                                lists:reverse(erlang:element(4, O@1)),
                                lists:append(lists:reverse(Context@4), Output)
                            ),
                            merge_hunks(
                                Base_lines,
                                Rest_ours@1,
                                Rest_theirs@1,
                                Merged_end,
                                New_output@4,
                                Conflicts
                            );

                        false ->
                            Merged_start = min(
                                erlang:element(2, O@1),
                                erlang:element(2, T@1)
                            ),
                            Merged_end@1 = max(
                                erlang:element(3, O@1),
                                erlang:element(3, T@1)
                            ),
                            Context@5 = slice_lines(
                                Base_lines,
                                Pos,
                                Merged_start
                            ),
                            Base_conflict_lines = slice_lines(
                                Base_lines,
                                Merged_start,
                                Merged_end@1
                            ),
                            Conflict_markers = [<<">>>>>>> theirs"/utf8>> |
                                lists:append(
                                    lists:reverse(erlang:element(4, T@1)),
                                    [<<"======="/utf8>> |
                                        lists:append(
                                            lists:reverse(
                                                erlang:element(4, O@1)
                                            ),
                                            [<<"<<<<<<< ours"/utf8>>]
                                        )]
                                )],
                            New_output@5 = lists:append(
                                Conflict_markers,
                                lists:append(lists:reverse(Context@5), Output)
                            ),
                            Conflict = {conflict,
                                Base_conflict_lines,
                                erlang:element(4, O@1),
                                erlang:element(4, T@1)},
                            merge_hunks(
                                Base_lines,
                                Rest_ours@1,
                                Rest_theirs@1,
                                Merged_end@1,
                                New_output@5,
                                [Conflict | Conflicts]
                            )
                    end
            end
    end.

-file("src/gliff/internal/merge.gleam", 9).
?DOC(false).
-spec merge3(binary(), binary(), binary()) -> gliff@types:merge_result().
merge3(Base, Ours, Theirs) ->
    Base_lines = split_lines(Base),
    Ours_lines = split_lines(Ours),
    Theirs_lines = split_lines(Theirs),
    Ours_edits = gliff@internal@myers:diff(Base_lines, Ours_lines),
    Theirs_edits = gliff@internal@myers:diff(Base_lines, Theirs_lines),
    Ours_hunks = edits_to_change_hunks(Ours_edits, 0, []),
    Theirs_hunks = edits_to_change_hunks(Theirs_edits, 0, []),
    merge_hunks(Base_lines, Ours_hunks, Theirs_hunks, 0, [], []).