src/gliff@internal@cleanup.erl

-module(gliff@internal@cleanup).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gliff/internal/cleanup.gleam").
-export([semantic/1, semantic_lossless/1]).

-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).

-file("src/gliff/internal/cleanup.gleam", 221).
?DOC(false).
-spec is_blank_line(binary()) -> boolean().
is_blank_line(S) ->
    gleam@string:trim(S) =:= <<""/utf8>>.

-file("src/gliff/internal/cleanup.gleam", 225).
?DOC(false).
-spec is_sentence_end(binary()) -> boolean().
is_sentence_end(C) ->
    ((C =:= <<"."/utf8>>) orelse (C =:= <<"!"/utf8>>)) orelse (C =:= <<"?"/utf8>>).

-file("src/gliff/internal/cleanup.gleam", 229).
?DOC(false).
-spec is_whitespace(binary()) -> boolean().
is_whitespace(C) ->
    (((C =:= <<" "/utf8>>) orelse (C =:= <<"\t"/utf8>>)) orelse (C =:= <<"\n"/utf8>>))
    orelse (C =:= <<"\r"/utf8>>).

-file("src/gliff/internal/cleanup.gleam", 240).
?DOC(false).
-spec is_alpha_or_digit(binary()) -> boolean().
is_alpha_or_digit(C) ->
    gleam_stdlib:contains_string(
        <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"/utf8>>,
        C
    ).

-file("src/gliff/internal/cleanup.gleam", 233).
?DOC(false).
-spec is_non_alnum(binary()) -> boolean().
is_non_alnum(C) ->
    case C of
        <<" "/utf8>> ->
            false;

        <<"\t"/utf8>> ->
            false;

        <<"\n"/utf8>> ->
            false;

        <<"\r"/utf8>> ->
            false;

        _ ->
            not is_alpha_or_digit(C)
    end.

-file("src/gliff/internal/cleanup.gleam", 185).
?DOC(false).
-spec score_between(binary(), binary()) -> integer().
score_between(Left, Right) ->
    L_end = gleam@string:last(Left),
    R_start = gleam@string:first(Right),
    case {L_end, R_start} of
        {{ok, Lc}, {ok, Rc}} ->
            L_blank = is_blank_line(Left),
            R_blank = is_blank_line(Right),
            case L_blank andalso R_blank of
                true ->
                    6;

                false ->
                    case L_blank orelse R_blank of
                        true ->
                            5;

                        false ->
                            case (Lc =:= <<"\n"/utf8>>) orelse (Rc =:= <<"\n"/utf8>>) of
                                true ->
                                    5;

                                false ->
                                    case is_sentence_end(Lc) andalso (Rc =:= <<" "/utf8>>) of
                                        true ->
                                            4;

                                        false ->
                                            case is_whitespace(Lc) orelse is_whitespace(
                                                Rc
                                            ) of
                                                true ->
                                                    3;

                                                false ->
                                                    case is_non_alnum(Lc) orelse is_non_alnum(
                                                        Rc
                                                    ) of
                                                        true ->
                                                            2;

                                                        false ->
                                                            1
                                                    end
                                            end
                                    end
                            end
                    end
            end;

        {_, _} ->
            5
    end.

-file("src/gliff/internal/cleanup.gleam", 178).
?DOC(false).
-spec score_pair({ok, binary()} | {error, nil}, {ok, binary()} | {error, nil}) -> integer().
score_pair(Left, Right) ->
    case {Left, Right} of
        {{ok, L}, {ok, R}} ->
            score_between(L, R);

        {_, _} ->
            5
    end.

-file("src/gliff/internal/cleanup.gleam", 166).
?DOC(false).
-spec score_boundary(list(binary()), list(binary())) -> integer().
score_boundary(Edit_end, After) ->
    Last_of_edit = gleam@list:last(Edit_end),
    First_of_after = gleam@list:first(After),
    score_pair(Last_of_edit, First_of_after).

-file("src/gliff/internal/cleanup.gleam", 172).
?DOC(false).
-spec score_boundary_left(list(binary()), list(binary())) -> integer().
score_boundary_left(Edit_start, Before) ->
    Last_of_before = gleam@list:last(Before),
    First_of_edit = gleam@list:first(Edit_start),
    score_pair(Last_of_before, First_of_edit).

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

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

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

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

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

-file("src/gliff/internal/cleanup.gleam", 249).
?DOC(false).
-spec merge_adjacent(list(gliff@types:edit())) -> list(gliff@types:edit()).
merge_adjacent(Edits) ->
    _pipe = merge_loop(Edits, []),
    lists:reverse(_pipe).

-file("src/gliff/internal/cleanup.gleam", 269).
?DOC(false).
-spec count_chars(list(binary())) -> integer().
count_chars(Lines) ->
    gleam@list:fold(Lines, 0, fun(Acc, Line) -> Acc + string:length(Line) end).

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

        [{delete, D}, {equal, Eq}, {insert, I} | Rest] ->
            Eq_len = count_chars(Eq),
            D_len = count_chars(D),
            I_len = count_chars(I),
            case ((Eq_len * 2) < D_len) orelse ((Eq_len * 2) < I_len) of
                true ->
                    eliminate_loop(
                        Rest,
                        [{insert, lists:append([Eq, I])},
                            {delete, lists:append([D, Eq])} |
                            Acc]
                    );

                false ->
                    eliminate_loop(
                        [{equal, Eq}, {insert, I} | Rest],
                        [{delete, D} | Acc]
                    )
            end;

        [{insert, I@1}, {equal, Eq@1}, {delete, D@1} | Rest@1] ->
            Eq_len@1 = count_chars(Eq@1),
            D_len@1 = count_chars(D@1),
            I_len@1 = count_chars(I@1),
            case ((Eq_len@1 * 2) < D_len@1) orelse ((Eq_len@1 * 2) < I_len@1) of
                true ->
                    eliminate_loop(
                        Rest@1,
                        [{delete, lists:append([Eq@1, D@1])},
                            {insert, lists:append([I@1, Eq@1])} |
                            Acc]
                    );

                false ->
                    eliminate_loop(
                        [{equal, Eq@1}, {delete, D@1} | Rest@1],
                        [{insert, I@1} | Acc]
                    )
            end;

        [Edit | Rest@2] ->
            eliminate_loop(Rest@2, [Edit | Acc])
    end.

-file("src/gliff/internal/cleanup.gleam", 19).
?DOC(false).
-spec eliminate_trivial_equalities(list(gliff@types:edit())) -> list(gliff@types:edit()).
eliminate_trivial_equalities(Edits) ->
    _pipe = eliminate_loop(Edits, []),
    lists:reverse(_pipe).

-file("src/gliff/internal/cleanup.gleam", 5).
?DOC(false).
-spec semantic(list(gliff@types:edit())) -> list(gliff@types:edit()).
semantic(Edits) ->
    _pipe = Edits,
    _pipe@1 = eliminate_trivial_equalities(_pipe),
    merge_adjacent(_pipe@1).

-file("src/gliff/internal/cleanup.gleam", 285).
?DOC(false).
-spec common_prefix_strings_inner(
    list(binary()),
    list(binary()),
    list(binary())
) -> list(binary()).
common_prefix_strings_inner(A, B, Acc) ->
    case {A, B} of
        {[Ah | At], [Bh | Bt]} when Ah =:= Bh ->
            common_prefix_strings_inner(At, Bt, [Ah | Acc]);

        {_, _} ->
            Acc
    end.

-file("src/gliff/internal/cleanup.gleam", 273).
?DOC(false).
-spec common_suffix_strings(list(binary()), list(binary())) -> list(binary()).
common_suffix_strings(A, B) ->
    A_rev = lists:reverse(A),
    B_start = B,
    _pipe = common_prefix_strings_inner(A_rev, B_start, []),
    lists:reverse(_pipe).

-file("src/gliff/internal/cleanup.gleam", 280).
?DOC(false).
-spec common_prefix_strings(list(binary()), list(binary())) -> list(binary()).
common_prefix_strings(A, B) ->
    _pipe = common_prefix_strings_inner(A, B, []),
    lists:reverse(_pipe).

-file("src/gliff/internal/cleanup.gleam", 297).
?DOC(false).
-spec take_last(list(EJR), integer()) -> list(EJR).
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/cleanup.gleam", 121).
?DOC(false).
-spec find_best_shift(list(binary()), list(binary()), list(binary())) -> {list(binary()),
    list(binary()),
    list(binary())}.
find_best_shift(Before, Edit, After) ->
    Suffix_common = common_suffix_strings(Edit, After),
    Prefix_common = common_prefix_strings(
        Edit,
        begin
            _pipe = Before,
            lists:reverse(_pipe)
        end
    ),
    Suffix_len = erlang:length(Suffix_common),
    Prefix_len = erlang:length(Prefix_common),
    case (Suffix_len > 0) andalso (score_boundary(Suffix_common, After) > score_boundary(
        Edit,
        After
    )) of
        true ->
            Shifted_edit = lists:append(
                take_last(Before, Suffix_len),
                gleam@list:take(Edit, erlang:length(Edit) - Suffix_len)
            ),
            New_before = gleam@list:take(
                Before,
                erlang:length(Before) - Suffix_len
            ),
            New_after = lists:append(gleam@list:take(Edit, Suffix_len), After),
            {New_before, Shifted_edit, New_after};

        false ->
            case (Prefix_len > 0) andalso (score_boundary_left(
                Prefix_common,
                Before
            )
            > score_boundary_left(Edit, Before)) of
                true ->
                    Shifted_edit@1 = lists:append(
                        gleam@list:drop(Edit, Prefix_len),
                        take_last(Before, Prefix_len)
                    ),
                    New_before@1 = lists:append(
                        Before,
                        gleam@list:take(Edit, Prefix_len)
                    ),
                    {New_before@1, Shifted_edit@1, After};

                false ->
                    {Before, Edit, After}
            end
    end.

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

        [{equal, Eq1}, {delete, D}, {equal, Eq2} | Rest] ->
            {New_eq1, New_d, New_eq2} = find_best_shift(Eq1, D, Eq2),
            case New_eq1 of
                [] ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2} | Rest],
                        [{delete, New_d} | Acc]
                    );

                _ ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2} | Rest],
                        [{delete, New_d}, {equal, New_eq1} | Acc]
                    )
            end;

        [{equal, Eq1@1}, {insert, I}, {equal, Eq2@1} | Rest@1] ->
            {New_eq1@1, New_i, New_eq2@1} = find_best_shift(Eq1@1, I, Eq2@1),
            case New_eq1@1 of
                [] ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2@1} | Rest@1],
                        [{insert, New_i} | Acc]
                    );

                _ ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2@1} | Rest@1],
                        [{insert, New_i}, {equal, New_eq1@1} | Acc]
                    )
            end;

        [{equal, Eq1@2}, {delete, D@1}, {insert, I@1}, {equal, Eq2@2} | Rest@2] ->
            {New_eq1@2, New_d@1, _} = find_best_shift(Eq1@2, D@1, Eq2@2),
            {_, New_i@1, New_eq2@2} = find_best_shift(Eq1@2, I@1, Eq2@2),
            case New_eq1@2 of
                [] ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2@2} | Rest@2],
                        [{insert, New_i@1}, {delete, New_d@1} | Acc]
                    );

                _ ->
                    shift_edits_to_boundaries(
                        [{equal, New_eq2@2} | Rest@2],
                        [{insert, New_i@1},
                            {delete, New_d@1},
                            {equal, New_eq1@2} |
                            Acc]
                    )
            end;

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

-file("src/gliff/internal/cleanup.gleam", 11).
?DOC(false).
-spec semantic_lossless(list(gliff@types:edit())) -> list(gliff@types:edit()).
semantic_lossless(Edits) ->
    _pipe = shift_edits_to_boundaries(Edits, []),
    _pipe@1 = lists:reverse(_pipe),
    merge_adjacent(_pipe@1).